diff options
Diffstat (limited to 'src/client/util/TooltipTextMenu.tsx')
-rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 360 |
1 files changed, 225 insertions, 135 deletions
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index cbabadd64..9f8d0b2f6 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,53 +1,37 @@ -import { action, IReactionDisposer, reaction } from "mobx"; -import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css -import { baseKeymap, lift, deleteSelection } from "prosemirror-commands"; -import { history, redo, undo } from "prosemirror-history"; -import { keymap } from "prosemirror-keymap"; -import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state"; +import { action } from "mobx"; +import { Dropdown, MenuItem, icons, } from "prosemirror-menu"; //no import css +import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { schema } from "./RichTextSchema"; -import { Schema, NodeType, MarkType, Mark } from "prosemirror-model"; -import React = require("react"); +import { Schema, NodeType, MarkType, Mark, ResolvedPos } from "prosemirror-model"; +import { Node as ProsNode } from "prosemirror-model"; import "./TooltipTextMenu.scss"; -const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands"); +const { toggleMark, setBlockType } = require("prosemirror-commands"); import { library } from '@fortawesome/fontawesome-svg-core'; -import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list'; -import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform'; -import { - faListUl, faGrinTongueSquint, -} from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { wrapInList, liftListItem, } from 'prosemirror-schema-list'; +import { faListUl } from '@fortawesome/free-solid-svg-icons'; import { FieldViewProps } from "../views/nodes/FieldView"; -import { throwStatement } from "babel-types"; const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); -import { View } from "@react-pdf/renderer"; import { DragManager } from "./DragManager"; import { Doc, Opt, Field } from "../../new_fields/Doc"; import { DocServer } from "../DocServer"; -import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DocumentManager } from "./DocumentManager"; import { Id } from "../../new_fields/FieldSymbols"; -import { Utils } from "../../Utils"; -// import { wrap } from "module"; - -const SVG = "http://www.w3.org/2000/svg"; +import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { public tooltip: HTMLElement; - private num_icons = 0; private view: EditorView; private fontStyles: MarkType[]; private fontSizes: MarkType[]; private listTypes: NodeType[]; - private editorProps: FieldViewProps; - private state: EditorState; + private editorProps: FieldViewProps & FormattedTextBoxProps; private fontSizeToNum: Map<MarkType, number>; private fontStylesToName: Map<MarkType, string>; private listTypeToIcon: Map<NodeType, string>; - private fontSizeIndicator: HTMLSpanElement = document.createElement("span"); private link: HTMLAnchorElement; private linkEditor?: HTMLDivElement; @@ -58,15 +42,22 @@ export class TooltipTextMenu { private fontStyleDom?: Node; private listTypeBtnDom?: Node; - constructor(view: EditorView, editorProps: FieldViewProps) { + private _activeMarks: Mark[] = []; + + private _collapseBtn?: MenuItem; + + constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) { this.view = view; - this.state = view.state; this.editorProps = editorProps; this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; + // this.createCollapse(); + // if (this._collapseBtn) { + // this.tooltip.appendChild(this._collapseBtn.render(this.view).dom); + // } //add the div which is the tooltip - view.dom.parentNode!.parentNode!.appendChild(this.tooltip); + //view.dom.parentNode!.parentNode!.appendChild(this.tooltip); //add additional icons library.add(faListUl); @@ -78,7 +69,7 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, - { command: deleteSelection, dom: this.icon("C", 'collapse', 'Collapse') } + { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") }, // { command: lift, dom: this.icon("<", "lift") }, @@ -120,6 +111,7 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p32, 32); this.fontSizeToNum.set(schema.marks.p48, 48); this.fontSizeToNum.set(schema.marks.p72, 72); + //this.fontSizeToNum.set(schema.marks.pFontSize,schema.marks.pFontSize.) this.fontSizes = Array.from(this.fontSizeToNum.keys()); //list types @@ -131,11 +123,23 @@ export class TooltipTextMenu { this.link = document.createElement("a"); this.link.target = "_blank"; this.link.style.color = "white"; - this.tooltip.appendChild(this.link); + //this.tooltip.appendChild(this.link); this.tooltip.appendChild(this.createLink().render(this.view).dom); + this.tooltip.appendChild(this.createStar().render(this.view).dom); + + + + this.updateListItemDropdown(":", this.listTypeBtnDom); + this.update(view, undefined); + + //view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode); + + // quick and dirty null check + const outer_div = this.editorProps.outer_div; + outer_div && outer_div(this.tooltip); } //label of dropdown will change to given label @@ -149,12 +153,15 @@ export class TooltipTextMenu { fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); }); - if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); } - this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), { + let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { label: label, css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" }) as MenuItem).render(this.view).dom; - this.tooltip.appendChild(this.fontSizeDom); + if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); } + else { + this.tooltip.appendChild(newfontSizeDom); + } + this.fontSizeDom = newfontSizeDom; } //label of dropdown will change to given label @@ -168,13 +175,16 @@ export class TooltipTextMenu { fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); }); - if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); } - this.fontStyleDom = (new Dropdown(cut(fontBtns), { + let newfontStyleDom = (new Dropdown(cut(fontBtns), { label: label, css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" }) as MenuItem).render(this.view).dom; + if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); } + else { + this.tooltip.appendChild(newfontStyleDom); + } + this.fontStyleDom = newfontStyleDom; - this.tooltip.appendChild(this.fontStyleDom); } updateLinkMenu() { @@ -189,13 +199,13 @@ export class TooltipTextMenu { this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; this.linkText.style.overflow = "hidden"; - this.linkText.style.color = "black"; + this.linkText.style.color = "white"; this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; let linkBtn = document.createElement("div"); linkBtn.textContent = ">>"; linkBtn.style.width = "10px"; linkBtn.style.height = "10px"; - linkBtn.style.color = "black"; + linkBtn.style.color = "white"; linkBtn.style.cssFloat = "left"; linkBtn.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; @@ -209,7 +219,7 @@ export class TooltipTextMenu { if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); } - else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f); + else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, undefined); } })); } @@ -239,26 +249,10 @@ export class TooltipTextMenu { hideSource: false }); }; - this.linkEditor.appendChild(this.linkDrag); - this.linkEditor.appendChild(this.linkText); - this.linkEditor.appendChild(linkBtn); - this.tooltip.appendChild(this.linkEditor); - - let starButton = document.createElement("span"); - // starButton.style.width = '10px'; - // starButton.style.height = '10px'; - starButton.style.marginLeft = '10px'; - starButton.textContent = "Summarize"; - starButton.style.color = 'black'; - starButton.style.height = '20px'; - starButton.style.backgroundColor = 'white'; - starButton.style.textAlign = 'center'; - starButton.onclick = () => { - let state = this.view.state; - this.insertStar(state, this.view.dispatch); - }; - - this.tooltip.appendChild(starButton); + // this.linkEditor.appendChild(this.linkDrag); + // this.linkEditor.appendChild(this.linkText); + // this.linkEditor.appendChild(linkBtn); + //this.tooltip.appendChild(this.linkEditor); } let node = this.view.state.selection.$from.nodeAfter; @@ -284,18 +278,17 @@ export class TooltipTextMenu { link = node && node.marks.find(m => m.type.name === "link"); } - insertStar(state: EditorState<any>, dispatch: any) { - console.log("creating star..."); - let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from }); + public static insertStar(state: EditorState<any>, dispatch: any) { + let newNode = schema.nodes.star.create({ visibility: false, text: state.selection.content(), textslice: state.selection.content().toJSON(), textlen: state.selection.to - state.selection.from }); if (dispatch) { - console.log(newNode.attrs.oldtext.toString()); + //console.log(newNode.attrs.text.toString()); dispatch(state.tr.replaceSelectionWith(newNode)); } return true; } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown - updateListItemDropdown(label: string, listTypeBtn: Node) { + updateListItemDropdown(label: string, listTypeBtn: any) { //remove old btn if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); } @@ -318,8 +311,8 @@ export class TooltipTextMenu { } //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text - changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) { - let { empty, $cursor, ranges } = view.state.selection as TextSelection; + changeToMarkInGroup = (markType: MarkType, view: EditorView, fontMarks: MarkType[]) => { + let { $cursor, ranges } = view.state.selection as TextSelection; let state = view.state; let dispatch = view.dispatch; @@ -331,25 +324,34 @@ export class TooltipTextMenu { dispatch(state.tr.removeStoredMark(type)); } } else { - let has = false, tr = state.tr; + let has = false; for (let i = 0; !has && i < ranges.length; i++) { let { $from, $to } = ranges[i]; has = state.doc.rangeHasMark($from.pos, $to.pos, type); } for (let i of ranges) { - let { $from, $to } = i; if (has) { toggleMark(type)(view.state, view.dispatch, view); } } } } - }); //actually apply font + }); + // fontsize + if (markType.name[0] === 'p') { + let size = this.fontSizeToNum.get(markType); + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + } + else { + let fontName = this.fontStylesToName.get(markType); + if (fontName) { this.updateFontStyleDropdown(fontName); } + } + //actually apply font return toggleMark(markType)(view.state, view.dispatch, view); } //remove all node typeand apply the passed-in one to the selected text - changeToNodeType(nodeType: NodeType | undefined, view: EditorView, allNodes: NodeType[]) { + changeToNodeType(nodeType: NodeType | undefined, view: EditorView) { //remove old liftListItem(schema.nodes.list_item)(view.state, view.dispatch); if (nodeType) { //add new @@ -366,13 +368,69 @@ export class TooltipTextMenu { execEvent: "", class: "menuicon", css: css, - enable(state) { return true; }, + enable() { return true; }, run() { changeToMarkInGroup(markType, view, groupMarks); } }); } + createStar() { + return new MenuItem({ + title: "Summarize", + label: "Summarize", + icon: icons.join, + css: "color:white;", + class: "summarize", + execEvent: "", + run: (state, dispatch) => { + TooltipTextMenu.insertStar(state, dispatch); + } + + }); + } + + createCollapse() { + this._collapseBtn = new MenuItem({ + title: "Collapse", + //label: "Collapse", + icon: icons.join, + execEvent: "", + css: "color:white;", + class: "summarize", + run: () => { + this.collapseToolTip(); + } + }); + } + + collapseToolTip() { + if (this._collapseBtn) { + if (this._collapseBtn.spec.title === "Collapse") { + // const newcollapseBtn = new MenuItem({ + // title: "Expand", + // icon: icons.join, + // execEvent: "", + // css: "color:white;", + // class: "summarize", + // run: (state, dispatch, view) => { + // this.collapseToolTip(); + // } + // }); + // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); + // this._collapseBtn = newcollapseBtn; + this.tooltip.style.width = "30px"; + this._collapseBtn.spec.title = "Expand"; + this._collapseBtn.render(this.view); + } + else { + this._collapseBtn.spec.title = "Collapse"; + this.tooltip.style.width = "550px"; + this._collapseBtn.render(this.view); + } + } + } + createLink() { let markType = schema.marks.link; return new MenuItem({ @@ -419,7 +477,7 @@ export class TooltipTextMenu { execEvent: "", class: "menuicon", css: css, - enable(state) { return true; }, + enable() { return true; }, run() { changeToNodeInGroup(nodeType, view, groupNodes); } @@ -438,7 +496,7 @@ export class TooltipTextMenu { span.className = name + " menuicon"; span.title = title; span.textContent = text; - span.style.color = "black"; + span.style.color = "white"; return span; } @@ -502,90 +560,122 @@ export class TooltipTextMenu { // Hide the tooltip if the selection is empty if (state.selection.empty) { - this.tooltip.style.display = "none"; - return; + //this.tooltip.style.display = "none"; + //return; } - let linksInSelection = this.activeMarksOnSelection([schema.marks.link]); - if (linksInSelection.length > 0) { - let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs; - this.link.href = attributes.href; - this.link.textContent = attributes.title; - this.link.style.visibility = "visible"; - } else this.link.style.visibility = "hidden"; - - // Otherwise, reposition it and update its content - this.tooltip.style.display = ""; - let { from, to } = state.selection; - let start = view.coordsAtPos(from), end = view.coordsAtPos(to); - // The box in which the tooltip is positioned, to use as base - let box = this.tooltip.offsetParent!.getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px"; - let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale; - let mid = Math.min(start.left, end.left) + width; - - //this.tooltip.style.width = 225 + "px"; - this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px"; - this.tooltip.style.top = "-100px"; - //this.tooltip.style.height = "100px"; - - // let transform = this.editorProps.ScreenToLocalTransform(); - // this.tooltip.style.width = `${225 / transform.Scale}px`; - // Utils //UPDATE LIST ITEM DROPDOWN - this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!); //UPDATE FONT STYLE DROPDOWN let activeStyles = this.activeMarksOnSelection(this.fontStyles); - if (activeStyles.length === 1) { - // if we want to update something somewhere with active font name - let fontName = this.fontStylesToName.get(activeStyles[0]); - if (fontName) { this.updateFontStyleDropdown(fontName); } - } else if (activeStyles.length === 0) { - //crimson on default - this.updateFontStyleDropdown("Crimson Text"); - } else { - this.updateFontStyleDropdown("Various"); + if (activeStyles !== undefined) { + // activeStyles.forEach((markType) => { + // this._activeMarks.push(this.view.state.schema.mark(markType)); + // }); + if (activeStyles.length === 1) { + // if we want to update something somewhere with active font name + let fontName = this.fontStylesToName.get(activeStyles[0]); + if (fontName) { this.updateFontStyleDropdown(fontName); } + } else if (activeStyles.length === 0) { + //crimson on default + this.updateFontStyleDropdown("Crimson Text"); + } else { + this.updateFontStyleDropdown("Various"); + } } //UPDATE FONT SIZE DROPDOWN let activeSizes = this.activeMarksOnSelection(this.fontSizes); - if (activeSizes.length === 1) { //if there's only one active font size - let size = this.fontSizeToNum.get(activeSizes[0]); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - } else if (activeSizes.length === 0) { - //should be 14 on default - this.updateFontSizeDropdown("14 pt"); - } else { //multiple font sizes selected - this.updateFontSizeDropdown("Various"); + if (activeSizes !== undefined) { + if (activeSizes.length === 1) { //if there's only one active font size + // activeSizes.forEach((markType) => { + // this._activeMarks.push(this.view.state.schema.mark(markType)); + // }); + let size = this.fontSizeToNum.get(activeSizes[0]); + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + } else if (activeSizes.length === 0) { + //should be 14 on default + this.updateFontSizeDropdown("14 pt"); + } else { //multiple font sizes selected + this.updateFontSizeDropdown("Various"); + } } - + this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); this.updateLinkMenu(); } //finds all active marks on selection in given group activeMarksOnSelection(markGroup: MarkType[]) { //current selection - let { empty, $cursor, ranges } = this.view.state.selection as TextSelection; + let { empty, ranges } = this.view.state.selection as TextSelection; let state = this.view.state; let dispatch = this.view.dispatch; - - let activeMarks = markGroup.filter(mark => { - if (dispatch) { - let has = false, tr = state.tr; - for (let i = 0; !has && i < ranges.length; i++) { - let { $from, $to } = ranges[i]; - return state.doc.rangeHasMark($from.pos, $to.pos, mark); + let activeMarks: MarkType[]; + if (!empty) { + activeMarks = markGroup.filter(mark => { + if (dispatch) { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + return state.doc.rangeHasMark($from.pos, $to.pos, mark); + } + } + return false; + }); + } + else { + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node !== null && ref_node !== this.view.state.doc) { + if (ref_node.isText) { } + else { + return []; + } + + this._activeMarks = ref_node.marks; + + activeMarks = markGroup.filter(mark_type => { + if (dispatch) { + let mark = state.schema.mark(mark_type); + return ref_node.marks.includes(mark); + } + return false; + }); } - return false; - }); + else { + return []; + } + + } return activeMarks; } - destroy() { this.tooltip.remove(); } + reference_node(pos: ResolvedPos<any>): ProsNode { + let ref_node: ProsNode = this.view.state.doc; + if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) { + ref_node = pos.nodeAfter; + } + else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) { + ref_node = pos.nodeBefore; + } + else if (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; + } + + }); + } + } + return ref_node; + } + + destroy() { + this.tooltip.remove(); + } } |