From ab092ae3e0adf1c4569aadea1b010972dab51c66 Mon Sep 17 00:00:00 2001 From: michi <michael@adornis.de> Date: Thu, 13 Feb 2025 22:02:47 +0000 Subject: [PATCH 1/2] feat: integrate flat list package + custom stylings per config files --- .../client/marks/text-align.ts | 8 +- .../nodes/grid/grid-cell/node-grid-cell.ts | 2 +- .../client/nodes/grid/node-grid.ts | 19 +-- .../client/nodes/hard-break.ts | 31 +++++ .../client/nodes/list/keymapPlugin.ts | 7 +- .../client/nodes/list/list.ts | 119 +++++------------- .../client/nodes/paragraph.ts | 9 +- .../client/prosemirror-editor.ts | 81 +++++++----- lab/html-based-buildify/client/starterPack.ts | 6 +- lab/html-based-buildify/client/types.ts | 3 +- .../client/x-prosemirror-toolbar.ts | 7 +- lab/html-based-buildify/schema/nodes/list.ts | 42 ------- 12 files changed, 150 insertions(+), 184 deletions(-) diff --git a/lab/html-based-buildify/client/marks/text-align.ts b/lab/html-based-buildify/client/marks/text-align.ts index a5541aac1f..580e48fc8d 100644 --- a/lab/html-based-buildify/client/marks/text-align.ts +++ b/lab/html-based-buildify/client/marks/text-align.ts @@ -1,4 +1,4 @@ -import type { EditorState, Transaction } from 'prosemirror-state'; +import { TextSelection, type EditorState, type Transaction } from 'prosemirror-state'; import type { EditorView } from 'prosemirror-view'; import { MenuItemGroup, type IMenuItem } from '../types.js'; import { isInText, updateNodeAttrs } from '../util.js'; @@ -21,7 +21,11 @@ export function toggleTextAlign(alignment: Alignment) { transaction = updateNodeAttrs(state, transaction, () => pos, { ...node.attrs, textAlign: alignment }); } }); - if (dispatch) dispatch(transaction); + if (dispatch) { + const newSelection = TextSelection.create(transaction.doc, selection.from, selection.to); + transaction = transaction.setSelection(newSelection); + dispatch(transaction); + } return applicable; }; } diff --git a/lab/html-based-buildify/client/nodes/grid/grid-cell/node-grid-cell.ts b/lab/html-based-buildify/client/nodes/grid/grid-cell/node-grid-cell.ts index 23bdf097f6..bc3a8310b2 100644 --- a/lab/html-based-buildify/client/nodes/grid/grid-cell/node-grid-cell.ts +++ b/lab/html-based-buildify/client/nodes/grid/grid-cell/node-grid-cell.ts @@ -103,7 +103,7 @@ export class NodeGridCell extends ChemistryLitElement { ':host': { border: this.isEditMode() ? '1px solid grey' : '', position: 'relative', - overflowWrap: 'break-word', + // overflowWrap: 'break-word', }, ':host(.grid-item)': { position: 'relative', diff --git a/lab/html-based-buildify/client/nodes/grid/node-grid.ts b/lab/html-based-buildify/client/nodes/grid/node-grid.ts index 836af2659c..3b52abafbd 100644 --- a/lab/html-based-buildify/client/nodes/grid/node-grid.ts +++ b/lab/html-based-buildify/client/nodes/grid/node-grid.ts @@ -18,6 +18,12 @@ export class NodeGrid extends BuildifyLitElement<HTMLBaseContainerGrid> { this._calculateOverflowVisuals(); } + override updated(changedProperties: PropertyValues): void { + super.updated(changedProperties); + + this._calculateOverflowVisuals(); + } + private _calculateOverflowVisuals() { requestAnimationFrame(() => { const content = this._data.value; @@ -26,21 +32,19 @@ export class NodeGrid extends BuildifyLitElement<HTMLBaseContainerGrid> { const container = this._gridRef.value; if (!container) return; - console.log('1', container); - const width = container.clientWidth; // Sichtbare Breite const scrollLeft = container.scrollLeft; // Aktuelle Scroll-Position const scrollWidth = container.scrollWidth; // Gesamte Breite des Inhalts const hasOverflow = width !== scrollWidth; const atStart = scrollLeft === 0; - const atEnd = scrollLeft + width >= scrollWidth; - - console.log({ width, scrollLeft, scrollWidth }); + const atEnd = scrollLeft + width >= scrollWidth - 1; // 🔥 Toleranz von 1px if (!hasOverflow) return (this._scrollInfo = undefined); - this._scrollInfo = { showStart: !atStart, showEnd: !atEnd }; + if (this._scrollInfo?.showStart !== !atStart || this._scrollInfo.showEnd !== !atEnd) { + this._scrollInfo = { showStart: !atStart, showEnd: !atEnd }; + } }); } @@ -65,7 +69,7 @@ export class NodeGrid extends BuildifyLitElement<HTMLBaseContainerGrid> { gridRowGap: content?.gridRowGap ?? '', padding: content?.padding ?? '', background: content?.backgroundColor ?? '', - overflowY: content?.allowHorizontalOverflow ? 'auto' : '', + overflowX: content?.allowHorizontalOverflow ? 'auto' : '', height: '100%', })} > @@ -145,7 +149,6 @@ export class NodeGrid extends BuildifyLitElement<HTMLBaseContainerGrid> { }, '::slotted(*)': { boxSizing: 'border-box', - overflowY: this._data.value?.allowHorizontalOverflow ? '' : 'hidden !important', }, }, ] as Styles[]; diff --git a/lab/html-based-buildify/client/nodes/hard-break.ts b/lab/html-based-buildify/client/nodes/hard-break.ts index 4dde70cd52..5caf77fc75 100644 --- a/lab/html-based-buildify/client/nodes/hard-break.ts +++ b/lab/html-based-buildify/client/nodes/hard-break.ts @@ -1,9 +1,40 @@ +import { keymap } from 'prosemirror-keymap'; +import type { EditorState, Transaction } from 'prosemirror-state'; import { HARD_BREAK_KEY, HARD_BREAK_SCHEMA } from '../../schema/nodes/hard-break.js'; import type { INodeConfig } from '../types.js'; +function insertHardBreak(state: EditorState, dispatch?: (tr: Transaction) => void) { + if (dispatch) { + const { schema, selection } = state; + const { $from } = selection; + let tr = state.tr.insert($from.pos, schema.nodes[HARD_BREAK_KEY]!.create()); + dispatch(tr); + } + return true; +} + export const hardBreak: INodeConfig = { node: { name: HARD_BREAK_KEY, schema: HARD_BREAK_SCHEMA, }, + menuItems: [ + { + icon: 'insert_page_break', + shouldVisualize(editorView) { + return insertHardBreak(editorView.state); + }, + label: 'Zeilenumbruch', + run(editorView) { + return insertHardBreak(editorView.state, editorView.dispatch); + }, + }, + ], + plugins(schema) { + return [ + keymap({ + 'Shift-Enter': insertHardBreak, + }), + ]; + }, }; diff --git a/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts b/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts index 51eeae140d..377cdbdcb9 100644 --- a/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts +++ b/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts @@ -1,14 +1,13 @@ import { keymap } from 'prosemirror-keymap'; import { Schema } from 'prosemirror-model'; -import { liftListItem, sinkListItem, splitListItem } from 'prosemirror-schema-list'; import { LIST_ITEM_KEY } from '../../../schema/nodes/list.js'; export const listKeymapPlugin = (schema: Schema) => { const listItem = schema.nodes[LIST_ITEM_KEY]; if (!listItem) throw new Error('list item not found in schema'); return keymap({ - Enter: splitListItem(listItem), - Tab: sinkListItem(listItem), - 'Shift-Tab': liftListItem(listItem), + // Enter: splitListItem(listItem), + // Tab: sinkListItem(listItem), + // 'Shift-Tab': liftListItem(listItem), }); }; diff --git a/lab/html-based-buildify/client/nodes/list/list.ts b/lab/html-based-buildify/client/nodes/list/list.ts index 7fb870d176..aa77b0d415 100644 --- a/lab/html-based-buildify/client/nodes/list/list.ts +++ b/lab/html-based-buildify/client/nodes/list/list.ts @@ -1,98 +1,37 @@ -import type { Schema } from 'prosemirror-model'; -import { wrapInList } from 'prosemirror-schema-list'; -import { Plugin } from 'prosemirror-state'; -import type { EditorView } from 'prosemirror-view'; -import { - LIST_ITEM_KEY, - LIST_ITEM_SCHEMA, - LIST_KEY, - LIST_SCHEMA, - ORDERED_LIST_KEY, - ORDERED_LIST_SCHEMA, -} from '../../../schema/nodes/list.js'; +// @ts-expect-error default flat list stylings +import ListStyles from 'text:prosemirror-flat-list/dist/style.css'; + +// others +import { css, unsafeCSS } from 'lit'; +import { createListPlugins, createListSpec, listInputRules, listKeymap } from 'prosemirror-flat-list'; +import { inputRules } from 'prosemirror-inputrules'; +import { keymap } from 'prosemirror-keymap'; import type { INodeConfig } from '../../types.js'; -import { handleKeyDown } from './handleKeyDown.js'; -import { listKeymapPlugin } from './keymapPlugin.js'; -export const listItem: INodeConfig = { +export const listConfig: INodeConfig = { node: { - name: LIST_ITEM_KEY, - schema: LIST_ITEM_SCHEMA, + name: 'list', + schema: createListSpec(), }, -}; - -export const bulletList: INodeConfig = { - node: { - name: LIST_KEY, - schema: LIST_SCHEMA, - }, - plugins: (schema: Schema) => [ - new Plugin({ - props: { - handleTextInput(view: EditorView, from, to, text) { - const { state, dispatch } = view; - const { $from } = state.selection; - const paragraphNode = $from.parent; - - // checken ob der Input valid ist - const checkText = paragraphNode.textBetween(0, $from.parentOffset, null, '\n'); - const shouldWrapInList = checkText === '-'; - if (!shouldWrapInList) return false; - - const tr = state.tr; - - // Lösche das eingegebene Zeichen "-" - tr.delete(from - 1, to); - - // Erstelle eine Bullet-Liste - dispatch(tr); - - const bulletList = state.schema.nodes[LIST_KEY]; - if (!bulletList) throw new Error('node not found in schema'); - - wrapInList(bulletList)(view.state, dispatch); - return true; - }, - handleKeyDown, - }, - }), - listKeymapPlugin(schema), - ], -}; - -export const orderedList: INodeConfig = { - node: { - name: ORDERED_LIST_KEY, - schema: ORDERED_LIST_SCHEMA, + plugins: schema => { + const listKeymapPlugin = keymap({ + ...listKeymap, + 'Shift-Tab': listKeymap['Mod-['], + Tab: listKeymap['Mod-]'], + }); + const listInputRulePlugin = inputRules({ rules: listInputRules }); + const listPlugins = createListPlugins({ schema }); + return [listKeymapPlugin, listInputRulePlugin, ...listPlugins]; }, - plugins: (schema: Schema) => [ - new Plugin({ - props: { - handleTextInput(view, from, to, text) { - const { state, dispatch } = view; - const { $from } = state.selection; - const paragraphNode = $from.parent; - - // Prüfe auf "1. ", "2. " usw. (Ordered List) - const orderedListMatch = paragraphNode.textBetween(0, $from.parentOffset, null, '\n').match(/^(\d+)\.$/); - if (!orderedListMatch) return false; // Wenn kein Muster erkannt wurde - - const tr = state.tr; - - // Lösche die Eingabe "1." - tr.delete(from - orderedListMatch[0].length, to); + stylings: css` + ${unsafeCSS(ListStyles)} - // Erstelle eine Ordered-Liste - dispatch(tr); + .prosemirror-flat-list { + margin-left: 18px; + } - const orderedList = state.schema.nodes[ORDERED_LIST_KEY]; - if (!orderedList) throw new Error('ordered list not found in schema'); - wrapInList(orderedList)(view.state, dispatch); - return true; - }, - handleKeyDown, - }, - }), - listKeymapPlugin(schema), - ], + .prosemirror-flat-list[data-list-kind='ordered']::before { + transform: translate(-6px, -1px); + } + `, }; diff --git a/lab/html-based-buildify/client/nodes/paragraph.ts b/lab/html-based-buildify/client/nodes/paragraph.ts index 897a15c1a5..6c261ef203 100644 --- a/lab/html-based-buildify/client/nodes/paragraph.ts +++ b/lab/html-based-buildify/client/nodes/paragraph.ts @@ -42,10 +42,14 @@ class ParagraphNodeView { this.placeholder.style.left = '0'; this.placeholder.style.top = '50%'; this.placeholder.style.transform = 'translateY(-50%)'; + this.placeholder.contentEditable = 'false'; const contentWrapper = document.createElement('span'); contentWrapper.style.minHeight = '1em'; contentWrapper.style.display = 'inline-block'; + contentWrapper.style.width = '100%'; + contentWrapper.style.outline = 'none'; + contentWrapper.contentEditable = 'true'; element.appendChild(this.placeholder); element.appendChild(contentWrapper); @@ -65,9 +69,10 @@ class ParagraphNodeView { } updatePlaceholderVisibility() { - const isEmpty = this.node.textContent.length === 0; + const isEditorEmpty = this.view.state.doc.textContent.trim() === ''; + const isThisElementEmpty = this.node.textContent.length === 0; - if (isEmpty) { + if (isEditorEmpty && isThisElementEmpty) { this.placeholder.style.display = 'block'; } else { this.placeholder.style.display = 'none'; diff --git a/lab/html-based-buildify/client/prosemirror-editor.ts b/lab/html-based-buildify/client/prosemirror-editor.ts index 7ae87108b1..02245758a2 100644 --- a/lab/html-based-buildify/client/prosemirror-editor.ts +++ b/lab/html-based-buildify/client/prosemirror-editor.ts @@ -14,7 +14,7 @@ import '@adornis/chemistry/elements/components/x-flex'; import '@adornis/chemistry/elements/components/x-icon'; import { FormField } from '@adornis/formfield/form-field.js'; import { ContextProvider, createContext, type Context } from '@lit/context'; -import { html, type PropertyValues } from 'lit'; +import { CSSResult, html, css as litCSS, unsafeCSS, type PropertyValues } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { baseKeymap } from 'prosemirror-commands'; import { dropCursor } from 'prosemirror-dropcursor'; @@ -48,9 +48,14 @@ import './x-prosemirror-toolbar'; const EDITOR_ID = 'editor'; -function getHtmlString(doc) { +function getHtmlString(doc, stylings: CSSResult[]) { const serializer = DOMSerializer.fromSchema(doc.type.schema); const docElement = serializer.serializeNode(doc); + const styleElement = document.createElement('style'); + styleElement.textContent = litCSS` + ${unsafeCSS(stylings)} + `.cssText; + docElement.appendChild(styleElement); const wrapper = document.createElement('div'); wrapper.appendChild(docElement); return wrapper.innerHTML; @@ -163,6 +168,15 @@ export class ProsemirrorEditor extends FormField<string> { return plugins; } + getStylings(): CSSResult[] { + const stylings: CSSResult[] = []; + for (const config of this.configs.value) { + if (!config.stylings) continue; + stylings.push(config.stylings); + } + return stylings; + } + generateSchema() { const nodes: Record<string, NodeSpec> = {}; const marks: Record<string, MarkSpec> = {}; @@ -264,7 +278,7 @@ export class ProsemirrorEditor extends FormField<string> { const node = document.createElement('div'); node.innerHTML = html; - if (getHtmlString(this.view.state.doc) === html) { + if (getHtmlString(this.view.state.doc, this.getStylings()) === html) { return; } @@ -305,10 +319,10 @@ export class ProsemirrorEditor extends FormField<string> { if (!view) return; const { state } = view; const value = JSON.stringify(state.doc.toJSON()); - this.value.next(getHtmlString(state.doc)); + this.value.next(getHtmlString(state.doc, this.getStylings())); this.dispatchEvent( new CustomEvent('value-changed', { - detail: { value, valueHTML: getHtmlString(state.doc) }, + detail: { value, valueHTML: getHtmlString(state.doc, this.getStylings()) }, }), ); } @@ -333,29 +347,7 @@ export class ProsemirrorEditor extends FormField<string> { : '100%'; return html` - <style> - ::selection { - color: inherit !important; - background-color: #eee; - } - - .ProseMirror.ProseMirror-focused { - outline: none; - } - - .ProseMirror { - outline: none; - padding: 0px 8px 16px 8px; - /* prosemirror warns us if we don't set this */ - /* // TODO do we want to load prosemirror's CSS? Has Michi avoided that for a reason? */ - white-space: pre-wrap; - } - - .ProseMirror-selectednode { - position: relative; - outline: 2px solid ${this.colors.accent}; - } - </style> + ${this._getEditorStylings()} <x-buildify-size-picker ${css({ marginBottom: '16px', display: 'block' })} .value=${this._selectedSize} @@ -380,6 +372,39 @@ export class ProsemirrorEditor extends FormField<string> { `; } + private _getEditorStylings() { + const styleElement = document.createElement('style'); + styleElement.textContent = unsafeCSS(` + /* others */ + ::selection { + color: inherit !important; + background-color: #eee; + } + + .ProseMirror.ProseMirror-focused { + outline: none; + } + + .ProseMirror { + outline: none; + padding: 0px 8px 16px 8px; + /* prosemirror warns us if we don't set this */ + /* // TODO do we want to load prosemirror's CSS? Has Michi avoided that for a reason? */ + white-space: pre-wrap; + } + + .ProseMirror-selectednode { + position: relative; + outline: 2px solid ${this.colors.accent}; + } + + /* custom stylings from configs */ + ${this.getStylings()} + `).cssText; + + return styleElement; + } + override styles() { return [ ...super.styles(), diff --git a/lab/html-based-buildify/client/starterPack.ts b/lab/html-based-buildify/client/starterPack.ts index c593a99472..42e5b0680b 100644 --- a/lab/html-based-buildify/client/starterPack.ts +++ b/lab/html-based-buildify/client/starterPack.ts @@ -20,7 +20,7 @@ import { icon } from './nodes/icon.js'; import { iconText, iconWrapper, textWrapper } from './nodes/iconText.js'; import { iframe } from './nodes/iframe.js'; import { image } from './nodes/image.js'; -import { bulletList, listItem, orderedList } from './nodes/list/list.js'; +import { listConfig } from './nodes/list/list.js'; import { paragraph } from './nodes/paragraph.js'; import { section } from './nodes/section.js'; import { spacing } from './nodes/spacing.js'; @@ -58,9 +58,7 @@ export const startedPack: Array<INodeConfig | IMarkConfig> = [ iconWrapper, textWrapper, image, - listItem, - bulletList, - orderedList, + listConfig, section, spacing, tag, diff --git a/lab/html-based-buildify/client/types.ts b/lab/html-based-buildify/client/types.ts index 060302bac2..180c0b36cc 100644 --- a/lab/html-based-buildify/client/types.ts +++ b/lab/html-based-buildify/client/types.ts @@ -2,7 +2,7 @@ import type { ChemistryLitElement } from '@adornis/chemistry/chemistry-lit-eleme import '@adornis/chemistry/elements/components/x-icon'; import type { TranslationController } from '@adornis/translation-core/client/translation-controller.js'; import type { TranslationDictionary } from '@adornis/translation-core/translation.js'; -import type { TemplateResult } from 'lit'; +import type { CSSResult, TemplateResult } from 'lit'; import type { MarkSpec, NodeSpec, Schema } from 'prosemirror-model'; import type { Plugin } from 'prosemirror-state'; import type { EditorView } from 'prosemirror-view'; @@ -38,6 +38,7 @@ export interface IEditorConfigBase { menuItems?: IMenuItem[]; plugins?: (schema: Schema) => Array<Plugin<any>>; editor?: EditorFunc<any, any>; + stylings?: CSSResult; } export interface INodeConfig extends IEditorConfigBase { diff --git a/lab/html-based-buildify/client/x-prosemirror-toolbar.ts b/lab/html-based-buildify/client/x-prosemirror-toolbar.ts index 77175edae4..43289947a2 100644 --- a/lab/html-based-buildify/client/x-prosemirror-toolbar.ts +++ b/lab/html-based-buildify/client/x-prosemirror-toolbar.ts @@ -34,9 +34,10 @@ export function renderMenuItemIcon({ borderRadius: '4px', color: color ?? '#333', })} - @click=${() => { + @click=${e => { if (!run) return; run(view); + view.focus(); }} > ${icon} @@ -52,6 +53,7 @@ export function renderMenuItem(item: IMenuItem, view: EditorView): TemplateResul @click=${() => { if (!item.run) return; item.run(view); + view.focus(); }} > ${item.render(view)} @@ -77,9 +79,10 @@ export function renderMenuItem(item: IMenuItem, view: EditorView): TemplateResul background: isActive ? '' : '#F2F2F1', }, })} - @click=${() => { + @click=${e => { if (!item.run) return; item.run(view); + view.focus(); }} > <x-text> ${item.label} </x-text> diff --git a/lab/html-based-buildify/schema/nodes/list.ts b/lab/html-based-buildify/schema/nodes/list.ts index fcc39b9e34..e69de29bb2 100644 --- a/lab/html-based-buildify/schema/nodes/list.ts +++ b/lab/html-based-buildify/schema/nodes/list.ts @@ -1,42 +0,0 @@ -import type { NodeSpec } from 'prosemirror-model'; - -export const LIST_ITEM_KEY = 'list_item'; -export const LIST_KEY = 'list'; -export const ORDERED_LIST_KEY = 'ordered_list'; - -export const LIST_ITEM_SCHEMA: NodeSpec = { - group: 'block', - content: 'paragraph block*', - toDOM() { - return ['li', 0]; - }, - parseDOM: [{ tag: 'li' }], -}; - -export const LIST_SCHEMA: NodeSpec = { - group: 'block', - draggable: true, - content: `${LIST_ITEM_KEY}+`, - toDOM() { - return ['ul', { style: 'padding-inline-start: 18px;' }, 0]; - }, - parseDOM: [{ tag: 'ul' }], -}; - -export const ORDERED_LIST_SCHEMA: NodeSpec = { - group: 'block', - draggable: true, - content: `${LIST_ITEM_KEY}+`, - attrs: { order: { default: 1 } }, - toDOM(node) { - return ['ol', { start: node.attrs.order, style: 'padding-inline-start: 18px;' }, 0]; - }, - parseDOM: [ - { - tag: 'ol', - getAttrs: dom => ({ - order: dom.hasAttribute('start') ? +dom.getAttribute('start')! : 1, - }), - }, - ], -}; -- GitLab From 68de0d8b495cf5960c9117fe9312a3166af2683b Mon Sep 17 00:00:00 2001 From: michi <michael@adornis.de> Date: Thu, 13 Feb 2025 22:10:44 +0000 Subject: [PATCH 2/2] refactor: remove old list node --- .../client/nodes/grid/node-grid.ts | 4 +- .../client/nodes/{list => }/list.ts | 2 +- .../client/nodes/list/handleKeyDown.ts | 122 ------------------ .../client/nodes/list/keymapPlugin.ts | 13 -- lab/html-based-buildify/client/starterPack.ts | 2 +- lab/html-based-buildify/migration.ts | 3 - lab/html-based-buildify/schema/nodes/list.ts | 0 7 files changed, 4 insertions(+), 142 deletions(-) rename lab/html-based-buildify/client/nodes/{list => }/list.ts (95%) delete mode 100644 lab/html-based-buildify/client/nodes/list/handleKeyDown.ts delete mode 100644 lab/html-based-buildify/client/nodes/list/keymapPlugin.ts delete mode 100644 lab/html-based-buildify/schema/nodes/list.ts diff --git a/lab/html-based-buildify/client/nodes/grid/node-grid.ts b/lab/html-based-buildify/client/nodes/grid/node-grid.ts index 3b52abafbd..1466af9abe 100644 --- a/lab/html-based-buildify/client/nodes/grid/node-grid.ts +++ b/lab/html-based-buildify/client/nodes/grid/node-grid.ts @@ -102,7 +102,7 @@ export class NodeGrid extends BuildifyLitElement<HTMLBaseContainerGrid> { top: '0', bottom: '0', width: '40px', - background: 'linear-gradient(to right, rgba(206,206,206, 1) 40%, rgba(0, 0, 0, 0))', + background: 'linear-gradient(to right, rgba(175, 202, 0, 1) 40%, rgba(0, 0, 0, 0))', color: '#333', fontSize: '24px', })} @@ -122,7 +122,7 @@ export class NodeGrid extends BuildifyLitElement<HTMLBaseContainerGrid> { top: '0', bottom: '0', width: '40px', - background: 'linear-gradient(to left, rgba(206,206,206, 1) 40%, rgba(0, 0, 0, 0))', + background: 'linear-gradient(to left, rgba(175, 202, 0, 1) 40%, rgba(0, 0, 0, 0))', color: '#333', fontSize: '24px', })} diff --git a/lab/html-based-buildify/client/nodes/list/list.ts b/lab/html-based-buildify/client/nodes/list.ts similarity index 95% rename from lab/html-based-buildify/client/nodes/list/list.ts rename to lab/html-based-buildify/client/nodes/list.ts index aa77b0d415..073ae5fd0f 100644 --- a/lab/html-based-buildify/client/nodes/list/list.ts +++ b/lab/html-based-buildify/client/nodes/list.ts @@ -6,7 +6,7 @@ import { css, unsafeCSS } from 'lit'; import { createListPlugins, createListSpec, listInputRules, listKeymap } from 'prosemirror-flat-list'; import { inputRules } from 'prosemirror-inputrules'; import { keymap } from 'prosemirror-keymap'; -import type { INodeConfig } from '../../types.js'; +import type { INodeConfig } from '../types.js'; export const listConfig: INodeConfig = { node: { diff --git a/lab/html-based-buildify/client/nodes/list/handleKeyDown.ts b/lab/html-based-buildify/client/nodes/list/handleKeyDown.ts deleted file mode 100644 index d48bb11156..0000000000 --- a/lab/html-based-buildify/client/nodes/list/handleKeyDown.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { splitListItem } from 'prosemirror-schema-list'; -import { TextSelection, type EditorState } from 'prosemirror-state'; -import type { EditorView } from 'prosemirror-view'; -import { LIST_KEY, ORDERED_LIST_KEY } from '../../../schema/nodes/list.js'; -import { PARAGRAPH_KEY } from '../../../schema/nodes/paragraph.js'; - -export const handleKeyDown = (view, event) => { - const { state } = view; - const { schema } = state; - const { selection } = state; - - // Prüfen, ob eine Auswahl existiert und der Cursor nicht erweitert ist - if (!selection || !selection.empty) { - return false; - } - - // BACKSPACE-Taste - if (event.key === 'Backspace') { - const { $anchor } = selection; - - // Prüfen, ob der Cursor sich innerhalb eines Listenelements befindet - if (isCursorInListItem($anchor, schema)) { - return handleBackspaceInListItem(view, state, schema, $anchor); - } - - // Prüfen, ob der Cursor in einem leeren Paragraphen nach einer Liste steht - if (isEmptyParagraphAfterList(view)) { - return handleBackspaceInEmptyParagraphAfterList(view, state, $anchor); - } - } - - return false; // Standardverhalten für alle anderen Tasten -}; - -/** - * Prüft, ob sich der Cursor in einem Listenelement befindet. - */ -const isCursorInListItem = ($anchor, schema) => { - return $anchor.node(-1).type === schema.nodes.list_item && $anchor.parentOffset === 0; -}; - -/** - * Behandelt die Backspace-Taste, wenn der Cursor am Anfang eines Listenelements steht. - */ -const handleBackspaceInListItem = (view, state, schema, $anchor) => { - const listItem = schema.nodes.list_item; - if (!listItem) { - throw new Error('list_item not found in schema'); - } - - console.log('Backspace pressed at the start of a list item', listItem); - const changed = splitListItem(listItem)(state, view.dispatch); - - if (!changed) { - console.log('No change, simulating Enter key press'); - simulateEnterKeyPress(view); - } - - return true; -}; - -/** - * Prüft, ob der Cursor in einem leeren Paragraphen steht, der direkt nach einer Liste kommt. - */ -const isEmptyParagraphAfterList = (view: EditorView) => { - const { state } = view; - const { selection, schema } = state; - - const { from, to } = selection; - if (from !== to) return false; // Kein gültiger Cursor - - // Node an der aktuellen Position auflösen - const resolvedPos = state.doc.resolve(from); - const resolvedNode = resolvedPos.node(); - - if (resolvedPos.start() - 2 < 0) return false; - - const posBefore = state.doc.resolve(resolvedPos.start() - 2); - const nodeBefore = posBefore.node(); - - return ( - resolvedNode.type === schema.nodes[PARAGRAPH_KEY] && - resolvedNode.content.size === 0 && - nodeBefore && - (nodeBefore.type === schema.nodes[ORDERED_LIST_KEY] || nodeBefore.type === schema.nodes[LIST_KEY]) - ); -}; - -/** - * Behandelt die Backspace-Taste in einem leeren Paragraphen, der direkt nach einer Liste steht. - */ -const handleBackspaceInEmptyParagraphAfterList = (view: EditorView, state: EditorState, $anchor) => { - console.log('Backspace pressed in an empty paragraph after a list'); - - console.log('anchor: ', $anchor, $anchor.before(), $anchor.after()); - - const tr = state.tr; - tr.delete($anchor.before(), $anchor.after()); // Leeren Paragraph löschen - - // Position in das letzte Listenelement setzen - const listEndPos = $anchor.before() - 1; - tr.setSelection(TextSelection.create(tr.doc, listEndPos, listEndPos)); - - view.dispatch(tr); - return true; -}; - -/** - * Simuliert das Drücken der Enter-Taste. - */ -const simulateEnterKeyPress = view => { - const enterEvent = new KeyboardEvent('keydown', { - key: 'Enter', - code: 'Enter', - keyCode: 13, - which: 13, - bubbles: true, - cancelable: true, - }); - - view.dom.dispatchEvent(enterEvent); -}; diff --git a/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts b/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts deleted file mode 100644 index 377cdbdcb9..0000000000 --- a/lab/html-based-buildify/client/nodes/list/keymapPlugin.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { keymap } from 'prosemirror-keymap'; -import { Schema } from 'prosemirror-model'; -import { LIST_ITEM_KEY } from '../../../schema/nodes/list.js'; - -export const listKeymapPlugin = (schema: Schema) => { - const listItem = schema.nodes[LIST_ITEM_KEY]; - if (!listItem) throw new Error('list item not found in schema'); - return keymap({ - // Enter: splitListItem(listItem), - // Tab: sinkListItem(listItem), - // 'Shift-Tab': liftListItem(listItem), - }); -}; diff --git a/lab/html-based-buildify/client/starterPack.ts b/lab/html-based-buildify/client/starterPack.ts index 42e5b0680b..8aadb6325b 100644 --- a/lab/html-based-buildify/client/starterPack.ts +++ b/lab/html-based-buildify/client/starterPack.ts @@ -20,7 +20,7 @@ import { icon } from './nodes/icon.js'; import { iconText, iconWrapper, textWrapper } from './nodes/iconText.js'; import { iframe } from './nodes/iframe.js'; import { image } from './nodes/image.js'; -import { listConfig } from './nodes/list/list.js'; +import { listConfig } from './nodes/list.js'; import { paragraph } from './nodes/paragraph.js'; import { section } from './nodes/section.js'; import { spacing } from './nodes/spacing.js'; diff --git a/lab/html-based-buildify/migration.ts b/lab/html-based-buildify/migration.ts index ae5c7a61e9..46bba1eb6b 100644 --- a/lab/html-based-buildify/migration.ts +++ b/lab/html-based-buildify/migration.ts @@ -35,7 +35,6 @@ import { GRID_CELL_KEY, GRID_KEY } from './schema/nodes/grid.js'; import { HARD_BREAK_KEY } from './schema/nodes/hard-break.js'; import { ICON_TEXT_KEY, ICON_WRAPPER_KEY, TEXT_WRAPPER_KEY } from './schema/nodes/icon-text.js'; import { IMAGE_KEY } from './schema/nodes/image.js'; -import { LIST_ITEM_KEY, LIST_KEY } from './schema/nodes/list.js'; import { SECTION_KEY } from './schema/nodes/section.js'; import { SPACING_KEY } from './schema/nodes/spacing.js'; import { TAG_KEY } from './schema/nodes/tag.js'; @@ -107,8 +106,6 @@ const PARSE_CONTENT_TEXT_TO_JSON = (content: BaseText, otherContents, customData let text = content.text; text = text.replaceAll('textStyle', FONT_SIZE_KEY); text = text.replaceAll('strike', STRIKE_THROUGH_KEY); - text = text.replaceAll('listItem', LIST_ITEM_KEY); - text = text.replaceAll('bulletList', LIST_KEY); text = text.replaceAll('hardBreak', HARD_BREAK_KEY); text = text.replaceAll('"fontSize":8', '"fontSize":"8px"'); text = text.replaceAll('"fontSize":12', '"fontSize":"12px"'); diff --git a/lab/html-based-buildify/schema/nodes/list.ts b/lab/html-based-buildify/schema/nodes/list.ts deleted file mode 100644 index e69de29bb2..0000000000 -- GitLab