Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions app/assets/javascript/lexxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7688,6 +7688,11 @@ class CommandDispatcher {
const selection = Lr$1();
if (!yr$1(selection)) return

if (as(selection.anchor.getNode())) {
selection.insertNodes([ St$3("h2") ]);
return
}

const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow();
let nextTag = "h2";
if (It$3(topLevelElement)) {
Expand Down Expand Up @@ -9023,6 +9028,11 @@ class Contents {
const selection = Lr$1();
if (!yr$1(selection)) return

if (as(selection.anchor.getNode())) {
selection.insertNodes([ newNodeFn() ]);
return
}

const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow();

// Check if format is already applied
Expand Down Expand Up @@ -9296,9 +9306,19 @@ class Contents {
#unwrap(node) {
const children = node.getChildren();

children.forEach((child) => {
node.insertBefore(child);
});
if (children.length == 0) {
node.insertBefore(Li());
} else {
children.forEach((child) => {
if (lr(child) && child.getTextContent().trim() !== "") {
const newParagraph = Li();
newParagraph.append(child);
node.insertBefore(newParagraph);
} else if (!jn(child)) {
node.insertBefore(child);
}
});
}

node.remove();
}
Expand All @@ -9312,6 +9332,12 @@ class Contents {
if (selectedNodes.length === 0) {
return
}

if (as(selectedNodes[0])) {
selection.insertNodes([ newNodeFn() ]);
return
}

const topLevelElements = new Set();
selectedNodes.forEach((node) => {
const topLevel = node.getTopLevelElementOrThrow();
Expand Down Expand Up @@ -9382,6 +9408,12 @@ class Contents {

#wrapCurrentLine(selection, newNodeFn) {
const anchorNode = selection.anchor.getNode();

if (as(anchorNode)) {
selection.insertNodes([ newNodeFn() ]);
return
}

const topLevelElement = anchorNode.getTopLevelElementOrThrow();

if (topLevelElement.getTextContent()) {
Expand Down Expand Up @@ -10143,7 +10175,7 @@ class LexicalEditorElement extends HTMLElement {
const root = No$1();
root.clear();
if (html !== "") root.append(...this.#parseHtmlIntoLexicalNodes(html));
root.select();
root.selectEnd();

this.#toggleEmptyStatus();

Expand All @@ -10167,6 +10199,7 @@ class LexicalEditorElement extends HTMLElement {
#parseHtmlIntoLexicalNodes(html) {
if (!html) html = "<p></p>";
const nodes = m$1(this.editor, parseHtml(`<div>${html}</div>`));

// Custom decorator block elements such action-text-attachments get wrapped into <p> automatically by Lexical.
// We flatten those.
return nodes.map(node => {
Expand Down
4 changes: 4 additions & 0 deletions app/assets/stylesheets/lexxy-content.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
font-style: italic;
margin: var(--lexxy-content-margin) 0;
padding: 0.5lh 2ch;

p:last-child {
margin-block-end: 0;
}
}

p:empty {
Expand Down
6 changes: 6 additions & 0 deletions src/editor/command_dispatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
$createTextNode,
$getSelection,
$isRangeSelection,
$isRootOrShadowRoot,
COMMAND_PRIORITY_LOW,
COMMAND_PRIORITY_NORMAL,
FORMAT_TEXT_COMMAND,
Expand Down Expand Up @@ -173,6 +174,11 @@ export class CommandDispatcher {
const selection = $getSelection()
if (!$isRangeSelection(selection)) return

if ($isRootOrShadowRoot(selection.anchor.getNode())) {
selection.insertNodes([ $createHeadingNode("h2") ])
return
}

const topLevelElement = selection.anchor.getNode().getTopLevelElementOrThrow()
let nextTag = "h2"
if ($isHeadingNode(topLevelElement)) {
Expand Down
18 changes: 15 additions & 3 deletions src/editor/contents.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,19 @@ export default class Contents {
#unwrap(node) {
const children = node.getChildren()

children.forEach((child) => {
node.insertBefore(child)
})
if (children.length == 0) {
node.insertBefore($createParagraphNode())
} else {
children.forEach((child) => {
if ($isTextNode(child) && child.getTextContent().trim() !== "") {
const newParagraph = $createParagraphNode()
newParagraph.append(child)
node.insertBefore(newParagraph)
} else if (!$isLineBreakNode(child)) {
node.insertBefore(child)
}
})
}

node.remove()
}
Expand All @@ -367,6 +377,7 @@ export default class Contents {
if (selectedNodes.length === 0) {
return
}

const topLevelElements = new Set()
selectedNodes.forEach((node) => {
const topLevel = node.getTopLevelElementOrThrow()
Expand Down Expand Up @@ -437,6 +448,7 @@ export default class Contents {

#wrapCurrentLine(selection, newNodeFn) {
const anchorNode = selection.anchor.getNode()

const topLevelElement = anchorNode.getTopLevelElementOrThrow()

if (topLevelElement.getTextContent()) {
Expand Down
39 changes: 22 additions & 17 deletions src/elements/editor.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { $addUpdateTag, $getNodeByKey, $getRoot, BLUR_COMMAND, CLEAR_HISTORY_COMMAND, COMMAND_PRIORITY_NORMAL, DecoratorNode, FOCUS_COMMAND, KEY_ENTER_COMMAND, SKIP_DOM_SELECTION_TAG, createEditor } from "lexical"
import { $addUpdateTag, $createParagraphNode, $getNodeByKey, $getRoot, BLUR_COMMAND, CLEAR_HISTORY_COMMAND, COMMAND_PRIORITY_NORMAL, DecoratorNode, FOCUS_COMMAND, KEY_ENTER_COMMAND, SKIP_DOM_SELECTION_TAG, createEditor } from "lexical"
import { ListItemNode, ListNode, registerList } from "@lexical/list"
import { AutoLinkNode, LinkNode } from "@lexical/link"
import { registerPlainText } from "@lexical/plain-text"
Expand Down Expand Up @@ -79,6 +79,20 @@ export default class LexicalEditorElement extends HTMLElement {
this.editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined)
}

focus() {
this.editor.focus()
}

toString() {
if (!this.cachedStringValue) {
this.editor?.getEditorState().read(() => {
this.cachedStringValue = $getRoot().getTextContent()
})
}

return this.cachedStringValue
}

get form() {
return this.internals.form
}
Expand Down Expand Up @@ -130,10 +144,6 @@ export default class LexicalEditorElement extends HTMLElement {
return parseInt(this.editorContentElement?.getAttribute("tabindex") ?? "0")
}

focus() {
this.editor.focus()
}

get value() {
if (!this.cachedValue) {
this.editor?.getEditorState().read(() => {
Expand All @@ -149,8 +159,8 @@ export default class LexicalEditorElement extends HTMLElement {
$addUpdateTag(SKIP_DOM_SELECTION_TAG)
const root = $getRoot()
root.clear()
if (html !== "") root.append(...this.#parseHtmlIntoLexicalNodes(html))
root.select()
root.append(...this.#parseHtmlIntoLexicalNodes(html))
root.selectEnd()

this.#toggleEmptyStatus()

Expand All @@ -161,19 +171,14 @@ export default class LexicalEditorElement extends HTMLElement {
})
}

toString() {
if (!this.cachedStringValue) {
this.editor?.getEditorState().read(() => {
this.cachedStringValue = $getRoot().getTextContent()
})
}

return this.cachedStringValue
}

#parseHtmlIntoLexicalNodes(html) {
if (!html) html = "<p></p>"
const nodes = $generateNodesFromDOM(this.editor, parseHtml(`<div>${html}</div>`))

if (nodes.length === 0) {
return [ $createParagraphNode() ]
}

// Custom decorator block elements such action-text-attachments get wrapped into <p> automatically by Lexical.
// We flatten those.
return nodes.map(node => {
Expand Down
4 changes: 2 additions & 2 deletions src/elements/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,11 @@ export default class LexicalToolbarElement extends HTMLElement {

<div class="lexxy-editor__toolbar-spacer" role="separator"></div>

<button class="lexxy-editor__toolbar-button" type="button" name="undo" data-command="undo" title="Undo" data-hotkey="cmd+z ctrl+z">
<button class="lexxy-editor__toolbar-button" type="button" name="undo" data-command="undo" title="Undo">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5.64648 8.26531C7.93911 6.56386 10.7827 5.77629 13.624 6.05535C16.4655 6.33452 19.1018 7.66079 21.0195 9.77605C22.5839 11.5016 23.5799 13.6516 23.8936 15.9352C24.0115 16.7939 23.2974 17.4997 22.4307 17.4997C21.5641 17.4997 20.8766 16.7915 20.7148 15.9401C20.4295 14.4379 19.7348 13.0321 18.6943 11.8844C17.3 10.3464 15.3835 9.38139 13.3174 9.17839C11.2514 8.97546 9.18359 9.54856 7.5166 10.7858C6.38259 11.6275 5.48981 12.7361 4.90723 13.9997H8.5C9.3283 13.9997 9.99979 14.6714 10 15.4997C10 16.3281 9.32843 16.9997 8.5 16.9997H1.5C0.671573 16.9997 0 16.3281 0 15.4997V8.49968C0.000213656 7.67144 0.671705 6.99968 1.5 6.99968C2.3283 6.99968 2.99979 7.67144 3 8.49968V11.0212C3.7166 9.9704 4.60793 9.03613 5.64648 8.26531Z"/></svg>
</button>

<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo" data-hotkey="cmd+shift+z ctrl+shift+z ctrl+y">
<button class="lexxy-editor__toolbar-button" type="button" name="redo" data-command="redo" title="Redo">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M18.2599 8.26531C15.9672 6.56386 13.1237 5.77629 10.2823 6.05535C7.4408 6.33452 4.80455 7.66079 2.88681 9.77605C1.32245 11.5016 0.326407 13.6516 0.0127834 15.9352C-0.105117 16.7939 0.608975 17.4997 1.47567 17.4997C2.34228 17.4997 3.02969 16.7915 3.19149 15.9401C3.47682 14.4379 4.17156 13.0321 5.212 11.8844C6.60637 10.3464 8.52287 9.38139 10.589 9.17839C12.655 8.97546 14.7227 9.54856 16.3897 10.7858C17.5237 11.6275 18.4165 12.7361 18.9991 13.9997H15.4063C14.578 13.9997 13.9066 14.6714 13.9063 15.4997C13.9063 16.3281 14.5779 16.9997 15.4063 16.9997H22.4063C23.2348 16.9997 23.9063 16.3281 23.9063 15.4997V8.49968C23.9061 7.67144 23.2346 6.99968 22.4063 6.99968C21.578 6.99968 20.9066 7.67144 20.9063 8.49968V11.0212C20.1897 9.9704 19.2984 9.03613 18.2599 8.26531Z"/></svg>
</button>

Expand Down
4 changes: 2 additions & 2 deletions test/system/editor_to_string_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ class EditorValueMethodsTest < ApplicationSystemTestCase
end
sleep 0.1

assert_editor_plain_text "\n\n[example.png]\n\n"
assert_editor_plain_text "[example.png]\n\n"

find("figcaption textarea").click.send_keys("Example Image")
find_editor.click
sleep 0.1

assert_editor_plain_text "\n\n[Example Image]\n\n"
assert_editor_plain_text "[Example Image]\n\n"
end

test "toString returns content for custom_action_text_attachment (mention)" do
Expand Down
2 changes: 1 addition & 1 deletion test/system/form_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class ActionTextLoadTest < ApplicationSystemTestCase
click_on "Edit this post"


assert_equal_html "<p><br></p><p>That</p>", find_editor.value
assert_equal_html "<p>That</p>", find_editor.value
end

test "resets editor to initial state when form is reset" do
Expand Down
3 changes: 3 additions & 0 deletions test/system/page_refreshes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ class PageRefreshesTest < ApplicationSystemTestCase

wait_for_editor

# Prompt is only opened if trigger follows a space or newline
find_editor.send "\n"

find_editor.send "1"
click_on_prompt "Peter Johnson"
assert_mention_attachment people(:peter)
Expand Down
1 change: 1 addition & 0 deletions test/test_helpers/editor_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def send_key(key, ctrl: false)
});
this.dispatchEvent(event);
JS
sleep 0.1
end

def send_tab(shift: false)
Expand Down