diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/RichTextMenu.tsx | 215 |
1 files changed, 134 insertions, 81 deletions
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx index 7e20d126c..6b6a2620e 100644 --- a/src/client/util/RichTextMenu.tsx +++ b/src/client/util/RichTextMenu.tsx @@ -8,16 +8,17 @@ import { EditorView } from "prosemirror-view"; import { EditorState, NodeSelection } from "prosemirror-state"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; -import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript } from "@fortawesome/free-solid-svg-icons"; +import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent } from "@fortawesome/free-solid-svg-icons"; import { MenuItem, Dropdown } from "prosemirror-menu"; import { updateBullets } from "./ProsemirrorExampleTransfer"; import { FieldViewProps } from "../views/nodes/FieldView"; import { NumCast } from "../../new_fields/Types"; import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; import { unimplementedFunction } from "../../Utils"; +import { wrapInList } from "prosemirror-schema-list"; const { toggleMark, setBlockType } = require("prosemirror-commands"); -library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript); +library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent); @observer export default class RichTextMenu extends AntimodeMenu { @@ -26,10 +27,9 @@ export default class RichTextMenu extends AntimodeMenu { private view?: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private _marksToDoms: Map<Mark, JSX.Element> = new Map(); - @observable private activeFontSize: string = ""; @observable private activeFontFamily: string = ""; + @observable private activeListType: string = ""; constructor(props: Readonly<{}>) { super(props); @@ -41,12 +41,7 @@ export default class RichTextMenu extends AntimodeMenu { this.view = view; } - // update() { - // console.log("update"); - // } - update(view: EditorView, lastState: EditorState | undefined) { - console.log("update"); this.updateFromDash(view, lastState, this.editorProps); } @@ -58,7 +53,6 @@ export default class RichTextMenu extends AntimodeMenu { } this.view = view; const state = view.state; - console.log("update from dash"); // DocumentDecorations.Instance.showTextBar(); props && (this.editorProps = props); // // Don't do anything if the document/selection didn't change @@ -84,14 +78,57 @@ export default class RichTextMenu extends AntimodeMenu { // this.update_mark_doms(); } + setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => { + if (mark) { + const node = (state.selection as NodeSelection).node; + if (node?.type === schema.nodes.ordered_list) { + let attrs = node.attrs; + if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; + if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; + const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); + dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); + } else { + toggleMark(mark.type, mark.attrs)(state, (tx: any) => { + const { from, $from, to, empty } = tx.selection; + if (!tx.doc.rangeHasMark(from, to, mark.type)) { + toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); + } else dispatch(tx); + }); + } + } + } + + // finds font sizes and families in selection + getActiveFontStylesOnSelection() { + if (!this.view) return; + + const activeFamilies: string[] = []; + const activeSizes: string[] = []; + const state = this.view.state; + const pos = this.view.state.selection.$from; + const ref_node = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => { + m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); + m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); + }); + } + + let styles = new Map<String, String[]>(); + styles.set("families", activeFamilies); + styles.set("sizes", activeSizes); + return styles; + } destroy() { console.log("destroy"); } - createButton(faIcon: string, title: string, command: any) { + createButton(faIcon: string, title: string, command?: any, onclick?: any) { const self = this; function onClick(e: React.PointerEvent) { + console.log("clicked button"); // dom.addEventListener("pointerdown", e => { e.preventDefault(); self.view && self.view.focus(); @@ -100,7 +137,8 @@ export default class RichTextMenu extends AntimodeMenu { // command(this.view.state, this.view.dispatch, this.view); // } // }); - self.view && command(self.view!.state, self.view!.dispatch, self.view); + self.view && command && command(self.view!.state, self.view!.dispatch, self.view); + self.view && onclick && onclick(self.view!.state, self.view!.dispatch, self.view); } return ( @@ -131,48 +169,29 @@ export default class RichTextMenu extends AntimodeMenu { }); } return <select onChange={e => onChange(e.target.value)}>{items}</select>; - - // let items: MenuItem[] = []; - // options.forEach(({ mark, title, label, command }) => { - // const self = this; - // function onSelect() { - // self.view && command(mark, self.view); - // } - // // this.createMarksOption("Set font size", String(mark.attrs.fontSize), onSelect) - // items.push( - // new MenuItem({ - // title: title, - // label: label, - // execEvent: "", - // class: "dropdown-item", - // css: "", - // run() { onSelect(); } - // }) - // ); - // }); - - // return <div>{(new Dropdown(items, { label: label }) as MenuItem).render(this.view!).dom}</div>; } - setMark = (mark: Mark, state: EditorState<any>, dispatch: any) => { - if (mark) { - const node = (state.selection as NodeSelection).node; - if (node?.type === schema.nodes.ordered_list) { - let attrs = node.attrs; - if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; - if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; - if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; - const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); - dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); - } else { - toggleMark(mark.type, mark.attrs)(state, (tx: any) => { - const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { - toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); - } else dispatch(tx); - }); + createNodesDropdown(activeOption: string, options: { node: NodeType | any | null, title: string, label: string, command: (node: NodeType | any) => void, hidden?: boolean }[]): JSX.Element { + let items = options.map(({ title, label, hidden }) => { + if (hidden) { + return label === activeOption ? + <option value={label} title={title} selected hidden>{label}</option> : + <option value={label} title={title} hidden>{label}</option>; } + return label === activeOption ? + <option value={label} title={title} selected>{label}</option> : + <option value={label} title={title}>{label}</option>; + }); + + const self = this; + function onChange(val: string) { + options.forEach(({ label, node, command }) => { + if (val === label) { + self.view && node && command(node); + } + }); } + return <select onChange={e => onChange(e.target.value)}>{items}</select>; } changeFontSize = (mark: Mark, view: EditorView) => { @@ -201,40 +220,44 @@ export default class RichTextMenu extends AntimodeMenu { this.setMark(view.state.schema.marks.pFontFamily.create({ family: fontName }), view.state, view.dispatch); } - // finds font sizes and families in selection - getActiveFontStylesOnSelection() { + // TODO: remove doesn't work + //remove all node type and apply the passed-in one to the selected text + changeListType = (nodeType: NodeType | undefined) => { + console.log("change the list type "); if (!this.view) return; - - const activeFamilies: string[] = []; - const activeSizes: string[] = []; - const state = this.view.state; - const pos = this.view.state.selection.$from; - const ref_node = this.reference_node(pos); - if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { - ref_node.marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontSize && activeSizes.push(String(m.attrs.fontSize) + "pt"); - }); + console.log("change the list type has view"); + + if (nodeType === schema.nodes.bullet_list) { + wrapInList(nodeType)(this.view.state, this.view.dispatch); + } else { + const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view!.dispatch(tx2); + })) { + const tx2 = this.view.state.tr; + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view.dispatch(tx3); + } } - - let styles = new Map<String, String[]>(); - styles.set("families", activeFamilies); - styles.set("sizes", activeSizes); - return styles; } - // changeToFontSize = (mark: Mark, view: EditorView) => { - // const size = mark.attrs.fontSize; - // if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - // if (this.editorProps) { - // const ruleProvider = this.editorProps.ruleProvider; - // const heading = NumCast(this.editorProps.Document.heading); - // if (ruleProvider && heading) { - // ruleProvider["ruleSize_" + heading] = size; - // } - // } - // this.setMark(view.state.schema.marks.pFontSize.create({ fontSize: size }), view.state, view.dispatch); - // } + insertSummarizer(state: EditorState<any>, dispatch: any) { + if (state.selection.empty) return false; + const mark = state.schema.marks.summarize.create(); + const tr = state.tr; + 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() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } reference_node(pos: ResolvedPos<any>): ProsNode | null { if (!this.view) return null; @@ -265,7 +288,6 @@ export default class RichTextMenu extends AntimodeMenu { } render() { - console.log("render"); const fontSizeOptions = [ { mark: schema.marks.pFontSize.create({ fontSize: 7 }), title: "Set font size", label: "7pt", command: this.changeFontSize }, { mark: schema.marks.pFontSize.create({ fontSize: 8 }), title: "Set font size", label: "8pt", command: this.changeFontSize }, @@ -296,6 +318,35 @@ export default class RichTextMenu extends AntimodeMenu { { mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true }, ] + // //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown + // updateListItemDropdown(label: string, listTypeBtn: any) { + // //remove old btn + // if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); } + + // //Make a dropdown of all list types + // const toAdd: MenuItem[] = []; + // this.listTypeToIcon.forEach((icon, type) => { + // toAdd.push(this.dropdownBulletBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeBulletType)); + // }); + // //option to remove the list formatting + // toAdd.push(this.dropdownBulletBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeBulletType)); + + // listTypeBtn = (new Dropdown(toAdd, { label: label, css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom; + + // //add this new button and return it + // this.tooltip.appendChild(listTypeBtn); + // return listTypeBtn; + // } + + const listTypeOptions = [ + { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, + { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType }, + { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, + ] + + // options: { node: NodeType | null, title: string, label: string, command: (node: NodeType) => void, hidden ?: boolean } [] + const buttons = [ this.createButton("bold", "Bold", toggleMark(schema.marks.strong)), this.createButton("italic", "Italic", toggleMark(schema.marks.em)), @@ -303,8 +354,10 @@ export default class RichTextMenu extends AntimodeMenu { this.createButton("strikethrough", "Strikethrough", toggleMark(schema.marks.strikethrough)), this.createButton("superscript", "Superscript", toggleMark(schema.marks.superscript)), this.createButton("subscript", "Subscript", toggleMark(schema.marks.subscript)), + this.createButton("indent", "Summarize", undefined, this.insertSummarizer), this.createMarksDropdown(this.activeFontSize, fontSizeOptions), this.createMarksDropdown(this.activeFontFamily, fontFamilyOptions), + this.createNodesDropdown(this.activeListType, listTypeOptions), ]; // this._marksToDoms = new Map(); |