diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
8 files changed, 301 insertions, 337 deletions
diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index eaa8fffaa..dc388b22a 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,3 +1,6 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/control-has-associated-label */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; @@ -24,90 +27,69 @@ import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; -export class DashFieldView { - dom: HTMLDivElement; // container for label and value - root: any; - node: any; - tbox: FormattedTextBox; - getpos: any; - @observable _nodeSelected = false; - NodeSelected = () => this._nodeSelected; +@observer +export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> { + // eslint-disable-next-line no-use-before-define + static Instance: DashFieldViewMenu; + static createFieldView: (e: React.MouseEvent) => void = emptyFunction; + static toggleFieldHide: () => void = emptyFunction; + static toggleValueHide: () => void = emptyFunction; + constructor(props: any) { + super(props); + DashFieldViewMenu.Instance = this; + } - unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); - constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - makeObservable(this); - const self = this; - this.node = node; - this.tbox = tbox; - this.getpos = getPos; - this.dom = document.createElement('div'); - this.dom.style.width = node.attrs.width; - this.dom.style.height = node.attrs.height; - this.dom.style.position = 'relative'; - this.dom.style.display = 'inline-block'; - const tBox = this.tbox; - this.dom.onkeypress = function (e: KeyboardEvent) { - e.stopPropagation(); - }; - this.dom.onkeydown = function (e: KeyboardEvent) { - e.stopPropagation(); - if (e.key === 'Tab') { - e.preventDefault(); - const editor = tbox.EditorView; - if (editor) { - const state = editor.state; - for (var i = self.getpos() + 1; i < state.doc.content.size; i++) { - if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) { - editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i)))); - return; - } - } - // tBox.setFocus(state.selection.to); - } - } - }; - this.dom.onkeyup = function (e: any) { - e.stopPropagation(); - }; - this.dom.onmousedown = function (e: any) { - e.stopPropagation(); - }; + showFields = (e: React.MouseEvent) => { + DashFieldViewMenu.createFieldView(e); + DashFieldViewMenu.Instance.fadeOut(true); + }; + toggleFieldHide = () => { + DashFieldViewMenu.toggleFieldHide(); + DashFieldViewMenu.Instance.fadeOut(true); + }; + toggleValueHide = () => { + DashFieldViewMenu.toggleValueHide(); + DashFieldViewMenu.Instance.fadeOut(true); + }; - this.root = ReactDOM.createRoot(this.dom); - this.root.render( - <DashFieldViewInternal - node={node} - unclickable={this.unclickable} - getPos={getPos} - fieldKey={node.attrs.fieldKey} - docId={node.attrs.docId} - width={node.attrs.width} - height={node.attrs.height} - hideKey={node.attrs.hideKey} - hideValue={node.attrs.hideValue} - editable={node.attrs.editable} - nodeSelected={this.NodeSelected} - tbox={tbox} - /> + @observable _fieldKey = ''; + + @action + public show = (x: number, y: number, fieldKey: string) => { + this._fieldKey = fieldKey; + this.jumpTo(x, y, true); + const hideMenu = () => { + this.fadeOut(true); + document.removeEventListener('pointerdown', hideMenu, true); + }; + document.addEventListener('pointerdown', hideMenu, true); + }; + render() { + return this.getElement( + <> + <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}> + <button type="button" className="antimodeMenu-button" onPointerDown={this.showFields}> + <FontAwesomeIcon icon="eye" size="sm" /> + </button> + </Tooltip> + {this._fieldKey.startsWith('#') ? null : ( + <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}> + <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}> + <FontAwesomeIcon icon="bullseye" size="sm" /> + </button> + </Tooltip> + )} + {this._fieldKey.startsWith('#') ? null : ( + <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}> + <button type="button" className="antimodeMenu-button" onPointerDown={this.toggleValueHide}> + <FontAwesomeIcon icon="hashtag" size="sm" /> + </button> + </Tooltip> + )} + </> ); } - destroy() { - setTimeout(() => { - try { - this.root.unmount(); - } catch {} - }); - } - deselectNode() { - runInAction(() => (this._nodeSelected = false)); - this.dom.classList.remove('ProseMirror-selectednode'); - } - selectNode() { - setTimeout(() => runInAction(() => (this._nodeSelected = true)), 100); - this.dom.classList.add('ProseMirror-selectednode'); - } } - interface IDashFieldViewInternal { fieldKey: string; docId: string; @@ -137,7 +119,9 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi makeObservable(this); this._fieldKey = this._props.fieldKey; this._textBoxDoc = this._props.tbox.Document; - const setDoc = action((doc: Doc) => (this._dashDoc = doc)); + const setDoc = action((doc: Doc) => { + this._dashDoc = doc; + }); if (this._props.docId) { DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc)); @@ -172,7 +156,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { return !this._dashDoc ? null : ( - <div onClick={action(e => (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}> + <div + onClick={action(() => { + this._expanded = !this._props.editable ? !this._expanded : true; + })} + style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}> <SchemaTableCell Document={this._dashDoc} col={0} @@ -187,20 +175,20 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi padding={0} getFinfo={emptyFunction} setColumnValues={returnFalse} - allowCRs={true} + allowCRs oneLine={!this._expanded && !this._props.nodeSelected()} finishEdit={this.finishEdit} transform={Transform.Identity} menuTarget={null} - autoFocus={true} + autoFocus rootSelected={this._props.tbox._props.rootSelected} /> </div> ); } - createPivotForField = (e: React.MouseEvent) => { - let container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement(); + createPivotForField = () => { + const container = this._props.tbox.DocumentView?.().containerViewPath?.().lastElement(); if (container) { const embedding = Doc.MakeEmbedding(container.Document); embedding._type_collection = CollectionViewType.Time; @@ -219,7 +207,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi toggleFieldHide = undoable( action(() => { const editor = this._props.tbox.EditorView!; - editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey ? true : false })); + editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: this._props.node.attrs.hideValue ? false : !this._props.node.attrs.hideKey })); }), 'hideKey' ); @@ -227,7 +215,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi toggleValueHide = undoable( action(() => { const editor = this._props.tbox.EditorView!; - editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue ? true : false })); + editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: this._props.node.attrs.hideKey ? false : !this._props.node.attrs.hideValue })); }), 'hideValue' ); @@ -243,11 +231,11 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label onPointerDownLabelSpan = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, moveEv => { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide; DashFieldViewMenu.toggleValueHide = this.toggleValueHide; - DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey); + DashFieldViewMenu.Instance.show(moveEv.clientX, moveEv.clientY + 16, this._fieldKey); const editor = this._props.tbox.EditorView!; setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100); }); @@ -277,7 +265,7 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi }}> {this._hideKey ? null : ( <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}> - {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey} + {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : (this._dashDoc?.title ?? '') + ':') + this._fieldKey} </span> )} {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent} @@ -293,65 +281,93 @@ export class DashFieldViewInternal extends ObservableReactComponent<IDashFieldVi ); } } -@observer -export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> { - static Instance: DashFieldViewMenu; - static createFieldView: (e: React.MouseEvent) => void = emptyFunction; - static toggleFieldHide: () => void = emptyFunction; - static toggleValueHide: () => void = emptyFunction; - constructor(props: any) { - super(props); - DashFieldViewMenu.Instance = this; - } - - showFields = (e: React.MouseEvent) => { - DashFieldViewMenu.createFieldView(e); - DashFieldViewMenu.Instance.fadeOut(true); - }; - toggleFieldHide = (e: React.MouseEvent) => { - DashFieldViewMenu.toggleFieldHide(); - DashFieldViewMenu.Instance.fadeOut(true); - }; - toggleValueHide = (e: React.MouseEvent) => { - DashFieldViewMenu.toggleValueHide(); - DashFieldViewMenu.Instance.fadeOut(true); - }; - - @observable _fieldKey = ''; +export class DashFieldView { + dom: HTMLDivElement; // container for label and value + root: any; + node: any; + tbox: FormattedTextBox; + getpos: any; + @observable _nodeSelected = false; + NodeSelected = () => this._nodeSelected; - @action - public show = (x: number, y: number, fieldKey: string) => { - this._fieldKey = fieldKey; - this.jumpTo(x, y, true); - const hideMenu = () => { - this.fadeOut(true); - document.removeEventListener('pointerdown', hideMenu, true); + unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + makeObservable(this); + const self = this; + this.node = node; + this.tbox = tbox; + this.getpos = getPos; + this.dom = document.createElement('div'); + this.dom.style.width = node.attrs.width; + this.dom.style.height = node.attrs.height; + this.dom.style.position = 'relative'; + this.dom.style.display = 'inline-block'; + this.dom.onkeypress = function (e: KeyboardEvent) { + e.stopPropagation(); }; - document.addEventListener('pointerdown', hideMenu, true); - }; - render() { - return this.getElement( - <> - <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}> - <button className="antimodeMenu-button" onPointerDown={this.showFields}> - <FontAwesomeIcon icon="eye" size="sm" /> - </button> - </Tooltip> - {this._fieldKey.startsWith('#') ? null : ( - <Tooltip key="key" title={<div className="dash-tooltip">Toggle view of field key</div>}> - <button className="antimodeMenu-button" onPointerDown={this.toggleFieldHide}> - <FontAwesomeIcon icon="bullseye" size="sm" /> - </button> - </Tooltip> - )} - {this._fieldKey.startsWith('#') ? null : ( - <Tooltip key="val" title={<div className="dash-tooltip">Toggle view of field value</div>}> - <button className="antimodeMenu-button" onPointerDown={this.toggleValueHide}> - <FontAwesomeIcon icon="hashtag" size="sm" /> - </button> - </Tooltip> - )} - </> + this.dom.onkeydown = function (e: KeyboardEvent) { + e.stopPropagation(); + if (e.key === 'Tab') { + e.preventDefault(); + const editor = tbox.EditorView; + if (editor) { + const { state } = editor; + for (let i = self.getpos() + 1; i < state.doc.content.size; i++) { + if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) { + editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i)))); + return; + } + } + } + } + }; + this.dom.onkeyup = function (e: any) { + e.stopPropagation(); + }; + this.dom.onmousedown = function (e: any) { + e.stopPropagation(); + }; + + this.root = ReactDOM.createRoot(this.dom); + this.root.render( + <DashFieldViewInternal + node={node} + unclickable={this.unclickable} + getPos={getPos} + fieldKey={node.attrs.fieldKey} + docId={node.attrs.docId} + width={node.attrs.width} + height={node.attrs.height} + hideKey={node.attrs.hideKey} + hideValue={node.attrs.hideValue} + editable={node.attrs.editable} + nodeSelected={this.NodeSelected} + tbox={tbox} + /> ); } + destroy() { + setTimeout(() => { + try { + this.root.unmount(); + } catch { + /* empty */ + } + }); + } + deselectNode() { + runInAction(() => { + this._nodeSelected = false; + }); + this.dom.classList.remove('ProseMirror-selectednode'); + } + selectNode() { + setTimeout( + action(() => { + this._nodeSelected = true; + }), + 100 + ); + this.dom.classList.add('ProseMirror-selectednode'); + } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 5b2b558fc..3a1a72910 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -350,7 +350,8 @@ footnote::before { span { font-family: inherit; background-color: inherit; - display: inline; // needs to be inline for search highlighting to appear // contents; // fixes problem where extra space is added around <ol> lists when inside a prosemirror span + display: inline; // needs to be inline for search highlighting to appear + // display: contents; // BUT needs to be 'contents' to avoid Chrome bug where extra space is added above and <ol> lists when inside a prosemirror span } blockquote { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a82f025f9..99a2f4ab9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1531,7 +1531,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { fontFamily: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily) })] : []), ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize) })] : []), ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), ...[schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })], diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index c3a5a2c86..073ed91c3 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -23,7 +23,7 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle tx2.doc.descendants((node: any, offset: any /* , index: any */) => { if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) { const { path } = tx2.doc.resolve(offset) as any; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty('type') && c.type === schema.nodes.ordered_list ? 1 : 0), 0); + let depth = Array.from(path).reduce((p: number, c: any) => p + (c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) { if (depth === 0 && !assignedMapStyle) mapStyle = node.attrs.mapStyle; depth++; @@ -34,18 +34,19 @@ export const updateBullets = (tx2: Transaction, schema: Schema, assignedMapStyle return tx2; }; -export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKeys?: KeyMap): KeyMap { +export function buildKeymap<S extends Schema<any>>(schema: S, props: any): KeyMap { const keys: { [key: string]: any } = {}; function bind(key: string, cmd: any) { - if (mapKeys) { - const mapped = mapKeys[key]; - if (mapped === false) return; - if (mapped) key = mapped; - } keys[key] = cmd; } + function onKey(): boolean | undefined { + // bcz: this is pretty hacky -- prosemirror doesn't send us the keyboard event, but the 'event' variable is in scope.. so we access it anyway + // eslint-disable-next-line no-restricted-globals + return props.onKey?.(event, props); + } + const canEdit = (state: any) => { switch (GetEffectiveAcl(props.TemplateDataDocument)) { case AclAugment: @@ -84,12 +85,12 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey // Commands for lists bind('Ctrl-i', (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); - bind('Ctrl-Tab', () => (props.onKey?.(event, props) ? true : true)); - bind('Alt-Tab', () => (props.onKey?.(event, props) ? true : true)); - bind('Meta-Tab', () => (props.onKey?.(event, props) ? true : true)); - bind('Meta-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Ctrl-Tab', () => onKey() || true); + bind('Alt-Tab', () => onKey() || true); + bind('Meta-Tab', () => onKey() || true); + bind('Meta-Enter', () => onKey() || true); bind('Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.onKey?.(event, props)) return true; + if (onKey()) return true; if (!canEdit(state)) return true; const ref = state.selection; const range = ref.$from.blockRange(ref.$to); @@ -119,10 +120,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey console.log('bullet promote fail'); } } + return undefined; }); bind('Shift-Tab', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.onKey?.(event, props)) return true; + if (onKey()) return true; if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); @@ -136,10 +138,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey ) { console.log('bullet demote fail'); } + return undefined; }); // Command to create a new Tab with a PDF of all the command shortcuts - bind('Mod-/', (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Mod-/', () => { const newDoc = Docs.Create.PdfDocument(ClientUtils.prepend('/assets/cheat-sheet.pdf'), { _width: 300, _height: 300 }); props.addDocTab(newDoc, OpenWhere.addRight); }); @@ -171,13 +174,13 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey SelectionManager.DeselectAll(); }); - bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true)); - bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true)); + bind('Alt-Enter', () => onKey() || true); + bind('Ctrl-Enter', () => onKey() || true); bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => { dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(1), state.doc.resolve(state.doc.content.size - 1)))); return true; }); - bind('Cmd-?', (state: EditorState, dispatch: (tx: Transaction) => void) => { + bind('Cmd-?', () => { RTFMarkup.Instance.open(); return true; }); @@ -200,7 +203,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const node = resolved.nodeAfter; const sm = state.storedMarks || undefined; if (node) { - tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]); + tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]); } } dispatch(tr); @@ -215,7 +218,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const node = resolved.nodeAfter; const sm = state.storedMarks || undefined; if (node) { - tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]); + tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]); } } dispatch(tr); @@ -230,7 +233,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const node = resolved.nodeAfter; const sm = state.storedMarks || undefined; if (node) { - tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]); + tr.replaceRangeWith(state.selection.from, state.selection.from, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]); } } dispatch(tr); @@ -262,7 +265,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { - if (props.onKey?.(event, props)) return true; + if (onKey()) return true; if (!canEdit(state)) return true; if ( @@ -296,7 +299,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey // command to break line const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => { - if (props.onKey?.(event, props)) return true; + if (onKey()) return true; if (!canEdit(state)) return true; const trange = state.selection.$from.blockRange(state.selection.$to); @@ -361,8 +364,8 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey bind('Enter', enter); // Command to create a blank space - bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true; + bind('Space', () => { + if (props.TemplateDataDocument && ![AclAdmin, AclAugment, AclEdit].includes(GetEffectiveAcl(props.TemplateDataDocument))) return true; return false; }); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index ec9c1a15d..6108383c2 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -23,10 +23,12 @@ import { FormattedTextBox } from './FormattedTextBox'; import { updateBullets } from './ProsemirrorExampleTransfer'; import './RichTextMenu.scss'; import { schema } from './schema_rts'; + const { toggleMark } = require('prosemirror-commands'); @observer export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { + // eslint-disable-next-line no-use-before-define static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined }); static get Instance() { return RichTextMenu._instance?.menu; @@ -116,7 +118,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer = reaction( () => SelectionManager.Views.slice(), - views => this.updateMenu(undefined, undefined, undefined, undefined) + () => this.updateMenu(undefined, undefined, undefined, undefined) ); } componentWillUnmount() { @@ -139,10 +141,10 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.setActiveMarkButtons(this.getActiveMarksOnSelection()); const active = this.getActiveFontStylesOnSelection(); - const activeFamilies = active.activeFamilies; - const activeSizes = active.activeSizes; - const activeColors = active.activeColors; - const activeHighlights = active.activeHighlights; + const { activeFamilies } = active; + const { activeSizes } = active; + const { activeColors } = active; + const { activeHighlights } = active; const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc(); const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey); @@ -174,8 +176,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { dispatch(updateBullets(markup, state.schema)); } else { const state = this.view?.state; - const tr = this.view?.state.tr; - if (tr && state) { + if (state) { + const { tr } = state; if (dontToggle) { tr.addMark(state.selection.from, state.selection.to, mark); dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise @@ -190,7 +192,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveAlignment() { if (this.view && this.TextView?._props.rootSelected?.()) { - const path = (this.view.state.selection.$from as any).path; + const { path } = this.view.state.selection.$from as any; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { return path[i].attrs.align || 'left'; @@ -222,24 +224,25 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const activeColors = new Set<string>(); const activeHighlights = new Set<string>(); if (this.view && this.TextView?._props.rootSelected?.()) { - const state = this.view.state; + const { state } = this.view; const pos = this.view.state.selection.$from; - var marks: Mark[] = [...(state.storedMarks ?? [])]; + let marks: Mark[] = [...(state.storedMarks ?? [])]; if (state.storedMarks !== null) { + /* empty */ } else if (state.selection.empty) { for (let i = 0; i <= pos.depth; i++) { marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks]; } } else { - state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => { node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); }); } marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family); - m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color); + m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.fontFamily); + m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.fontColor); m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize); - m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight)); + m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHigh)); }); } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) { SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize))); @@ -254,26 +257,27 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return found; } - //finds all active marks on selection in given group + // finds all active marks on selection in given group getActiveMarksOnSelection() { if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[]; - const state = this.view.state; - var marks: Mark[] = [...(state.storedMarks ?? [])]; + const { state } = this.view; + let marks: Mark[] = [...(state.storedMarks ?? [])]; const pos = this.view.state.selection.$from; if (state.storedMarks !== null) { + /* empty */ } else if (state.selection.empty) { for (let i = 0; i <= pos.depth; i++) { marks = [...Array.from(pos.node(i).marks), ...this.view.state.selection.$anchor.marks(), ...marks]; } } else { - state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + state.doc.nodesBetween(state.selection.from, state.selection.to, (node /* , pos, parent, index */) => { node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark)); }); } const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; - return markGroup.filter(mark_type => { - const mark = state.schema.mark(mark_type); + return markGroup.filter(markType => { + const mark = state.schema.mark(markType); return mark.isInSet(marks); }); } @@ -291,7 +295,6 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this._superscriptActive = false; activeMarks.forEach(mark => { - // prettier-ignore switch (mark.name) { case 'noAutoLinkAnchor': this._noLinkActive = true; break; case 'strong': this._boldActive = true; break; @@ -300,7 +303,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { case 'strikethrough': this._strikethroughActive = true; break; case 'subscript': this._subscriptActive = true; break; case 'superscript': this._superscriptActive = true; break; - } + default: + } // prettier-ignore }); } @@ -353,53 +357,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } }; - setFontSize = (fontSize: string) => { + setFontField = (value: string, fontField: 'fontSize' | 'fontFamily' | 'fontColor' | 'fontHighlight') => { if (this.view) { - if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { - this.TextView.dataDoc[this.TextView.fieldKey + '_fontSize'] = fontSize; + if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === value))) { + this.TextView.dataDoc[this.TextView.fieldKey + `_${fontField}`] = value; this.view.focus(); } else { - const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize }); + const attrs: { [key: string]: string } = {}; + attrs[fontField] = value; + const fmark = this.view?.state.schema.marks['pF' + fontField.substring(1)].create(attrs); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); } - } else if (SelectionManager.Views.length) { - SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontSize'] = fontSize)); - } else Doc.UserDoc().fontSize = fontSize; - this.updateMenu(this.view, undefined, this.props, this.layoutDoc); - }; - - setFontFamily = (family: string) => { - if (this.view) { - const fmark = this.view.state.schema.marks.pFontFamily.create({ family }); - this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); - this.view.focus(); - } else if (SelectionManager.Views.length) { - SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontFamily'] = family)); - } else Doc.UserDoc().fontFamily = family; + } else Doc.UserDoc()[fontField] = value; this.updateMenu(this.view, undefined, this.props, this.layoutDoc); }; - setHighlight(color: string) { - if (this.view) { - const highlightMark = this.view.state.schema.mark(this.view.state.schema.marks.marker, { highlight: color }); - this.setMark(highlightMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(highlightMark)), true); - this.view.focus(); - } else Doc.UserDoc()._fontHighlight = color; - this.updateMenu(this.view, undefined, this.props, this.layoutDoc); - } - - setColor(color: string) { - if (this.view) { - const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color }); - this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); - this.view.focus(); - } else if (SelectionManager.Views.length) { - SelectionManager.Views.forEach(dv => (dv.layoutDoc[dv.LayoutFieldKey + '_fontColor'] = color)); - } else Doc.UserDoc().fontColor = color; - this.updateMenu(this.view, undefined, this.props, this.layoutDoc); - } - // TODO: remove doesn't work // remove all node type and apply the passed-in one to the selected text changeListType = (mapStyle: string) => { @@ -407,7 +380,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const newMapStyle = active === mapStyle ? '' : mapStyle; if (!this.view || newMapStyle === '') return; - let inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list; + const inList = this.view.state.selection.$anchor.node(1).type === schema.nodes.ordered_list; const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); if (inList) { const tx2 = updateBullets(this.view.state.tr, schema, newMapStyle, this.view.state.doc.resolve(this.view.state.selection.$anchor.before(1) + 1).pos, this.view.state.doc.resolve(this.view.state.selection.$anchor.after(1)).pos); @@ -428,7 +401,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { insertSummarizer(state: EditorState, dispatch: any) { if (state.selection.empty) return false; const mark = state.schema.marks.summarize.create(); - const tr = state.tr; + const { tr } = state; tr.addMark(state.selection.from, state.selection.to, mark); const content = tr.selection.content(); const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); @@ -436,13 +409,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return true; } - vcenterToggle = (view: EditorView, dispatch: any) => { + vcenterToggle = () => { this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered); }; align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { if (this.TextView?._props.rootSelected?.()) { - var tr = view.state.tr; - view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { + let { tr } = view.state; + view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, align: alignment }, node.marks); return false; @@ -455,56 +428,14 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } }; - insetParagraph(state: EditorState, dispatch: any) { - var tr = state.tr; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { + paragraphSetup(state: EditorState, dispatch: any, field: 'inset' | 'indent', value?: 0 | 10 | -10) { + let { tr } = state; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => { if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { - const inset = (node.attrs.inset ? Number(node.attrs.inset) : 0) + 10; - tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks); - return false; - } - return true; - }); - dispatch?.(tr); - return true; - } - outsetParagraph(state: EditorState, dispatch: any) { - var tr = state.tr; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { - const inset = Math.max(0, (node.attrs.inset ? Number(node.attrs.inset) : 0) - 10); - tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, inset }, node.marks); - return false; - } - return true; - }); - dispatch?.(tr); - return true; - } - - indentParagraph(state: EditorState, dispatch: any) { - var tr = state.tr; - const heading = false; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { - const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined; - const indent = !nodeval ? 25 : nodeval < 0 ? 0 : nodeval + 25; - tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks); - return false; - } - return true; - }); - !heading && dispatch?.(tr); - return true; - } - - hangingIndentParagraph(state: EditorState, dispatch: any) { - var tr = state.tr; - state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos, parent, index) => { - if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) { - const nodeval = node.attrs.indent ? Number(node.attrs.indent) : undefined; - const indent = !nodeval ? -25 : nodeval > 0 ? 0 : nodeval - 10; - tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent }, node.marks); + const newValue = !value ? + (node.attrs[field] ? 0 : node.attrs[field] + 10) : + Math.max(0, value); // prettier-ignore + tr = tr.setNodeMarkup(pos, node.type, { ...node.attrs, ...(field === 'inset' ? { inset: newValue } : { indent: newValue }) }, node.marks); return false; } return true; @@ -514,7 +445,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } insertBlockquote(state: EditorState, dispatch: any) { - const path = (state.selection.$from as any).path; + const { path } = state.selection.$from as any; if (path.length > 6 && path[path.length - 6].type === schema.nodes.blockquote) { lift(state, dispatch); } else { @@ -547,13 +478,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } @action - fillBrush(state: EditorState, dispatch: any) { + fillBrush() { if (!this.view) return; if (!Array.from(this.brushMarks.keys()).length) { - const selected_marks = this.getMarksInSelection(this.view.state); - if (selected_marks.size >= 0) { - this.brushMarks = selected_marks; + const selectedMarks = this.getMarksInSelection(this.view.state); + if (selectedMarks.size >= 0) { + this.brushMarks = selectedMarks; } } else { const { from, to, $from } = this.view.state.selection; @@ -597,9 +528,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const button = ( <Tooltip title={<div className="dash-tooltip">set hyperlink</div>} placement="bottom"> - <button className="antimodeMenu-button color-preview-button"> - <FontAwesomeIcon icon="link" size="lg" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button type="button" className="antimodeMenu-button color-preview-button"> + <FontAwesomeIcon icon="link" size="lg" /> + </button> + } </Tooltip> ); @@ -607,21 +541,22 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { <div className="dropdown link-menu"> <p>Linked to:</p> <input value={link} ref={this._linkToRef} placeholder="Enter URL" onChange={onLinkChange} /> - <button className="make-button" onPointerDown={e => this.makeLinkToURL(link, 'add:right')}> + <button type="button" className="make-button" onPointerDown={() => this.makeLinkToURL(link)}> Apply hyperlink </button> <div className="divider" /> - <button className="remove-button" onPointerDown={e => this.deleteLink()}> + <button type="button" className="remove-button" onPointerDown={() => this.deleteLink()}> Remove link </button> </div> ); - return <ButtonDropdown view={this.view} key={'link button'} button={button} dropdownContent={dropdownContent} openDropdownOnButton={true} link={true} />; + // eslint-disable-next-line no-use-before-define + return <ButtonDropdown view={this.view} key="link button" button={button} dropdownContent={dropdownContent} openDropdownOnButton link />; } async getTextLinkTargetTitle() { - if (!this.view) return; + if (!this.view) return undefined; const node = this.view.state.selection.$from.nodeAfter; const link = node && node.marks.find(m => m.type.name === 'link'); @@ -633,15 +568,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (linkclicked) { const linkDoc = await DocServer.GetRefField(linkclicked); if (linkDoc instanceof Doc) { - const link_anchor_1 = await Cast(linkDoc.link_anchor_1, Doc); - const link_anchor_2 = await Cast(linkDoc.link_anchor_2, Doc); + const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc); + const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc); const currentDoc = SelectionManager.Docs.lastElement(); - if (currentDoc && link_anchor_1 && link_anchor_2) { - if (Doc.AreProtosEqual(currentDoc, link_anchor_1)) { - return StrCast(link_anchor_2.title); + if (currentDoc && linkAnchor1 && linkAnchor2) { + if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) { + return StrCast(linkAnchor2.title); } - if (Doc.AreProtosEqual(currentDoc, link_anchor_2)) { - return StrCast(link_anchor_1.title); + if (Doc.AreProtosEqual(currentDoc, linkAnchor2)) { + return StrCast(linkAnchor1.title); } } } @@ -653,11 +588,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return link.attrs.title; } } + return undefined; } // TODO: should check for valid URL @undoBatch - makeLinkToURL = (target: string, lcoation: string) => { + makeLinkToURL = (target: string) => { ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); }; @@ -736,7 +672,11 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps render() { return ( - <div className="button-dropdown-wrapper" ref={node => (this.ref = node)}> + <div + className="button-dropdown-wrapper" + ref={node => { + this.ref = node; + }}> {!this._props.pdf ? ( <div className="antimodeMenu-button dropdown-button-combined" onPointerDown={this._props.openDropdownOnButton ? this.onDropdownClick : undefined}> {this._props.button} @@ -747,9 +687,12 @@ export class ButtonDropdown extends ObservableReactComponent<ButtonDropdownProps ) : ( <> {this._props.button} - <button className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}> - <FontAwesomeIcon icon="caret-down" size="sm" /> - </button> + { + // eslint-disable-next-line jsx-a11y/control-has-associated-label + <button type="button" className="dropdown-button antimodeMenu-button" key="antimodebutton" onPointerDown={this.onDropdownClick}> + <FontAwesomeIcon icon="caret-down" size="sm" /> + </button> + } </> )} {this.showDropdown ? this._props.dropdownContent : null} @@ -762,10 +705,11 @@ interface RichTextMenuPluginProps { editorProps: any; } export class RichTextMenuPlugin extends React.Component<RichTextMenuPluginProps> { - render() { - return null; - } + // eslint-disable-next-line react/no-unused-class-component-methods update(view: EditorView, lastState: EditorState | undefined) { RichTextMenu.Instance?.updateMenu(view, lastState, this.props.editorProps, (view as any).TextView?.layoutDoc); } + render() { + return null; + } } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 5b5617484..88adab66e 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -263,7 +263,7 @@ export class RichTextRules { }; if (isValidColor(color)) { - return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ color: color })); + return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontColor.create({ fontColor: color })); } return null; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index d1c7b72a5..6e1f325cf 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -127,29 +127,29 @@ export const marks: { [index: string]: MarkSpec } = { /* FONTS */ pFontFamily: { - attrs: { family: { default: '' } }, + attrs: { fontFamily: { default: '' } }, parseDOM: [ { tag: 'span', getAttrs(dom: any) { const cstyle = getComputedStyle(dom); if (cstyle.font) { - if (cstyle.font.indexOf('Times New Roman') !== -1) return { family: 'Times New Roman' }; - if (cstyle.font.indexOf('Arial') !== -1) return { family: 'Arial' }; - if (cstyle.font.indexOf('Georgia') !== -1) return { family: 'Georgia' }; - if (cstyle.font.indexOf('Comic Sans') !== -1) return { family: 'Comic Sans MS' }; - if (cstyle.font.indexOf('Tahoma') !== -1) return { family: 'Tahoma' }; - if (cstyle.font.indexOf('Crimson') !== -1) return { family: 'Crimson Text' }; + if (cstyle.font.indexOf('Times New Roman') !== -1) return { fontFamily: 'Times New Roman' }; + if (cstyle.font.indexOf('Arial') !== -1) return { fontFamily: 'Arial' }; + if (cstyle.font.indexOf('Georgia') !== -1) return { fontFamily: 'Georgia' }; + if (cstyle.font.indexOf('Comic Sans') !== -1) return { fontFamily: 'Comic Sans MS' }; + if (cstyle.font.indexOf('Tahoma') !== -1) return { fontFamily: 'Tahoma' }; + if (cstyle.font.indexOf('Crimson') !== -1) return { fontFamily: 'Crimson Text' }; } - return { family: '' }; + return { fontFamily: '' }; }, }, ], - toDOM: node => (node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0]), + toDOM: node => (node.attrs.fontFamily ? ['span', { style: `font-family: "${node.attrs.fontFamily}";` }] : ['span', 0]), }, // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. pFontColor: { - attrs: { color: { default: '' } }, + attrs: { fontColor: { default: '' } }, inclusive: true, parseDOM: [ { @@ -159,24 +159,24 @@ export const marks: { [index: string]: MarkSpec } = { }, }, ], - toDOM: node => (node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]), + toDOM: node => (node.attrs.fontColor ? ['span', { style: 'color:' + node.attrs.fontColor }] : ['span', 0]), }, - marker: { + pFontHighlight: { attrs: { - highlight: { default: 'transparent' }, + fontHighlight: { default: 'transparent' }, }, inclusive: true, parseDOM: [ { tag: 'span', getAttrs(dom: any) { - return { highlight: dom.getAttribute('backgroundColor') }; + return { fontHighlight: dom.getAttribute('background-color') }; }, }, ], toDOM(node: any) { - return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; + return node.attrs.fontHighlight ? ['span', { style: 'background-color:' + node.attrs.fontHighlight }] : ['span', { style: 'background-color: transparent' }]; }, }, diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 70b6604ab..184487b7d 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -4,14 +4,14 @@ import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './Para import { DocServer } from '../../../DocServer'; import { Doc, Field, FieldType } from '../../../../fields/Doc'; -const blockquoteDOM: DOMOutputSpec = ['blockquote', 0], - hrDOM: DOMOutputSpec = ['hr'], - preDOM: DOMOutputSpec = ['pre', ['code', 0]], - brDOM: DOMOutputSpec = ['br'], - ulDOM: DOMOutputSpec = ['ul', 0]; +const blockquoteDOM: DOMOutputSpec = ['blockquote', 0]; +const hrDOM: DOMOutputSpec = ['hr']; +const preDOM: DOMOutputSpec = ['pre', ['code', 0]]; +const brDOM: DOMOutputSpec = ['br']; +// const ulDOM: DOMOutputSpec = ['ul', 0]; -function formatAudioTime(time: number) { - time = Math.round(time); +function formatAudioTime(timeIn: number) { + const time = Math.round(timeIn); const hours = Math.floor(time / 60 / 60); const minutes = Math.floor(time / 60) - hours * 60; const seconds = time % 60; |
