aboutsummaryrefslogtreecommitdiff
path: root/src/client/util/TooltipTextMenu.tsx
diff options
context:
space:
mode:
authorMohammad Amoush <mohammad_amoush@brown.edu>2019-07-16 18:03:12 -0400
committerMohammad Amoush <mohammad_amoush@brown.edu>2019-07-16 18:03:12 -0400
commit1cedadbdf01c392ca9910e3ca18f3875d9a86fed (patch)
tree602608ba06b997cd3144395640e404a01f666291 /src/client/util/TooltipTextMenu.tsx
parentf70b95879e87a6bb61aaae5de29747d9474623a7 (diff)
parentf18be9418b9237acd847eaf71adc034226c54695 (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into youtube-api-muhammed
Diffstat (limited to 'src/client/util/TooltipTextMenu.tsx')
-rw-r--r--src/client/util/TooltipTextMenu.tsx498
1 files changed, 376 insertions, 122 deletions
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 34785446b..f4579fc51 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,51 +1,38 @@
-import { action, IReactionDisposer, reaction } from "mobx";
-import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
-import { baseKeymap, lift } 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,
-} 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";
-import { View } from "@react-pdf/renderer";
+const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
import { DragManager } from "./DragManager";
import { Doc, Opt, Field } from "../../new_fields/Doc";
-import { Utils } from "../northstar/utils/Utils";
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";
-
-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 linkEditor?: HTMLDivElement;
private linkText?: HTMLDivElement;
private linkDrag?: HTMLImageElement;
@@ -54,26 +41,35 @@ 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.dragElement(this.tooltip);
+ // 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);
//add the buttons to the tooltip
let items = [
- { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
- { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") },
- { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") },
- { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
- { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
- { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
+ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") },
+ { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") },
+ { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") },
+ { 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: 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") },
@@ -86,10 +82,14 @@ export class TooltipTextMenu {
dom.addEventListener("pointerdown", e => {
e.preventDefault();
view.focus();
- command(view.state, view.dispatch, view);
+ if (dom.contains(e.target as Node)) {
+ e.stopPropagation();
+ command(view.state, view.dispatch, view);
+ }
});
});
+ this.updateLinkMenu();
//list of font styles
this.fontStylesToName = new Map();
@@ -108,10 +108,14 @@ export class TooltipTextMenu {
this.fontSizeToNum.set(schema.marks.p12, 12);
this.fontSizeToNum.set(schema.marks.p14, 14);
this.fontSizeToNum.set(schema.marks.p16, 16);
+ this.fontSizeToNum.set(schema.marks.p18, 18);
+ this.fontSizeToNum.set(schema.marks.p20, 20);
this.fontSizeToNum.set(schema.marks.p24, 24);
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, 10);
+ this.fontSizeToNum.set(schema.marks.pFontSize, 10);
this.fontSizes = Array.from(this.fontSizeToNum.keys());
//list types
@@ -120,7 +124,21 @@ export class TooltipTextMenu {
this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
this.listTypes = Array.from(this.listTypeToIcon.keys());
+ // 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
@@ -131,15 +149,18 @@ export class TooltipTextMenu {
//font SIZES
let fontSizeBtns: MenuItem[] = [];
this.fontSizeToNum.forEach((number, mark) => {
- fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+ 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:white; min-width: 60px; padding-left: 5px; margin-right: 0;"
+ 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
@@ -150,22 +171,26 @@ export class TooltipTextMenu {
//font STYLES
let fontBtns: MenuItem[] = [];
this.fontStylesToName.forEach((name, mark) => {
- fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+ 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:white; width: 125px; margin-left: -3px; padding-left: 2px;"
+ 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() {
if (!this.linkEditor || !this.linkText) {
this.linkEditor = document.createElement("div");
- this.linkEditor.style.color = "white";
+ this.linkEditor.className = "ProseMirror-icon menuicon";
+ this.linkEditor.style.color = "black";
this.linkText = document.createElement("div");
this.linkText.style.cssFloat = "left";
this.linkText.style.marginRight = "5px";
@@ -178,8 +203,8 @@ export class TooltipTextMenu {
this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); };
let linkBtn = document.createElement("div");
linkBtn.textContent = ">>";
- linkBtn.style.width = "20px";
- linkBtn.style.height = "20px";
+ linkBtn.style.width = "10px";
+ linkBtn.style.height = "10px";
linkBtn.style.color = "white";
linkBtn.style.cssFloat = "left";
linkBtn.onpointerdown = (e: PointerEvent) => {
@@ -194,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);
}
}));
}
@@ -205,28 +230,32 @@ export class TooltipTextMenu {
};
this.linkDrag = document.createElement("img");
this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
- this.linkDrag.style.width = "20px";
- this.linkDrag.style.height = "20px";
- this.linkDrag.style.color = "white";
+ this.linkDrag.style.width = "15px";
+ this.linkDrag.style.height = "15px";
+ this.linkDrag.title = "Drag to create link";
+ this.linkDrag.style.color = "black";
this.linkDrag.style.background = "black";
this.linkDrag.style.cssFloat = "left";
this.linkDrag.onpointerdown = (e: PointerEvent) => {
let dragData = new DragManager.LinkDragData(this.editorProps.Document);
dragData.dontClearTextBox = true;
+ e.stopPropagation();
+ let ctrlKey = e.ctrlKey;
DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
{
handlers: {
dragComplete: action(() => {
- let m = dragData.droppedDocuments;
- this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+ // let m = dragData.droppedDocuments;
+ let linkDoc = dragData.linkDocument;
+ linkDoc instanceof Doc && this.makeLink(DocServer.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
}),
},
hideSource: false
});
};
this.linkEditor.appendChild(this.linkDrag);
- this.linkEditor.appendChild(this.linkText);
- this.linkEditor.appendChild(linkBtn);
+ // this.linkEditor.appendChild(this.linkText);
+ // this.linkEditor.appendChild(linkBtn);
this.tooltip.appendChild(this.linkEditor);
}
@@ -236,39 +265,89 @@ export class TooltipTextMenu {
this.linkText.onkeydown = (e: KeyboardEvent) => {
if (e.key === "Enter") {
- this.makeLink(this.linkText!.textContent!);
+ // this.makeLink(this.linkText!.textContent!);
e.stopPropagation();
e.preventDefault();
}
};
- this.tooltip.appendChild(this.linkEditor);
+ // this.tooltip.appendChild(this.linkEditor);
+ }
+
+ dragElement(elmnt: HTMLElement) {
+ var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+ if (elmnt) {
+ // if present, the header is where you move the DIV from:
+ elmnt.onpointerdown = dragMouseDown;
+ }
+ const self = this;
+
+ function dragMouseDown(e: PointerEvent) {
+ e = e || window.event;
+ //e.preventDefault();
+ // get the mouse cursor position at startup:
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ document.onpointerup = closeDragElement;
+ // call a function whenever the cursor moves:
+ document.onpointermove = elementDrag;
+ }
+
+ function elementDrag(e: PointerEvent) {
+ e = e || window.event;
+ //e.preventDefault();
+ // calculate the new cursor position:
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ // set the element's new position:
+ elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
+ elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
+ }
+
+ function closeDragElement() {
+ // stop moving when mouse button is released:
+ document.onpointerup = null;
+ document.onpointermove = null;
+ //self.highlightSearchTerms(self.state, ["hello"]);
+ //FormattedTextBox.Instance.unhighlightSearchTerms();
+ }
}
- makeLink = (target: string) => {
+ makeLink = (target: string, location: string) => {
let node = this.view.state.selection.$from.nodeAfter;
- let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target });
+ let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
node = this.view.state.selection.$from.nodeAfter;
link = node && node.marks.find(m => m.type.name === "link");
}
+ 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.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); }
//Make a dropdown of all list types
let toAdd: MenuItem[] = [];
this.listTypeToIcon.forEach((icon, type) => {
- toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+ toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
});
//option to remove the list formatting
- toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+ toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
listTypeBtn = (new Dropdown(toAdd, {
label: label,
- css: "color:white; width: 40px;"
+ css: "color:black; width: 40px;"
}) as MenuItem).render(this.view).dom;
//add this new button and return it
@@ -277,8 +356,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;
@@ -290,25 +369,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
@@ -325,13 +413,114 @@ 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({
+ title: "Add or remove link",
+ label: "Add or remove link",
+ execEvent: "",
+ icon: icons.link,
+ css: "color:white;",
+ class: "menuicon",
+ enable(state) { return !state.selection.empty; },
+ run: (state, dispatch, view) => {
+ // to remove link
+ let curLink = "";
+ if (this.markActive(state, markType)) {
+
+ let { from, $from, to, empty } = state.selection;
+ let node = state.doc.nodeAt(from);
+ node && node.marks.map(m => {
+ m.type === markType && (curLink = m.attrs.href);
+ });
+ //toggleMark(markType)(state, dispatch);
+ //return true;
+ }
+ // to create link
+ openPrompt({
+ title: "Create a link",
+ fields: {
+ href: new TextField({
+ value: curLink,
+ label: "Link Target",
+ required: true
+ }),
+ title: new TextField({ label: "Title" })
+ },
+ callback(attrs: any) {
+ toggleMark(markType, attrs)(view.state, view.dispatch);
+ view.focus();
+ },
+ flyout_top: 0,
+ flyout_left: 0
+ });
+ }
+ });
+ }
+
//makes a button for the drop down FOR NODE TYPES
//css is the style you want applied to the button
dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
@@ -341,18 +530,24 @@ export class TooltipTextMenu {
execEvent: "",
class: "menuicon",
css: css,
- enable(state) { return true; },
+ enable() { return true; },
run() {
changeToNodeInGroup(nodeType, view, groupNodes);
}
});
}
+ markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
+ let { from, $from, to, empty } = state.selection;
+ if (empty) return type.isInSet(state.storedMarks || $from.marks());
+ else return state.doc.rangeHasMark(from, to, type);
+ };
+
// Helper function to create menu icons
- icon(text: string, name: string) {
+ icon(text: string, name: string, title: string = name) {
let span = document.createElement("span");
span.className = name + " menuicon";
- span.title = name;
+ span.title = title;
span.textContent = text;
span.style.color = "white";
return span;
@@ -395,6 +590,20 @@ export class TooltipTextMenu {
};
}
+ getMarksInSelection(state: EditorState<any>, targets: MarkType<any>[]) {
+ let found: Mark<any>[] = [];
+ let { from, to } = state.selection as TextSelection;
+ state.doc.nodesBetween(from, to, (node) => {
+ let marks = node.marks;
+ if (marks) {
+ marks.forEach(m => {
+ if (targets.includes(m.type)) found.push(m);
+ });
+ }
+ });
+ return found;
+ }
+
//updates the tooltip menu when the selection changes
update(view: EditorView, lastState: EditorState | undefined) {
let state = view.state;
@@ -404,76 +613,121 @@ 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;
}
- // 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";
//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.updateLinkMenu();
+ this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks));
}
//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();
+ }
}