diff options
Diffstat (limited to 'src/client/util/TooltipTextMenu.tsx')
-rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 220 |
1 files changed, 188 insertions, 32 deletions
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 7951e5686..a92cbd263 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,28 +1,43 @@ import { action, IReactionDisposer, reaction } from "mobx"; -import { baseKeymap } from "prosemirror-commands"; +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 } from "prosemirror-state"; +import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { schema } from "./RichTextSchema"; -import { Schema, NodeType } from "prosemirror-model"; +import { Schema, NodeType, MarkType } from "prosemirror-model"; import React = require("react"); import "./TooltipTextMenu.scss"; const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands"); import { library } from '@fortawesome/fontawesome-svg-core'; -import { wrapInList, bulletList } from 'prosemirror-schema-list'; -import { faListUl } from '@fortawesome/free-solid-svg-icons'; +import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list'; +import { + faListUl, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FieldViewProps } from "../views/nodes/FieldView"; import { throwStatement } from "babel-types"; +const SVG = "http://www.w3.org/2000/svg"; //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { private tooltip: HTMLElement; + private num_icons = 0; + private view: EditorView; + private fontStyles: MarkType[]; + private fontSizes: MarkType[]; private editorProps: FieldViewProps; + private state: EditorState; + private fontSizeToNum: Map<MarkType, number>; + private fontStylesToName: Map<MarkType, string>; + private fontSizeIndicator: HTMLSpanElement = document.createElement("span"); constructor(view: EditorView, editorProps: FieldViewProps) { + this.view = view; + this.state = view.state; this.editorProps = editorProps; this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; @@ -32,7 +47,6 @@ export class TooltipTextMenu { //add additional icons library.add(faListUl); - //add the buttons to the tooltip let items = [ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") }, @@ -41,36 +55,145 @@ export class TooltipTextMenu { { 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") }, - //this doesn't work currently - look into notion of active block { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, + { command: lift, dom: this.icon("<", "lift") }, ]; - items.forEach(({ dom }) => this.tooltip.appendChild(dom)); - - //pointer down handler to activate button effects - this.tooltip.addEventListener("pointerdown", e => { - e.preventDefault(); - view.focus(); - items.forEach(({ command, dom }) => { - if (dom.contains(e.target as Node)) { - let active = command(view.state, view.dispatch, view); - //uncomment this if we want the bullet button to disappear if current selection is bulleted - // dom.style.display = active ? "" : "none" - } + //add menu items + items.forEach(({ dom, command }) => { + this.tooltip.appendChild(dom); + + //pointer down handler to activate button effects + dom.addEventListener("pointerdown", e => { + e.preventDefault(); + view.focus(); + command(view.state, view.dispatch, view); }); + }); + //list of font styles + this.fontStylesToName = new Map(); + this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); + this.fontStylesToName.set(schema.marks.arial, "Arial"); + this.fontStylesToName.set(schema.marks.georgia, "Georgia"); + this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans"); + this.fontStylesToName.set(schema.marks.tahoma, "Tahoma"); + this.fontStylesToName.set(schema.marks.impact, "Impact"); + this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); + this.fontStyles = Array.from(this.fontStylesToName.keys()); + + //font sizes + this.fontSizeToNum = new Map(); + this.fontSizeToNum.set(schema.marks.p10, 10); + this.fontSizeToNum.set(schema.marks.p12, 12); + this.fontSizeToNum.set(schema.marks.p16, 16); + 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.fontSizes = Array.from(this.fontSizeToNum.keys()); + + this.addFontDropdowns(); + this.update(view, undefined); } + //adds font size and font style dropdowns + addFontDropdowns() { + //filtering function - might be unecessary + let cut = (arr: MenuItem[]) => arr.filter(x => x); + //font STYLES + let fontBtns: MenuItem[] = []; + this.fontStylesToName.forEach((name, mark) => { + fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 120px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + }); + + //font size indicator + this.fontSizeIndicator = this.icon("12", "font-size-indicator"); + + //font SIZES + let fontSizeBtns: MenuItem[] = []; + this.fontSizeToNum.forEach((number, mark) => { + fontSizeBtns.push(this.dropdownBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); + }); + + //dropdown to hold font btns + let dd_fontStyle = new Dropdown(cut(fontBtns), { label: "Font Style", css: "color:white;" }) as MenuItem; + let dd_fontSize = new Dropdown(cut(fontSizeBtns), { label: "Font Size", css: "color:white; margin-left: -6px;" }) as MenuItem; + this.tooltip.appendChild(dd_fontStyle.render(this.view).dom); + this.tooltip.appendChild(this.fontSizeIndicator); + this.tooltip.appendChild(dd_fontSize.render(this.view).dom); + dd_fontStyle.render(this.view).dom.nodeValue = "TEST"; + console.log(dd_fontStyle.render(this.view).dom.nodeValue); + } + + //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; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + fontMarks.forEach((type) => { + if (dispatch) { + if ($cursor) { + if (type.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(type)); + } + } else { + let has = false, tr = state.tr; + 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 + return toggleMark(markType)(view.state, view.dispatch, view); + } + + //makes a button for the drop down + //css is the style you want applied to the button + dropdownBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "menuicon", + css: css, + enable(state) { return true; }, + run() { + changeToMarkInGroup(markType, view, groupMarks); + } + }); + } // Helper function to create menu icons icon(text: string, name: string) { let span = document.createElement("span"); - span.className = "menuicon " + name; + span.className = name + " menuicon"; span.title = name; span.textContent = text; + span.style.color = "white"; return span; } + //method for checking whether node can be inserted + canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) { + let $from = state.selection.$from; + for (let d = $from.depth; d >= 0; d--) { + let index = $from.index(d); + if ($from.node(d).canReplaceWith(index, index, nodeType)) return true; + } + return false; + } + + //adapted this method - use it to check if block has a tag (ie bulleting) blockActive(type: NodeType<Schema<string, string>>, state: EditorState) { let attrs = {}; @@ -89,15 +212,6 @@ export class TooltipTextMenu { } } - //this doesn't currently work but could be used to use icons for buttons - unorderedListIcon(): HTMLSpanElement { - let span = document.createElement("span"); - let icon = document.createElement("FontAwesomeIcon"); - icon.className = "menuicon fa fa-smile-o"; - span.appendChild(icon); - return span; - } - // Create an icon for a heading at the given level heading(level: number) { return { @@ -132,10 +246,52 @@ export class TooltipTextMenu { let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale; let mid = Math.min(start.left, end.left) + width; - //THIS WIDTH IS 15 * NUMBER OF ICONS + 15 - this.tooltip.style.width = 122 + "px"; + this.tooltip.style.width = 220 + "px"; this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px"; + + 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]); + } else if (activeStyles.length === 0) { + //crimson on default + } + + //update font size indicator + 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.fontSizeIndicator.innerHTML = String(size); + } + //should be 14 on default + } else if (activeSizes.length === 0) { + this.fontSizeIndicator.innerHTML = "14"; + //multiple font sizes selected + } else { + this.fontSizeIndicator.innerHTML = ""; + } + } + + //finds all active marks on selection + activeMarksOnSelection(markGroup: MarkType[]) { + //current selection + let { empty, $cursor, 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); + } + } + return false; + }); + return activeMarks; } destroy() { this.tooltip.remove(); } -}
\ No newline at end of file +} |