Skip to content

Commit

Permalink
v1.0.1 (#6)
Browse files Browse the repository at this point in the history
Co-authored-by: Douglas Marques <[email protected]>
  • Loading branch information
douglasmarques and doug-atlassian authored Aug 20, 2024
1 parent affbc84 commit 5897f3e
Show file tree
Hide file tree
Showing 17 changed files with 617 additions and 594 deletions.
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ java {

dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.jsoup)
implementation(libs.kotlinx.serialization.json)
api(libs.jsoup)
testImplementation(kotlin("test"))
testImplementation(libs.test.assertj)
}

group = "com.atlassian"
version = "1.0.0"
version = "1.0.1"
description = "prosemirror"

val javaVersion = JavaVersion.VERSION_17
Expand Down
1,057 changes: 519 additions & 538 deletions config/ktlint/baseline.xml

Large diffs are not rendered by default.

34 changes: 20 additions & 14 deletions src/main/kotlin/com/atlassian/prosemirror/history/History.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.atlassian.prosemirror.transform.Mapping
import com.atlassian.prosemirror.transform.Step
import com.atlassian.prosemirror.transform.StepMap
import com.atlassian.prosemirror.transform.Transform
import com.atlassian.prosemirror.util.safeMode

import kotlin.math.max
import kotlin.math.min
Expand Down Expand Up @@ -586,22 +587,27 @@ class HistoryPluginSpec(conf: HistoryOptionsConfig) : PluginSpec<HistoryState>()
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth")
fun buildCommand(redo: Boolean, scroll: Boolean): Command {
return { state, dispatch ->
val hist = historyKey.getState(state)
if (hist == null || (if (redo) hist.undone else hist.done).eventCount == 0) {
false
} else {
if (dispatch != null) {
histTransaction(hist, state, redo)?.let {
dispatch(
if (scroll) {
it.scrollIntoView()
} else {
it
}
)
try {
val hist = historyKey.getState(state)
if (hist == null || (if (redo) hist.undone else hist.done).eventCount == 0) {
false
} else {
if (dispatch != null) {
histTransaction(hist, state, redo)?.let {
dispatch(
if (scroll) {
it.scrollIntoView()
} else {
it
}
)
}
}
true
}
true
} catch (ex: Exception) {
if (!safeMode) throw ex
false
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/atlassian/prosemirror/model/Fragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class Fragment {
max(0, from - start),
min(child.content.size, to - start),
f,
nodeStart + start
nodeStart + start,
terminate
)
}
if (terminate()) return
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/com/atlassian/prosemirror/model/Replace.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ data class Slice(
}

fun removeRange(content: Fragment, from: Int, to: Int): Fragment {
val (index, offset) = content.findIndex(from).run { Index.index to Index.offset }
val (index, offset) = content.findIndex(from).run { index to offset }
val child = content.maybeChild(index)
val (indexTo, offsetTo) = content.findIndex(to).run { Index.index to Index.offset }
val (indexTo, offsetTo) = content.findIndex(to).run { this.index to this.offset }
if (offset == from || child!!.isText) {
if (offsetTo != to && !content.child(indexTo).isText) throw RangeError("Removing non-flat range")
return content.cut(0, from).append(content.cut(to))
Expand All @@ -119,7 +119,7 @@ fun removeRange(content: Fragment, from: Int, to: Int): Fragment {

@Suppress("ReturnCount")
fun insertInto(content: Fragment, dist: Int, insert: Fragment, parent: Node? = null): Fragment? {
val (index, offset) = content.findIndex(dist).run { Index.index to Index.offset }
val (index, offset) = content.findIndex(dist).run { index to offset }
val child = content.maybeChild(index)
if (offset == dist || child!!.isText) {
if (parent != null && !parent.canReplace(index, index, insert)) return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ class ResolvedPos(
var node = doc
while (true) {
val ind = node.content.findIndex(parentOffset)
val index = Index.index
val offset = Index.offset
val index = ind.index
val offset = ind.offset
val rem = parentOffset - offset
path.add(node)
path.add(index)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/atlassian/prosemirror/model/ToDom.kt
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ open class DOMSerializer(
fun fromSchema(schema: Schema): DOMSerializer {
return schema.cached.getOrPut("domSerializer") {
DOMSerializer(
nodesFromSchema(schema),
marksFromSchema(schema)
this.nodesFromSchema(schema),
this.marksFromSchema(schema)
)
} as DOMSerializer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual

@Suppress("UNCHECKED_CAST")
internal val JSON: Json by lazy {
val JSON: Json by lazy {
Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/atlassian/prosemirror/state/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ open class Plugin<PluginState>(
* transactions, i.e. it won't be passed transactions that it
* already saw.
*/
internal open val appendTransaction: ((List<Transaction>, PMEditorState, PMEditorState) -> Transaction?)? = null
open val appendTransaction: ((List<Transaction>, PMEditorState, PMEditorState) -> Transaction?)? = null

/**
* When present, this will be called before a transaction is
Expand Down
17 changes: 16 additions & 1 deletion src/main/kotlin/com/atlassian/prosemirror/state/State.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.atlassian.prosemirror.model.Node
import com.atlassian.prosemirror.model.RangeError
import com.atlassian.prosemirror.model.Schema
import com.atlassian.prosemirror.transform.Transform
import com.atlassian.prosemirror.util.safeMode
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
Expand Down Expand Up @@ -106,6 +107,10 @@ internal class EmptyEditorStateConfig(
override val plugins: List<Plugin<*>> = emptyList()
) : EditorStateConfig

// The state of a ProseMirror editor is represented by an object of this type. A state is a
// persistent data structure—it isn't updated, but rather a new state value is computed from an old
// one using the [`apply`](#state.EditorState.apply) method.
//
// A state holds a number of built-in fields, and plugins can [define](#state.PluginSpec.state)
// additional fields.
class PMEditorState internal constructor(
Expand Down Expand Up @@ -166,8 +171,18 @@ class PMEditorState internal constructor(
// Verbose variant of [`apply`](#state.EditorState.apply) that returns the precise transactions
// that were applied (which might be influenced by the
// [transaction hooks](#state.PluginSpec.filterTransaction) of plugins) along with the new state.
@Suppress("TooGenericExceptionCaught")
fun applyTransaction(rootTr: Transaction): ApplyTransactionResult {
return try {
applyTransactionInternal(rootTr)
} catch (e: Exception) {
if (!safeMode) throw e
ApplyTransactionResult(state = this, transactions = emptyList()) // empty transaction result
}
}

@Suppress("NestedBlockDepth", "ComplexMethod")
private fun applyTransaction(rootTr: Transaction): ApplyTransactionResult {
private fun applyTransactionInternal(rootTr: Transaction): ApplyTransactionResult {
if (!this.filterTransaction(rootTr)) return ApplyTransactionResult(state = this, transactions = emptyList())

val trs = mutableListOf(rootTr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,13 @@ class Transaction : Transform {
if (selection._from.doc.nodeId != this.doc.nodeId) {
throw RangeError("Selection passed to setSelection must point at the current document")
}
val changed = this.curSelection != selection
this.curSelection = selection
this.curSelectionFor = this.steps.size
this.updated = (this.updated or UPDATED_SEL) and UPDATED_MARKS.inv()
this.storedMarks = null
if (changed) {
this.storedMarks = null
}
}

// Whether the selection was explicitly updated by this transaction.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import com.atlassian.prosemirror.model.Node
import com.atlassian.prosemirror.model.NodeType
import com.atlassian.prosemirror.model.ResolvedPos
import com.atlassian.prosemirror.model.Slice
import com.atlassian.prosemirror.util.resolveAndLog
import com.atlassian.prosemirror.util.resolveSafe
import kotlin.math.max
import kotlin.math.min

Expand All @@ -16,7 +16,7 @@ import kotlin.math.min
// it would be a no-op (an empty slice over an empty range).
fun replaceStep(doc: Node, from: Int, to: Int = from, slice: Slice = Slice.empty): Step? {
if (from == to && slice.size == 0) return null
val (resolvedFrom, resolvedTo) = doc.resolveAndLog(from, to) ?: return null
val (resolvedFrom, resolvedTo) = doc.resolveSafe(from, to) ?: return null
// Optimization -- avoid work if it's obvious that it's not needed.
if (fitsTrivially(resolvedFrom, resolvedTo, slice)) return ReplaceStep(from, to, slice)
return Fitter(resolvedFrom, resolvedTo, slice).fit()
Expand Down Expand Up @@ -439,7 +439,7 @@ fun definesContent(type: NodeType) = type.spec.defining == true || type.spec.def
@Suppress("LongMethod", "ComplexMethod")
fun replaceRange(tr: Transform, from: Int, to: Int, slice: Slice): Transform? {
if (slice.size == 0) return tr.deleteRange(from, to)
val (_from, _to) = tr.doc.resolveAndLog(from, to) ?: return null
val (_from, _to) = tr.doc.resolveSafe(from, to) ?: return null
if (fitsTrivially(_from, _to, slice)) {
return tr.step(ReplaceStep(from, to, slice))
}
Expand Down Expand Up @@ -570,7 +570,7 @@ fun replaceRangeWith(tr: Transform, from: Int, to: Int, node: Node) {
}

fun deleteRange(tr: Transform, from: Int, to: Int): Transform {
val (_from, _to) = tr.doc.resolveAndLog(from, to) ?: return tr
val (_from, _to) = tr.doc.resolveSafe(from, to) ?: return tr
val covered = coveredDepths(_from, _to)
for (i in 0 until covered.size) {
val depth = covered[i]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.atlassian.prosemirror.model.NodeRange
import com.atlassian.prosemirror.model.NodeType
import com.atlassian.prosemirror.model.RangeError
import com.atlassian.prosemirror.model.Slice
import com.atlassian.prosemirror.util.resolveAndLog
import com.atlassian.prosemirror.util.resolveSafe

fun canCut(node: Node, start: Int, end: Int) =
(start == 0 || node.canReplace(start, node.childCount)) && (end == node.childCount || node.canReplace(0, end))
Expand Down Expand Up @@ -182,6 +182,8 @@ fun canChangeType(doc: Node, pos: Int, type: NodeType): Boolean {
return resolvedPos.parent.canReplaceWith(index, index + 1, type)
}

// Change the type, attributes, and/or marks of the node at `pos`. When `type` isn't given, the
// existing node type is preserved,
fun setNodeMarkup(tr: Transform, pos: Int, type: NodeType?, attrs: Attrs?, marks: List<Mark>?): Transform {
val node = tr.doc.nodeAt(pos) ?: throw RangeError("No node at given position")
val thisType = type ?: node.type
Expand Down Expand Up @@ -243,7 +245,7 @@ fun canSplit(doc: Node, pos: Int, depth: Int = 1, typesAfter: List<NodeBase?>? =
}

fun split(tr: Transform, pos: Int, depth: Int = 1, typesAfter: List<NodeBase?>?): Transform {
val thisPos = tr.doc.resolveAndLog(pos) ?: return tr
val thisPos = tr.doc.resolveSafe(pos) ?: return tr
var before = Fragment.empty
var after = Fragment.empty
var d = thisPos.depth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.atlassian.prosemirror.model.NodeBase
import com.atlassian.prosemirror.model.NodeRange
import com.atlassian.prosemirror.model.NodeType
import com.atlassian.prosemirror.model.Slice
import com.atlassian.prosemirror.util.safeMode

class TransformError(message: String, cause: Throwable? = null) : Error(message, cause)

Expand Down Expand Up @@ -39,11 +40,15 @@ open class Transform(
val docChanged: Boolean
get() = this.steps.isNotEmpty()

var error: Throwable? = null

// Apply a new step in this transform, saving the result. Throws an error when the step fails.
fun step(step: Step): Transform {
val result = this.maybeStep(step)
if (result.failed != null) {
throw TransformError(result.failed)
val exception = TransformError(result.failed)
this.error = exception
if (!safeMode) throw exception
}
return this
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/com/atlassian/prosemirror/util/Tools.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.atlassian.prosemirror.util
import kotlin.math.max
import kotlin.math.min

var safeMode = true

fun String.slice(from: Int, to: Int): String {
return substring(max(0, from), min(to, length))
}
Expand Down
34 changes: 16 additions & 18 deletions src/main/kotlin/com/atlassian/prosemirror/util/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ import com.atlassian.prosemirror.model.ResolvedPos

fun getMilliseconds() = System.currentTimeMillis()

fun Node.resolveAndLog(from: Int, to: Int): Pair<ResolvedPos, ResolvedPos>? {
try {
val resolvedFrom = resolve(from)
val resolvedTo = resolve(to)
return resolvedFrom to resolvedTo
} catch (e: RangeError) {
// It's safe to log Node.toString as UGC is not returned in Debug mode
// TODO: add library to log
return null
}
fun Node.resolveSafe(from: Int, to: Int): Pair<ResolvedPos, ResolvedPos>? {
try {
val resolvedFrom = resolve(from)
val resolvedTo = resolve(to)
return resolvedFrom to resolvedTo
} catch (e: RangeError) {
if (!safeMode) throw e
return null
}
}

fun Node.resolveAndLog(pos: Int): ResolvedPos? {
try {
return resolve(pos)
} catch (e: RangeError) {
// It's safe to log Node.toString as UGC is not returned in Debug mode
// TODO: add library to log
return null
}
fun Node.resolveSafe(pos: Int): ResolvedPos? {
try {
return resolve(pos)
} catch (e: RangeError) {
if (!safeMode) throw e
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ import com.atlassian.prosemirror.testbuilder.NodeSpecImpl
import com.atlassian.prosemirror.testbuilder.PMNodeBuilder.Companion.doc
import com.atlassian.prosemirror.testbuilder.PMNodeBuilder.Companion.pos
import com.atlassian.prosemirror.testbuilder.schema
import com.atlassian.prosemirror.util.safeMode
import kotlin.test.BeforeTest
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown
import kotlin.test.Test

@Suppress("LargeClass")
class TransformTest {

@BeforeTest
fun setup() {
safeMode = true
}

// region addMark
fun add(doc: Node, mark: Mark, expect: Node) {
Expand Down Expand Up @@ -510,13 +516,17 @@ class TransformTest {
)

@Test
fun `preserves content constraints before`() =
fun `preserves content constraints before`() {
safeMode = false
splitFail(doc { blockquote { +"<a>" + p { +"x" } } })
}

@Test
fun `preserves content constraints after`() =
fun `preserves content constraints after`() {
safeMode = false
splitFail(doc { blockquote { p { +"x" } + "<a>" } })
// endregion
}
// endregion

// region lift
fun lift(doc: Node, expect: Node) {
Expand Down

0 comments on commit 5897f3e

Please sign in to comment.