diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/RichTextRules.ts')
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 144 |
1 files changed, 70 insertions, 74 deletions
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 42665830f..bf11dfe62 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,17 +1,17 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; +import { ClientUtils } from '../../../../ClientUtils'; import { Doc, DocListCast, FieldResult, StrListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; -import { DocServer } from '../../../DocServer'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { DocUtils } from '../../../documents/DocUtils'; import { CollectionView } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; -import { KeyValueBox } from '../KeyValueBox'; import { FormattedTextBox } from './FormattedTextBox'; import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; @@ -48,13 +48,9 @@ export class RichTextRules { /^A\.\s$/, schema.nodes.ordered_list, // match => { - () => { - return { mapStyle: 'multi', bulletStyle: 1 }; - // return ({ order: +match[1] }) - }, - (match: any, node: any) => { - return node.childCount + node.attrs.order === +match[1]; - }, + () => ({ mapStyle: 'multi', bulletStyle: 1 }), + // return ({ order: +match[1] }) + (match: any, node: any) => node.childCount + node.attrs.order === +match[1], ((type: any) => ({ type: type, attrs: { mapStyle: 'multi', bulletStyle: 1 } })) as any ), @@ -70,7 +66,7 @@ export class RichTextRules { // ``` create code block new InputRule(/^```$/, (state, match, start, end) => { - let $start = state.doc.resolve(start); + const $start = state.doc.resolve(start); if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), schema.nodes.code_block)) return null; // this enables text with code blocks to be used as a 'paint' function via a styleprovider button that is added to Docs that have an onPaint script @@ -86,13 +82,13 @@ export class RichTextRules { }), // %<font-size> set the font size - new InputRule(new RegExp(/%([0-9]+)\s$/), (state, match, start, end) => { + new InputRule(/%([0-9]+)\s$/, (state, match, start, end) => { const size = Number(match[1]); return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); }), - //Create annotation to a field on the text document - new InputRule(new RegExp(/>::$/), (state, match, start, end) => { + // Create annotation to a field on the text document + new InputRule(/>::$/, (state, match, start, end) => { const creator = (doc: Doc) => { const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); @@ -107,7 +103,7 @@ export class RichTextRules { .insert(start, newNode) .replaceRangeWith(start + 1, end + 2, dashDoc) .insertText(' ', start + 2) - .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + .setStoredMarks([...node.marks, ...(sm || [])]) : this.TextBox.EditorView.state.tr ); }; @@ -117,8 +113,8 @@ export class RichTextRules { return null; }), - //Create annotation to a field on the text document - new InputRule(new RegExp(/>>$/), (state, match, start, end) => { + // Create annotation to a field on the text document + new InputRule(/>>$/, (state, match, start, end) => { const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); textDoc.inlineTextCount = numInlines + 1; @@ -150,13 +146,13 @@ export class RichTextRules { .insert(start, newNode) .replaceRangeWith(start + 1, end + 1, dashDoc) .insertText(' ', start + 2) - .setStoredMarks([...node.marks, ...(sm ? sm : [])]) + .setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; return replaced; }), // set the First-line indent node type for the selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/(%d|d)$/), (state, match, start, end) => { + new InputRule(/(%d|d)$/, (state, match, start, end) => { if (!match[0].startsWith('%') && !this.EnteringStyle) return null; const pos = state.doc.resolve(start) as any; for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { @@ -171,7 +167,7 @@ export class RichTextRules { }), // set the Hanging indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/(%h|h)$/), (state, match, start, end) => { + new InputRule(/(%h|h)$/, (state, match, start, end) => { if (!match[0].startsWith('%') && !this.EnteringStyle) return null; const pos = state.doc.resolve(start) as any; for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { @@ -186,11 +182,11 @@ export class RichTextRules { }), // set the Quoted indent node type for the current selection's paragraph (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/(%q|q)$/), (state, match, start, end) => { + new InputRule(/(%q|q)$/, (state, match, start, end) => { if (!match[0].startsWith('%') && !this.EnteringStyle) return null; const pos = state.doc.resolve(start) as any; if (state.selection instanceof NodeSelection && state.selection.node.type === schema.nodes.ordered_list) { - const node = state.selection.node; + const { node } = state.selection; return state.tr.setNodeMarkup(pos.pos, node.type, { ...node.attrs, indent: node.attrs.indent === 30 ? undefined : 30 }); } for (let depth = pos.path.length / 3 - 1; depth >= 0; depth--) { @@ -205,46 +201,43 @@ export class RichTextRules { }), // center justify text - new InputRule(new RegExp(/%\^/), (state, match, start, end) => { + new InputRule(/%\^/, (state, match, start, end) => { const resolved = state.doc.resolve(start) as any; if (resolved?.parent.type.name === 'paragraph') { return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'center' }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'center' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), // left justify text - new InputRule(new RegExp(/%\[/), (state, match, start, end) => { + new InputRule(/%\[/, (state, match, start, end) => { const resolved = state.doc.resolve(start) as any; if (resolved?.parent.type.name === 'paragraph') { return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'left' }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'left' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), // right justify text - new InputRule(new RegExp(/%\]/), (state, match, start, end) => { + new InputRule(/%\]/, (state, match, start, end) => { const resolved = state.doc.resolve(start) as any; if (resolved?.parent.type.name === 'paragraph') { return state.tr.deleteRange(start, end).setNodeMarkup(resolved.path[resolved.path.length - 4], schema.nodes.paragraph, { ...resolved.parent.attrs, align: 'right' }, resolved.parent.marks); - } else { - const node = resolved.nodeAfter; - const sm = state.storedMarks || undefined; - const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; - return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); } + const node = resolved.nodeAfter; + const sm = state.storedMarks || undefined; + const replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: 'right' })).setStoredMarks([...node.marks, ...(sm || [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), // activate a style by name using prefix '%<color name>' - new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => { + new InputRule(/%[a-zA-Z_]+$/, (state, match, start, end) => { const color = match[0].substring(1, match[0].length); const marks = RichTextMenu.Instance?._brushMap.get(color); @@ -259,7 +252,7 @@ export class RichTextRules { } if (marks) { const tr = state.tr.deleteRange(start, end); - return marks ? Array.from(marks).reduce((tr, m) => tr.addStoredMark(m), tr) : tr; + return marks ? Array.from(marks).reduce((tr2, m) => tr2.addStoredMark(m), tr) : tr; } const isValidColor = (strColor: string) => { @@ -269,33 +262,33 @@ 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; }), // toggle alternate text UI %/ - new InputRule(new RegExp(/%\//), (state, match, start, end) => { + new InputRule(/%\//, (state, match, start, end) => { setTimeout(() => this.TextBox.cycleAlternateText(true)); return state.tr.deleteRange(start, end); }), // stop using active style - new InputRule(new RegExp(/%%$/), (state, match, start, end) => { + new InputRule(/%%$/, (state, match, start, end) => { const tr = state.tr.deleteRange(start, end); const marks = state.tr.selection.$anchor.nodeBefore?.marks; return marks ? Array.from(marks) .filter(m => m.type !== state.schema.marks.user_mark) - .reduce((tr, m) => tr.removeStoredMark(m), tr) + .reduce((tr2, m) => tr2.removeStoredMark(m), tr) : tr; }), // create a hyperlink to a titled document // @(<doctitle>) - new InputRule(new RegExp(/@\(([a-zA-Z_@\.\? \-0-9]+)\)/), (state, match, start, end) => { + new InputRule(/@\(([a-zA-Z_@.? \-0-9]+)\)/, (state, match, start, end) => { const docTitle = match[1]; const prefixLength = '@('.length; if (docTitle) { @@ -315,11 +308,11 @@ export class RichTextRules { teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection)))); } }; - const getTitledDoc = (docTitle: string) => { - if (!DocServer.FindDocByTitle(docTitle)) { - Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); + const getTitledDoc = (title: string) => { + if (!Doc.FindDocByTitle(title)) { + Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); } - const titledDoc = DocServer.FindDocByTitle(docTitle); + const titledDoc = Doc.FindDocByTitle(title); return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; }; const target = getTitledDoc(docTitle); @@ -335,22 +328,31 @@ export class RichTextRules { // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value] // [@{this,doctitle,}.fieldKey] new InputRule( - new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/), + /\[(@|@this\.|@[a-zA-Z_? \-0-9]+\.)([a-zA-Z_?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_().@?+\-*/ 0-9()]*))?\]/, (state, match, start, end) => { const docTitle = match[1].substring(1).replace(/\.$/, ''); const fieldKey = match[2]; const assign = match[4] === ':' ? (match[4] = '') : match[4]; const value = match[5]; const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('='); - const getTitledDoc = (docTitle: string) => DocServer.FindDocByTitle(docTitle); + const getTitledDoc = (title: string) => Doc.FindDocByTitle(title); // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' ) if (value?.includes(',') && !value.startsWith('((')) { const values = value.split(','); const strs = values.some(v => !v.match(/^[-]?[0-9.]$/)); this.Document[DocData][fieldKey] = strs ? new List<string>(values) : new List<number>(values.map(v => Number(v))); } else if (value) { - KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined: - (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore + Doc.SetField( + this.Document, + fieldKey, + assign + value, + Doc.IsDataProto(this.Document) ? true : undefined, + assign.includes(':=') + ? undefined + : (gptval: FieldResult) => { + (dataDoc ? this.Document[DocData] : this.Document)[fieldKey] = gptval as string; + } + ); if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr; } const target = docTitle ? getTitledDoc(docTitle) : undefined; @@ -361,9 +363,9 @@ export class RichTextRules { ), // pass the contents between '((' and '))' to chatGPT and append the result - new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => { - var count = 0; // ignore first return value which will be the notation that chat is pending a result - KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { + new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => { + let count = 0; // ignore first return value which will be the notation that chat is pending a result + Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { if (count) { const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length)))); @@ -376,7 +378,7 @@ export class RichTextRules { // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // @(wiki:title) - new InputRule(new RegExp(/@\(wiki:([a-zA-Z_@:\.\?\-0-9 ]+)\)$/), (state, match, start, end) => { + new InputRule(/@\(wiki:([a-zA-Z_@:.?\-0-9 ]+)\)$/, (state, match, start, end) => { const title = match[1].trim().replace(/ /g, '_'); this.TextBox.EditorView?.dispatch(state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end)))); @@ -392,7 +394,7 @@ export class RichTextRules { // create an inline equation node // %eq - new InputRule(new RegExp(/%eq/), (state, match, start, end) => { + new InputRule(/%eq/, (state, match, start, end) => { const fieldKey = 'math' + Utils.GenerateGuid(); this.TextBox.dataDoc[fieldKey] = 'y='; const tr = state.tr.setSelection(new TextSelection(state.tr.doc.resolve(end - 3), state.tr.doc.resolve(end))).replaceSelectionWith(schema.nodes.equation.create({ fieldKey })); @@ -400,10 +402,10 @@ export class RichTextRules { }), // create an inline view of a tag stored under the '#' field - new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { + new InputRule(/#([a-zA-Z_-]+[a-zA-Z_\-0-9]*)\s$/, (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - //this.Document[DocData]['#' + tag] = '#' + tag; + // this.Document[DocData]['#' + tag] = '#' + tag; const tags = StrListCast(this.Document[DocData].tags); if (!tags.includes(tag)) { tags.push(tag); @@ -417,29 +419,25 @@ export class RichTextRules { }), // # heading - textblockTypeInputRule(new RegExp(/^(#{1,6})\s$/), schema.nodes.heading, match => { - return { level: match[1].length }; - }), + textblockTypeInputRule(/^(#{1,6})\s$/, schema.nodes.heading, match => ({ level: match[1].length })), // set the Todo user-tag on the current selection (assumes % was used to initiate an EnteringStyle mode) - new InputRule(new RegExp(/[ti!x]$/), (state, match, start, end) => { + new InputRule(/[ti!x]$/, (state, match, start, end) => { if (state.selection.to === state.selection.from || !this.EnteringStyle) return null; const tag = match[0] === 't' ? 'todo' : match[0] === 'i' ? 'ignore' : match[0] === 'x' ? 'disagree' : match[0] === '!' ? 'important' : '??'; const node = (state.doc.resolve(start) as any).nodeAfter; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) { - } return node ? state.tr .removeMark(start, end, schema.marks.user_mark) - .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })) - .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) + .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })) + .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; }), - new InputRule(new RegExp(/%\(/), (state, match, start, end) => { + new InputRule(/%\(/, (state, match, start, end) => { const node = (state.doc.resolve(start) as any).nodeAfter; const sm = state.storedMarks?.slice() || []; const mark = state.schema.marks.summarizeInclusive.create(); @@ -452,9 +450,7 @@ export class RichTextRules { return replaced.setSelection(new TextSelection(replaced.doc.resolve(end))).setStoredMarks([...node.marks, ...sm]); }), - new InputRule(new RegExp(/%\)/), (state, match, start, end) => { - return state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create()); - }), + new InputRule(/%\)/, (state, match, start, end) => state.tr.deleteRange(start, end).removeStoredMark(state.schema.marks.summarizeInclusive.create())), ], }; } |