diff --git a/editor-document.js b/editor-document.js index e608ffd..ab68d8f 100644 --- a/editor-document.js +++ b/editor-document.js @@ -88,8 +88,8 @@ class EditorDocument { formatToHtml(baseElement=document.createElement("div")) { const root = baseElement - const cssFontSize = this.fontSize / this.scale - // Wrapping poems should generally be discouraged, as formatting changes may affect how it is paced + const cssFontSize = this.fontSize / this.scale + // Wrapping poems should generally be discouraged, as formatting changes may affect how it is paced // and read, however we do not want to cut off content either so such compromise must be made... root.style.overflow = "auto" root.style.fontFamily = "Arial, Helvetica, sans-serif" @@ -167,6 +167,8 @@ class EditorDocument { context.clearRect(0, 0, canvas.width, canvas.height) let line = 1 + const positionContainer = this.getPositionContainer() + const containerNode = positionContainer.node function renderFragment(data, _this) { switch (data.type) { case "fragment": { @@ -217,6 +219,18 @@ class EditorDocument { context.font = font.join(" ") context.fillStyle = colour context.fillText(data.content, 0, line * _this.fontSize) + + if (data === containerNode) { + // Draw cursor (x, y, width, height) + const thisLeft = context.measureText(data.content.slice(0, positionContainer.relativePosition)) + // TODO: Maintain measure width for currently line + context.fillStyle = _this.colourHex + context.fillRect( + thisLeft.width, + (line - 1) * _this.fontSize + 2, + 1.5 * _this.scale, + _this.fontSize + 4) + } break } case "newline": { @@ -578,94 +592,98 @@ class EditorDocument { + '\uE002' + this.data.slice(this.position) } - } - - getText() { - let text = "" - function visitNode(data) { - switch (data.type) { - case "fragment": - for (let i = 0; i < data.children.length; i++) { - const child = data.children[i] - visitNode(child) - } - break - case "text": - text += data.content - break - } - } - visitNode(this.data) - return text - } - - // Finds the node which contains the cursor currently - getPositionContainer() { - let currentlyAt = 0 - function visitNode(data, _this) { - switch (data.type) { - case "fragment": - for (let i = 0; i < data.children.length; i++) { - const child = data.children[i] - const found = visitNode(child, _this) - if (found) { - return found - } - } - return data - case "text": - currentlyAt += data.content.length - if (currentlyAt >= _this.position) { - return data - } - break - } - - return null - } - return visitNode(this.data, this) - } - - // Creates a new text node at cursor position containing given text - addText(value) { - if (this.hasSelection()) { - this.deleteSelection() - } - - const container = this.getPositionContainer() - if (container === null) { - return new Error("Couldn't add text, cursor position outside of document nodes range") - } - // Empty fragment, create new text node - if (container.type === "fragment") { - const textContainer = new Text(value) - container.children.push(textContainer) - } - else { - // TODO: Split current text in half? Put new text node inbetween? - } - this.position += value.length - } - + } + + getText() { + let text = "" + function visitNode(data) { + switch (data.type) { + case "fragment": + for (let i = 0; i < data.children.length; i++) { + const child = data.children[i] + visitNode(child) + } + break + case "text": + text += data.content + break + } + } + visitNode(this.data) + return text + } + + // Finds the node which contains the cursor currently + getPositionContainer() { + let nodeStart = 0 + let currentlyAt = 0 + function visitNode(data, _this) { + switch (data.type) { + case "fragment": + for (let i = 0; i < data.children.length; i++) { + const child = data.children[i] + const found = visitNode(child, _this) + if (found) { + return found + } + } + return data + case "text": + nodeStart = currentlyAt + currentlyAt += data.content.length + if (currentlyAt >= _this.position) { + return data + } + break + } + + return null + } + return { + node: visitNode(this.data, this), + relativePosition: this.position - nodeStart + } + } + + getParentNode(targetNode) { + function visitNode(data, _this) { + if (data.type === "fragment") { + for (const child of data.children) { + if (child === targetNode) { + return child + } + return visitNode(child, _this) + } + } + + return null + } + + return visitNode(this.data, _this) + } + // Attempts to create or append to text node at cursor position containing given text - appendText(value) { + insertText(value) { if (this.hasSelection()) { this.deleteSelection() } - - const container = this.getPositionContainer() - if (container === null) { - return new Error("Couldn't add text, cursor position outside of document nodes range") - } - // Empty fragment, create new text node - if (container.type === "fragment") { - const textContainer = new Text(value) - container.children.push(textContainer) - } - else { - container.content += value - } - this.position += value.length + + const container = this.getPositionContainer() + if (container.node === null) { + return new Error("Couldn't add text, cursor position outside of document nodes range") + } + // Empty fragment, create new text node + if (container.node.type === "fragment") { + const textContainer = new Text(value) + container.node.children.push(textContainer) + } + else if (container.node.type === "text") { + container.node.content += value + } + else { + throw new Error("Not implemented") + } + this.position += value.length } addNewLine() { @@ -688,15 +706,16 @@ class EditorDocument { if (this.hasSelection()) { this.deleteSelection() } - else { - // TODO: This will be painful to handle across node boundaries - if (count > 0) { - this.data = this.data.slice(0, this.position - count) + this.data.slice(this.position) - this.position = Math.max(0, this.position - count) - } - else { - this.data = this.data.slice(0, this.position) + this.data.slice(this.position - count) + else { + // TODO: This will be painful to handle across node boundaries + const container = this.getPositionContainer() + const node = container.node + if (node === null) { + return new Error("Couldn't delete text, cursor position outside of document nodes range") } + + node.content = node.content.slice(0, this.position - count) + node.content.slice(this.position) + this.position = Math.max(0, this.position - count) } } diff --git a/poem-editor.css b/poem-editor.css index 5c55d02..f247b33 100644 --- a/poem-editor.css +++ b/poem-editor.css @@ -98,7 +98,7 @@ max-width: 100%; overflow-x: visible; } -#formattingToolbar > div { +.toolbar-item { height: 32px; width: 32px; min-width: 32px; @@ -107,15 +107,18 @@ border-radius: 4px; user-select: none; padding: 1px; + border: none; transition: .05 transform; } -#formattingToolbar > div:active { +.toolbar-item:active { transform: scale(0.98); } -#formattingToolbar > div > div { +.toolbar-item > div { background-color: var(--button-transparent); border-radius: 4px; pointer-events: none; + font-family: Arial, Helvetica, sans-serif; + font-size: 16px; } .separator { background: transparent; @@ -127,6 +130,13 @@ margin-right: 8px; flex-grow: 1; } +#formattingColourRect { + background-color: black; + width: calc(100% - 2px); + height: calc(100% - 2px); + margin: 1px; + border-radius: 4px; +} #poem-tags button { border-radius: 4px; margin: 2px; diff --git a/poem-editor.html b/poem-editor.html index 678a839..5569501 100644 --- a/poem-editor.html +++ b/poem-editor.html @@ -14,7 +14,7 @@ - +
-
+
-
+ +
-
+ +
-
+ +
-
+ +
-
+ +
-
+ +
-
+ +
-
- -
-
-
+ + +
-
-
+ +
+
-
+ +
-
-
Rhyme finder ➕
-
-
AI coauthor ➕
-
+ -
+