aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/views/global/globalScripts.ts50
-rw-r--r--src/client/views/nodes/formattedText/DashFieldView.scss1
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.scss14
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx47
-rw-r--r--src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts2
-rw-r--r--src/client/views/nodes/formattedText/RichTextMenu.tsx199
-rw-r--r--src/client/views/nodes/formattedText/RichTextRules.ts4
-rw-r--r--src/client/views/nodes/formattedText/nodes_rts.ts30
9 files changed, 113 insertions, 236 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index b73111062..38ebc86e7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -725,7 +725,7 @@ pie title Minerals in my tap water
btnList: new List<string>(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) },
{ title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 6 },
{ title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
- { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} },
+ { title: "Highlight",toolTip: "Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(this.toolType, value, _readOnly_);}'}},
{ title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
{ title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} },
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index dab642499..497ab98d8 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -151,26 +151,26 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
const map: Map<'font'|'fontColor'|'highlight'|'fontSize'|'alignment', { checkResult: () => any; setDoc: () => void;}> = new Map([
['font', {
checkResult: () => RichTextMenu.Instance?.fontFamily,
- setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
+ setDoc: () => value && RichTextMenu.Instance?.setFontFamily(value),
}],
['highlight', {
- checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
- setDoc: () => value && RichTextMenu.Instance.setHighlight(value),
+ checkResult: () => RichTextMenu.Instance?.fontHighlight,
+ setDoc: () => value && RichTextMenu.Instance?.setHighlight(value),
}],
['fontColor', {
checkResult: () => RichTextMenu.Instance?.fontColor,
- setDoc: () => value && RichTextMenu.Instance.setColor(value),
+ setDoc: () => value && RichTextMenu.Instance?.setColor(value),
}],
['alignment', {
- checkResult: () => RichTextMenu.Instance.textAlign,
- setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+ checkResult: () => RichTextMenu.Instance?.textAlign,
+ setDoc: () => value && editorView?.state ? RichTextMenu.Instance?.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
}],
['fontSize', {
checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
setDoc: () => {
if (typeof value === 'number') value = value.toString();
if (value && Number(value).toString() === value) value += 'px';
- RichTextMenu.Instance.setFontSize(value);
+ RichTextMenu.Instance?.setFontSize(value);
},
}],
]);
@@ -182,45 +182,45 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
});
type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
-type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
+type attrfuncs = [attrname, { checkResult: () => boolean; toggle?: () => any }];
ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
const textView = RichTextMenu.Instance?.TextView;
const editorView = textView?.EditorView;
// prettier-ignore
const alignments:attrfuncs[] = (['left','right','center','vcent'] as ("left"|"center"|"right"|"vcent")[]).map((where) =>
- [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance.textVcenter:
- (RichTextMenu.Instance.textAlign === where)):
+ [ where, { checkResult: () =>(editorView ? (where === 'vcent' ? RichTextMenu.Instance?.textVcenter ?? false:
+ (RichTextMenu.Instance?.textAlign === where)):
where === 'vcent' ? BoolCast(Doc.UserDoc()._layout_centered):
(Doc.UserDoc().textAlign ===where) ? true:false),
- toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance.vcenterToggle(editorView, editorView.dispatch):
- RichTextMenu.Instance.align(editorView, editorView.dispatch, where)):
+ toggle: () => (editorView?.state ? (where === 'vcent' ? RichTextMenu.Instance?.vcenterToggle(editorView, editorView.dispatch):
+ RichTextMenu.Instance?.align(editorView, editorView.dispatch, where)):
where === 'vcent' ? Doc.UserDoc()._layout_centered = !Doc.UserDoc()._layout_centered:
(Doc.UserDoc().textAlign = where))}]); // prettier-ignore
// prettier-ignore
const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
- [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
- toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
+ [ list, { checkResult: () => (editorView ? RichTextMenu.Instance?.getActiveListStyle() === list:false),
+ toggle: () => editorView?.state && RichTextMenu.Instance?.changeListType(list) }]);
// prettier-ignore
const attrs:attrfuncs[] = [
['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
['elide', { checkResult: () => false,
toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}],
- ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
+ ['noAutoLink',{ checkResult: () => ((editorView && RichTextMenu.Instance?.noAutoLink) ?? false),
toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
- ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
- ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
- ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
- toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
+ ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance?.bold??false : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance?.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
+ ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance?.italics ?? false : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance?.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
+ ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance?.underline ?? false: (Doc.UserDoc().textDecoration === 'underline') ? true:false),
+ toggle: editorView ? RichTextMenu.Instance?.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]
const map = new Map(attrs.concat(alignments).concat(listings));
if (checkResult) {
return map.get(charStyle)?.checkResult();
}
- undoable(() => map.get(charStyle)?.toggle(), 'toggle ' + charStyle)();
+ undoable(() => map.get(charStyle)?.toggle?.(), 'toggle ' + charStyle)();
});
export function checkInksToGroup() {
@@ -448,10 +448,10 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
*/
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
SelectionManager.Docs.map(doc => (doc._text_fontFamily = key));
- const editorView = RichTextMenu.Instance.TextView?.EditorView;
+ const editorView = RichTextMenu.Instance?.TextView?.EditorView;
if (checkResult) {
- return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
+ return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily);
}
- if (editorView) RichTextMenu.Instance.setFontFamily(key);
+ if (editorView) RichTextMenu.Instance?.setFontFamily(key);
else Doc.UserDoc().fontFamily = key;
});
diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss
index 74eeb014c..d79df4272 100644
--- a/src/client/views/nodes/formattedText/DashFieldView.scss
+++ b/src/client/views/nodes/formattedText/DashFieldView.scss
@@ -26,6 +26,7 @@
display: inline-block;
font-weight: normal;
background: rgba(0, 0, 0, 0.1);
+ cursor: default;
}
.dashFieldView-fieldSpan {
min-width: 8px;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index 03ff0436b..3dcc45c96 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -273,6 +273,7 @@ footnote::before {
height: 20px;
&::before {
content: '→';
+ cursor: default;
}
&:hover {
background: orange;
@@ -348,6 +349,7 @@ footnote::before {
touch-action: none;
span {
font-family: inherit;
+ background-color: inherit;
}
blockquote {
@@ -397,6 +399,7 @@ footnote::before {
font-family: inherit;
}
margin-left: 0;
+ background-color: inherit;
}
.decimal2-ol {
counter-reset: deci2;
@@ -406,6 +409,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2.1em;
+ background-color: inherit;
}
.decimal3-ol {
counter-reset: deci3;
@@ -415,6 +419,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2.85em;
+ background-color: inherit;
}
.decimal4-ol {
counter-reset: deci4;
@@ -424,6 +429,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 3.85em;
+ background-color: inherit;
}
.decimal5-ol {
counter-reset: deci5;
@@ -432,6 +438,7 @@ footnote::before {
font-family: inherit;
}
font-size: smaller;
+ background-color: inherit;
}
.decimal6-ol {
counter-reset: deci6;
@@ -440,6 +447,7 @@ footnote::before {
font-family: inherit;
}
font-size: smaller;
+ background-color: inherit;
}
.decimal7-ol {
counter-reset: deci7;
@@ -448,6 +456,7 @@ footnote::before {
font-family: inherit;
}
font-size: smaller;
+ background-color: inherit;
}
.multi1-ol {
@@ -458,6 +467,7 @@ footnote::before {
}
margin-left: 0;
padding-left: 1.2em;
+ background-color: inherit;
}
.multi2-ol {
counter-reset: multi2;
@@ -467,6 +477,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2em;
+ background-color: inherit;
}
.multi3-ol {
counter-reset: multi3;
@@ -476,6 +487,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 2.85em;
+ background-color: inherit;
}
.multi4-ol {
counter-reset: multi4;
@@ -485,6 +497,7 @@ footnote::before {
}
font-size: smaller;
padding-left: 3.85em;
+ background-color: inherit;
}
//.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " }
@@ -788,6 +801,7 @@ footnote::before {
height: 20px;
&::before {
content: '→';
+ cursor: default;
}
&:hover {
background: orange;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2e8444379..c2f3a6e4b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -291,7 +291,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
});
};
AnchorMenu.Instance.Highlight = undoable((color: string) => {
- this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+ this._editorView?.state && RichTextMenu.Instance?.setHighlight(color);
return undefined;
}, 'highlght text');
AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
@@ -1577,32 +1577,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
document.removeEventListener('pointerup', this.onSelectEnd);
};
onPointerUp = (e: React.PointerEvent): void => {
- const editor = this._editorView!;
- const state = editor?.state;
- if (!state || !editor || !this.ProseRef?.children[0].className.includes('-focused')) return;
- if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
- else if (this._props.isContentActive() && !e.button) {
- const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
- let xpos = pcords?.pos || 0;
- while (xpos > 0 && !state.doc.resolve(xpos).node()?.isTextblock) {
- xpos = xpos - 1;
- }
- let node: any;
- try {
- node = state.doc.nodeAt(xpos);
- } catch (e) {}
- if (node?.type !== schema.nodes.dashFieldView) {
- editor.dispatch(state.tr.setSelection(TextSelection.near(state.doc.resolve(xpos))));
- let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
- while (target && !target.dataset?.targethrefs) target = target.parentElement;
- FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
- } else if (node) {
- try {
- editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos))));
- } catch (e) {
- editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(xpos - 1))));
- }
- }
+ const state = this.EditorView?.state;
+ if (state && this.ProseRef?.children[0].className.includes('-focused') && this._props.isContentActive() && !e.button) {
+ if (!state.selection.empty && !(state.selection instanceof NodeSelection)) this.setupAnchorMenu();
+ let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
+ for (let target = e.target as any; target && !target.dataset?.targethrefs; target = target.parentElement);
+ while (target && !target.dataset?.targethrefs) target = target.parentElement;
+ FormattedTextBoxComment.update(this, this.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true');
}
};
@action
@@ -1788,7 +1769,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
this._editorView!.dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from)));
(document.activeElement as any).blur?.();
SelectionManager.DeselectAll();
- RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined);
+ RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined);
return;
case 'Enter':
this.insertTime();
@@ -1827,12 +1808,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0);
const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined;
if (children && !SnappingManager.IsDragging) {
- const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0'));
- const toHgt = (node: Element) => {
+ const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0;
+ const toNum = (val: string) => Number(val.replace('px', ''));
+ const toHgt = (node: Element): number => {
const { height, marginTop, marginBottom } = getComputedStyle(node);
- return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
+ const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height);
+ return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom));
};
- const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins);
+ const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children);
const scrollHeight = this.ProseRef && proseHeight;
if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) {
// if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation
diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
index 92cc65fc5..ec8879487 100644
--- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
+++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts
@@ -104,7 +104,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey
!wrapInList(schema.nodes.ordered_list)(newstate.state as any, (tx2: Transaction) => {
const tx25 = updateBullets(tx2, schema);
const ol_node = tx25.doc.nodeAt(range!.start)!;
- const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, { ...ol_node.attrs, ...(marks?.[0]?.type === schema.marks.pFontSize ? { fontSize: marks[0].attrs.fontSize } : {}) });
+ const tx3 = tx25.setNodeMarkup(range!.start, ol_node.type, ol_node.attrs, marks);
// when promoting to a list, assume list will format things so don't copy the stored marks.
marks && tx3.ensureMarks([...marks]);
marks && tx3.setStoredMarks([...marks]);
diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx
index 9b6eb8b8d..9c282a1d2 100644
--- a/src/client/views/nodes/formattedText/RichTextMenu.tsx
+++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx
@@ -27,7 +27,10 @@ const { toggleMark } = require('prosemirror-commands');
@observer
export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
- @observable static Instance: RichTextMenu;
+ static _instance: { menu: RichTextMenu | undefined } = observable({ menu: undefined });
+ static get Instance() {
+ return RichTextMenu._instance?.menu;
+ }
public overMenu: boolean = false; // kind of hacky way to prevent selects not being selectable
private _linkToRef = React.createRef<HTMLInputElement>();
@@ -67,7 +70,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
constructor(props: AntimodeMenuProps) {
super(props);
makeObservable(this);
- RichTextMenu.Instance = this;
+ RichTextMenu._instance.menu = this;
this.updateMenu(undefined, undefined, props, this.layoutDoc);
this._canFade = false;
this.Pinned = true;
@@ -131,11 +134,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return;
}
- // update active marks
- const activeMarks = this.getActiveMarksOnSelection();
- this.setActiveMarkButtons(activeMarks);
-
- // update active font family and size
+ this.setActiveMarkButtons(this.getActiveMarksOnSelection());
const active = this.getActiveFontStylesOnSelection();
const activeFamilies = active.activeFamilies;
const activeSizes = active.activeSizes;
@@ -164,15 +163,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
const fromRange = numberRange(state.selection.from).reverse();
const newPos = nodeOl ? fromRange.find(i => state.doc.nodeAt(i)?.type === state.schema.nodes.ordered_list) ?? state.selection.from : state.selection.from;
const node = (state.selection as NodeSelection).node ?? (newPos >= 0 ? state.doc.nodeAt(newPos) : undefined);
- if (node?.type === schema.nodes.ordered_list) {
- let attrs = node.attrs;
- if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family };
- if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: mark.attrs.fontSize };
- if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color };
- const tr = updateBullets(state.tr.setNodeMarkup(newPos, node.type, attrs), state.schema);
- dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to))));
- }
- {
+ if (node?.type === schema.nodes.ordered_list || node?.type === schema.nodes.list_item) {
+ const hasMark = node.marks.some(m => m.type === mark.type);
+ const otherMarks = node.marks.filter(m => m.type !== mark.type);
+ const addAnyway = node.marks.filter(m => m.type === mark.type && Object.keys(m.attrs).some(akey => m.attrs[akey] !== mark.attrs[akey]));
+ const markup = state.tr.setNodeMarkup(newPos, node.type, node.attrs, hasMark && !addAnyway ? otherMarks : [...otherMarks, mark]);
+ dispatch(updateBullets(markup, state.schema));
+ } else {
const state = this.view?.state;
const tr = this.view?.state.tr;
if (tr && state) {
@@ -225,11 +222,12 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
if (this.view && this.TextView?._props.rootSelected?.()) {
const state = this.view.state;
const pos = this.view.state.selection.$from;
- const marks: Mark[] = [...(state.storedMarks ?? [])];
+ var marks: Mark[] = [...(state.storedMarks ?? [])];
if (state.storedMarks !== null) {
} else if (state.selection.empty) {
- const ref_node = this.reference_node(pos);
- marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : []));
+ 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) => {
node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
@@ -256,41 +254,26 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
//finds all active marks on selection in given group
getActiveMarksOnSelection() {
- let activeMarks: MarkType[] = [];
- if (!this.view || !this.TextView?._props.rootSelected?.()) return activeMarks;
+ if (!this.view || !this.TextView?._props.rootSelected?.()) return [] as MarkType[];
- const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript];
- if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type);
- //current selection
- const { empty, ranges, $to } = this.view.state.selection as TextSelection;
const state = this.view.state;
- if (!empty) {
- activeMarks = markGroup.filter(mark => {
- const has = false;
- for (let i = 0; !has && i < ranges.length; i++) {
- return state.doc.rangeHasMark(ranges[i].$from.pos, ranges[i].$to.pos, mark);
- }
- return false;
- });
- } else {
- const pos = this.view.state.selection.$from;
- const ref_node: ProsNode | null = this.reference_node(pos);
- if (ref_node !== null && ref_node !== this.view.state.doc) {
- if (ref_node.isText) {
- } else {
- return [];
- }
- activeMarks = markGroup.filter(mark_type => {
- // if (mark_type === state.schema.marks.pFontSize) {
- // return mark.isINSet
- // ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name);
- // }
- const mark = state.schema.mark(mark_type);
- return mark.isInSet(ref_node.marks);
- });
+ var marks: Mark[] = [...(state.storedMarks ?? [])];
+ const pos = this.view.state.selection.$from;
+ if (state.storedMarks !== null) {
+ } 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) => {
+ node.marks?.filter(mark => !mark.isInSet(marks)).map(mark => marks.push(mark));
+ });
}
- return activeMarks;
+ 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 mark.isInSet(marks);
+ });
}
@action
@@ -418,7 +401,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// TODO: remove doesn't work
// remove all node type and apply the passed-in one to the selected text
changeListType = (mapStyle: string) => {
- const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle();
+ const active = this.view?.state && RichTextMenu.Instance?.getActiveListStyle();
const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle });
if (!this.view || nodeType?.attrs.mapStyle === '') return;
@@ -566,7 +549,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
// todo: add brushes to brushMap to save with a style name
onBrushNameKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
- RichTextMenu.Instance.brushMarks && RichTextMenu.Instance._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
+ RichTextMenu.Instance?.brushMarks && RichTextMenu.Instance?._brushMap.set(this._brushNameRef.current!.value, RichTextMenu.Instance.brushMarks);
this._brushNameRef.current!.style.background = 'lightGray';
}
};
@@ -574,7 +557,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
@action
clearBrush() {
- RichTextMenu.Instance.brushMarks = new Set();
+ RichTextMenu.Instance && (RichTextMenu.Instance.brushMarks = new Set());
}
@action
@@ -710,118 +693,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> {
}
};
- linkExtend($start: ResolvedPos, href: string) {
- const mark = this.view!.state.schema.marks.linkAnchor;
-
- let startIndex = $start.index();
- let endIndex = $start.indexAfter();
-
- while (startIndex > 0 && $start.parent.child(startIndex - 1).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) startIndex--;
- while (endIndex < $start.parent.childCount && $start.parent.child(endIndex).marks.filter(m => m.type === mark && m.attrs.allAnchors.find((item: { href: string }) => item.href === href)).length) endIndex++;
-
- let startPos = $start.start();
- let endPos = startPos;
- for (let i = 0; i < endIndex; i++) {
- const size = $start.parent.child(i).nodeSize;
- if (i < startIndex) startPos += size;
- endPos += size;
- }
- return { from: startPos, to: endPos };
- }
-
- reference_node(pos: ResolvedPos): ProsNode | null {
- if (!this.view) return null;
-
- let ref_node: ProsNode = this.view.state.doc;
- if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
- ref_node = pos.nodeBefore;
- }
- if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
- if (!pos.nodeBefore || this.view.state.selection.$from.pos !== this.view.state.selection.$to.pos) {
- ref_node = pos.nodeAfter;
- }
- }
- if (!ref_node && pos.pos > 0) {
- let skip = false;
- for (let i: number = pos.pos - 1; i > 0; i--) {
- this.view.state.doc.nodesBetween(i, pos.pos, (node: ProsNode) => {
- if (node.isLeaf && !skip) {
- ref_node = node;
- skip = true;
- }
- });
- }
- }
- if (!ref_node.isLeaf && ref_node.childCount > 0) {
- ref_node = ref_node.child(0);
- }
- return ref_node;
- }
-
render() {
return null;
- // TraceMobx();
- // const row1 = <div className="antimodeMenu-row" key="row 1" style={{ display: this.collapsed ? "none" : undefined }}>{[
- // //!this.collapsed ? this.getDragger() : (null),
- // // !this.Pinned ? (null) : <div key="frag1"> {[
- // // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- // // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- // // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- // // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- // // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- // // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- // // <div className="richTextMenu-divider" key="divider" />
- // // ]}</div>,
- // this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)),
- // this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)),
- // this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)),
- // this.createButton("strikethrough", "Strikethrough", this.strikethroughActive, toggleMark(schema.marks.strikethrough)),
- // this.createButton("superscript", "Superscript", this.superscriptActive, toggleMark(schema.marks.superscript)),
- // this.createButton("subscript", "Subscript", this.subscriptActive, toggleMark(schema.marks.subscript)),
- // this.createColorButton(),
- // this.createHighlighterButton(),
- // this.createLinkButton(),
- // this.createBrushButton(),
- // <div className="collectionMenu-divider" key="divider 2" />,
- // this.createButton("align-left", "Align Left", this.activeAlignment === "left", this.alignLeft),
- // this.createButton("align-center", "Align Center", this.activeAlignment === "center", this.alignCenter),
- // this.createButton("align-right", "Align Right", this.activeAlignment === "right", this.alignRight),
- // this.createButton("indent", "Inset More", undefined, this.insetParagraph),
- // this.createButton("outdent", "Inset Less", undefined, this.outsetParagraph),
- // this.createButton("hand-point-left", "Hanging Indent", undefined, this.hangingIndentParagraph),
- // this.createButton("hand-point-right", "Indent", undefined, this.indentParagraph),
- // ]}</div>;
-
- // const row2 = <div className="antimodeMenu-row row-2" key="row2">
- // {this.collapsed ? this.getDragger() : (null)}
- // <div key="row 2" style={{ display: this.collapsed ? "none" : undefined }}>
- // <div className="collectionMenu-divider" key="divider 3" />
- // {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => {
- // this.activeFontSize = val;
- // SelectionManager.Views.map(dv => dv.Document._text_fontSize = val);
- // })),
- // this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => {
- // this.activeFontFamily = val;
- // SelectionManager.Views.map(dv => dv.Document._text_fontFamily = val);
- // })),
- // <div className="collectionMenu-divider" key="divider 4" />,
- // this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})),
- // this.createButton("sort-amount-down", "Summarize", undefined, this.insertSummarizer),
- // this.createButton("quote-left", "Blockquote", undefined, this.insertBlockquote),
- // this.createButton("minus", "Horizontal Rule", undefined, this.insertHorizontalRule)
- // ]}
- // </div>
- // {/* <div key="collapser">
- // {<div key="collapser">
- // <button className="antimodeMenu-button" key="collapse menu" title="Collapse menu" onClick={this.toggleCollapse} style={{ backgroundColor: this.collapsed ? "#121212" : "", width: 25 }}>
- // <FontAwesomeIcon icon="chevron-left" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.3s", transform: `rotate(${this.collapsed ? 180 : 0}deg)` }} />
- // </button>
- // </div> }
- // <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}>
- // <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} />
- // </button>
- // </div> */}
- // </div>;
}
}
diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts
index e8cf9e992..42665830f 100644
--- a/src/client/views/nodes/formattedText/RichTextRules.ts
+++ b/src/client/views/nodes/formattedText/RichTextRules.ts
@@ -246,7 +246,7 @@ export class RichTextRules {
// activate a style by name using prefix '%<color name>'
new InputRule(new RegExp(/%[a-zA-Z_]+$/), (state, match, start, end) => {
const color = match[0].substring(1, match[0].length);
- const marks = RichTextMenu.Instance._brushMap.get(color);
+ const marks = RichTextMenu.Instance?._brushMap.get(color);
if (
DocListCast((Doc.UserDoc().template_notes as Doc).data)
@@ -367,7 +367,7 @@ export class RichTextRules {
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))));
- RichTextMenu.Instance.elideSelection(this.TextBox.EditorView?.state, true);
+ RichTextMenu.Instance?.elideSelection(this.TextBox.EditorView?.state, true);
}
count++;
});
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index cab3a6ef5..ceafc3ba1 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -24,6 +24,7 @@ export const nodes: { [index: string]: NodeSpec } = {
// :: NodeSpec The top level document node.
doc: {
content: 'block+',
+ marks: '_',
},
paragraph: ParagraphNodeSpec,
@@ -331,12 +332,10 @@ export const nodes: { [index: string]: NodeSpec } = {
...orderedList,
content: 'list_item+',
group: 'block',
+ marks: '_',
attrs: {
bulletStyle: { default: 0 },
- mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet"
- fontColor: { default: 'inherit' },
- fontSize: { default: undefined },
- fontFamily: { default: undefined },
+ mapStyle: { default: 'decimal' }, // "decimal", "multi", "bullet",
visibility: { default: true },
indent: { default: undefined },
},
@@ -376,9 +375,10 @@ export const nodes: { [index: string]: NodeSpec } = {
],
toDOM(node: Node) {
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
- const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : '';
- const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : '';
- const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : '';
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : '';
if (node.attrs.mapStyle === 'bullet') {
return [
@@ -386,7 +386,7 @@ export const nodes: { [index: string]: NodeSpec } = {
{
'data-mapStyle': node.attrs.mapStyle,
'data-bulletStyle': node.attrs.bulletStyle,
- style: `${fsize} ${ffam} ${fcol} ${marg}`,
+ style: `${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
},
0,
];
@@ -398,7 +398,7 @@ export const nodes: { [index: string]: NodeSpec } = {
class: `${map}-ol`,
'data-mapStyle': node.attrs.mapStyle,
'data-bulletStyle': node.attrs.bulletStyle,
- style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}`,
+ style: `list-style: none; ${fhigh} ${fsize} ${ffam} ${fcol} ${marg}`,
},
0,
]
@@ -422,16 +422,22 @@ export const nodes: { [index: string]: NodeSpec } = {
},
},
],
- toDOM(node: any) {
+ toDOM(node: Node) {
+ const fhigh = (found => (found ? `background-color: ${found};` : ''))(node.marks.find(m => m.type.name === 'marker')?.attrs.highlight);
+ const fsize = (found => (found ? `font-size: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontSize')?.attrs.fontSize);
+ const ffam = (found => (found ? `font-family: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontFamily')?.attrs.family);
+ const fcol = (found => (found ? `color: ${found};` : ''))(node.marks.find(m => m.type.name === 'pFontColor')?.attrs.color);
const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : '';
return [
'li',
- { class: `${map}`, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
+ { class: `${map}`, style: `${fhigh} ${fsize} ${ffam} ${fcol} `, 'data-mapStyle': node.attrs.mapStyle, 'data-bulletStyle': node.attrs.bulletStyle },
node.attrs.visibility
? 0
: [
'span',
- { style: `position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre` },
+ {
+ style: `${fhigh} ${fsize} ${ffam} ${fcol} position: relative; width: 100%; height: 1.5em; overflow: hidden; display: ${node.attrs.mapStyle !== 'bullet' ? 'inline-block' : 'list-item'}; text-overflow: ellipsis; white-space: pre`,
+ },
`${node.firstChild?.textContent}...`,
],
];