diff --git a/model/config/ktlint/baseline.xml b/model/config/ktlint/baseline.xml index de9b1c9..4a8cf06 100644 --- a/model/config/ktlint/baseline.xml +++ b/model/config/ktlint/baseline.xml @@ -703,25 +703,50 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -796,7 +821,4 @@ - - - diff --git a/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/FromDom.kt b/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/FromDom.kt index 1ab92c1..29513cd 100644 --- a/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/FromDom.kt +++ b/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/FromDom.kt @@ -323,7 +323,7 @@ class DOMParser( // or has an '=' sign after the prop, followed by the given // value. style.length > prop.length && - (style[prop.length] != 61.toChar() || style.slice(prop.length + 1 until style.length) != value) + (style[prop.length] != '=' || style.slice(prop.length + 1 until style.length) != value) ) { continue } diff --git a/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/ToDom.kt b/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/ToDom.kt index 5e20548..e3cffef 100644 --- a/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/ToDom.kt +++ b/model/src/commonMain/kotlin/com/atlassian/prosemirror/model/ToDom.kt @@ -164,7 +164,7 @@ open class DOMSerializer( if (space > 0) { dom.attr( // name.slice(0 until space), - name.slice(space + 1..space + 1), + name.substring(space + 1), attrs[name].toString() ) } else { diff --git a/model/src/commonTest/kotlin/com/atlassian/prosemirror/model/DomTest.kt b/model/src/commonTest/kotlin/com/atlassian/prosemirror/model/DomTest.kt index fd9f3da..d388349 100644 --- a/model/src/commonTest/kotlin/com/atlassian/prosemirror/model/DomTest.kt +++ b/model/src/commonTest/kotlin/com/atlassian/prosemirror/model/DomTest.kt @@ -7,48 +7,30 @@ import com.atlassian.prosemirror.testbuilder.NodeBuildCompanion import com.atlassian.prosemirror.testbuilder.NodeBuilder import com.atlassian.prosemirror.testbuilder.NodeSpecImpl import com.atlassian.prosemirror.testbuilder.PMNodeBuilder.Companion.doc -import kotlin.test.Ignore import kotlin.test.Test import com.atlassian.prosemirror.testbuilder.schema as testSchema - -val schemaWithComment = Schema( - SchemaSpec( - nodes = testSchema.spec.nodes + mapOf( - "doc" to (testSchema.spec.nodes["doc"] as NodeSpecImpl).copy(marks = "comment") - ), - marks = testSchema.spec.marks + mapOf( - "comment" to MarkSpecImpl( - parseDOM = listOf(ParseRuleImpl(tag = "div.comment")), - toDOM = { _, _ -> - DOMOutputSpec.ArrayDOMOutputSpec(listOf("div", mapOf("class" to "comment"), 0)) - } - ) - ) - ) -) - -fun NodeBuilder.comment(func: NodeBuilder.() -> Unit) = mark("comment", func) +import com.atlassian.prosemirror.testbuilder.AttributeSpecImpl +import com.atlassian.prosemirror.testbuilder.PMNodeBuilder class CommentNodeBuilder( pos: Int = 0, marks: List = emptyList(), - override val schema: Schema = schemaWithComment + override val schema: Schema = testSchema ) : NodeBuilder(pos, marks, schema) { - override val checked: Boolean get() = false override fun create(pos: Int, marks: List, schema: Schema): NodeBuilder { return CommentNodeBuilder(pos, marks, schema) } +} - companion object : NodeBuildCompanion(schemaWithComment) { - override val checked: Boolean - get() = false +class CustomNodeBuildCompanion(schema: Schema): NodeBuildCompanion(schema) { + override val checked: Boolean + get() = false - override fun create(): CommentNodeBuilder { - return CommentNodeBuilder() - } + override fun create(): CommentNodeBuilder { + return CommentNodeBuilder(schema = schema) } } @@ -87,11 +69,23 @@ class DomTest { ) } - @Ignore // "This test is failing - fix code" @Test fun `can represent links`() { + // custom link mark that has a title=null attribute + fun NodeBuilder.aWithTitle(href: String = "foo", func: NodeBuilder.() -> Unit) = + mark("link", func, attrs = mapOf("href" to href, "title" to null)) + test( - doc { p { +"a " + a(href = "foo") { +"big " + a(href = "bar") { +"nested" } + " link" } } }, + // TypeScript code: doc(p("a ", a({href: "foo"}, "big ", a({href: "bar"}, "nested"), " link"))) + // converts to the code below because each node cannot have more than 1 Link mark + doc { + p { + +"a " + + aWithTitle(href = "foo") { +"big "} + + aWithTitle(href = "bar") { +"nested" } + + aWithTitle(href = "foo") { +" link" } + } + }, "

a big nested link

" ) } @@ -184,7 +178,26 @@ class DomTest { @Test fun `can parse marks on block nodes`() { - val doc = CommentNodeBuilder.doc { + val schemaWithComment = Schema( + SchemaSpec( + nodes = testSchema.spec.nodes + mapOf( + "doc" to (testSchema.spec.nodes["doc"] as NodeSpecImpl).copy(marks = "comment") + ), + marks = testSchema.spec.marks + mapOf( + "comment" to MarkSpecImpl( + parseDOM = listOf(ParseRuleImpl(tag = "div.comment")), + toDOM = { _, _ -> + DOMOutputSpec.ArrayDOMOutputSpec(listOf("div", mapOf("class" to "comment"), 0)) + } + ) + ) + ) + ) + + fun NodeBuilder.comment(func: NodeBuilder.() -> Unit) = + mark("comment", func) + + val doc = CustomNodeBuildCompanion(schemaWithComment).doc { p { +"one" } + this.comment { p { +"two" } + p { strong { +"three" } } } + p { +"four" } } test( @@ -193,46 +206,88 @@ class DomTest { ) } - // TODO convert tests below -// it("parses unique, non-exclusive, same-typed marks", () => { -// let commentSchema = new Schema({ -// nodes: schema.spec.nodes, -// marks: schema.spec.marks.update("comment", { -// attrs: { id: { default: null }}, -// parseDOM: [{ -// tag: "span.comment", -// getAttrs(dom) { return { id: parseInt((dom as HTMLElement).getAttribute('data-id')!, 10) } } -// }], -// excludes: '', -// toDOM(mark: Mark) { return ["span", {class: "comment", "data-id": mark.attrs.id }, 0] } -// }) -// }) -// let b = builders(commentSchema) -// test(b.schema.nodes.doc.createAndFill(undefined, [ -// b.schema.nodes.paragraph.createAndFill(undefined, [ -// b.schema.text('double comment', [ -// b.schema.marks.comment.create({ id: 1 }), -// b.schema.marks.comment.create({ id: 2 }) -// ])! -// ])! -// ])!, -// "

double comment

")() -// }) -// -// it("serializes non-spanning marks correctly", () => { -// let markSchema = new Schema({ -// nodes: schema.spec.nodes, -// marks: schema.spec.marks.update("test", { -// parseDOM: [{tag: "test"}], -// toDOM() { return ["test", 0] }, -// spanning: false -// }) -// }) -// let b = builders(markSchema) as any -// test(b.doc(b.paragraph(b.test("a", b.image({src: "x"}), "b"))), -// "

ab

")() -// }) -// + @Test + fun `parses unique non-exclusive same-typed marks`() { + val commentSchema = Schema( + SchemaSpec( + nodes = testSchema.spec.nodes, + marks = testSchema.spec.marks + mapOf( + "comment" to MarkSpecImpl( + attrs = mapOf("id" to AttributeSpecImpl(default = null)), + parseDOM = listOf( + ParseRuleImpl( + tag = "span.comment", + getNodeAttrs = { dom -> + val id = dom.attribute("data-id")?.int() ?: 10 + ParseRuleMatch(mapOf("id" to id)) + }, + ) + ), + excludes = "", + toDOM = { mark, _ -> + DOMOutputSpec.ArrayDOMOutputSpec( + listOf( + "span", + mapOf("class" to "comment", "data-id" to mark.attrs["id"]), + 0 + ) + ) + } + ) + ) + ) + ) + val doc = commentSchema.nodes["doc"]!!.createAndFill( + attrs = null, + content = listOf( + commentSchema.nodes["paragraph"]!!.createAndFill( + attrs = null, + content = listOf( + commentSchema.text( + text = "double comment", + marks = listOf( + commentSchema.marks["comment"]!!.create(mapOf("id" to 1)), + commentSchema.marks["comment"]!!.create(mapOf("id" to 2)) + ) + ) + ), + marks = null + )!! + ), + marks = null + )!! + test( + doc, + "

double comment

" + ) + } + + @Test + fun `serializes non-spanning marks correctly`() { + val markSchema = Schema( + SchemaSpec( + nodes = testSchema.spec.nodes, + marks = testSchema.spec.marks + mapOf( + "test" to MarkSpecImpl( + parseDOM = listOf(ParseRuleImpl(tag = "test")), + toDOM = { _, _ -> DOMOutputSpec.ArrayDOMOutputSpec(listOf("test", 0)) }, + spanning = false + ) + ) + ) + ) + val b = CustomNodeBuildCompanion(markSchema) + + fun NodeBuilder.test(func: NodeBuilder.() -> Unit) = + mark("test", func) + + test( + b.doc { p { test { +"a" + img(mapOf("src" to "x")) {} + "b" } } }, + "

ab

" + ) + } + + // Skipping the following tests because we don't support them yet // it("serializes an element and an attribute with XML namespace", () => { // let xmlnsSchema = new Schema({ // nodes: { @@ -430,12 +485,16 @@ class DomTest { doc { p { em { +"Hello" } + "!" } } ) } + + @Test + fun `interprets font-weight bold as strong`() { + recover( + "

Hello

", + doc { p { strong { +"Hello" } } } + ) + } } -// it("interprets font-weight: bold as strong", -// recover("

Hello

", -// doc(p(strong("Hello"))))) -// // it("allows clearing of pending marks", // recover("

One

Two

foobarbazquuxxyz") // }) // }) + +fun com.fleeksoft.ksoup.nodes.Attribute?.int(default: Int? = null): Int? { + return try { + this?.value?.takeIf { it.isNotBlank() }?.toInt() + } catch (ex: NumberFormatException) { + default + } +} diff --git a/test-builder/config/ktlint/baseline.xml b/test-builder/config/ktlint/baseline.xml index 1ed1fab..c64df6f 100644 --- a/test-builder/config/ktlint/baseline.xml +++ b/test-builder/config/ktlint/baseline.xml @@ -80,43 +80,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-builder/src/commonMain/kotlin/com/atlassian/prosemirror/testbuilder/SchemaBasic.kt b/test-builder/src/commonMain/kotlin/com/atlassian/prosemirror/testbuilder/SchemaBasic.kt index 37cd0dc..f284772 100644 --- a/test-builder/src/commonMain/kotlin/com/atlassian/prosemirror/testbuilder/SchemaBasic.kt +++ b/test-builder/src/commonMain/kotlin/com/atlassian/prosemirror/testbuilder/SchemaBasic.kt @@ -13,6 +13,7 @@ import com.atlassian.prosemirror.model.ParseRuleMatch import com.atlassian.prosemirror.model.PreserveWhitespace import com.atlassian.prosemirror.model.Schema import com.atlassian.prosemirror.model.SchemaSpec +import com.fleeksoft.ksoup.nodes.Element val pDOM: DOMOutputSpec = DOMOutputSpec.ArrayDOMOutputSpec(listOf("p", 0)) val blockquoteDOM: DOMOutputSpec = DOMOutputSpec.ArrayDOMOutputSpec(listOf("blockquote", 0)) @@ -200,11 +201,12 @@ val marks = mapOf( // pasted content will be inexplicably wrapped in `` // tags with a font-weight normal. ParseRuleImpl(tag = "b", getNodeAttrs = { node -> - // TODO find a way to parse css - // node.style.fontWeight != "normal" && null - ParseRuleMatch(null) + ParseRuleMatch(null, node.styles()["font-weight"] != "normal") }), -// {style: "font-weight", getAttrs: (value: string) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null} + ParseRuleImpl(style = "font-weight", getStyleAttrs = { value -> + val regex = "^bold(er)?|[5-9]\\d{2,}".toRegex() + ParseRuleMatch(null, regex.matches(value)) + }) ), toDOM = { _, _ -> strongDOM } ), @@ -232,3 +234,11 @@ val marks = mapOf( // To reuse elements from this schema, extend or read from its `spec.nodes` and `spec.marks` // [properties](#model.Schema.spec). val schemaBasic = Schema(SchemaSpec(nodes = nodes, marks = marks)) + +fun Element.styles(): Map { + val style = attribute("style")?.value ?: return emptyMap() + return style.split(";").associate { + val (key, value) = it.split(":") + key.trim() to value.trim() + } +}