aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFawn <fangrui_tong@brown.edu>2020-01-07 08:04:32 -0500
committerFawn <fangrui_tong@brown.edu>2020-01-07 08:04:32 -0500
commit372d8623ce5b0fd545739cb53a38f6d3f759e7e4 (patch)
treefb1b5e977c5f401bb5b57f31012b98626d28c902 /src
parente29ba39e74d2d22a74e767c39c30055ebf2ca5e0 (diff)
integrated color and highlight tools in richtextmenu
Diffstat (limited to 'src')
-rw-r--r--src/client/util/RichTextMenu.scss2
-rw-r--r--src/client/util/RichTextMenu.tsx225
2 files changed, 164 insertions, 63 deletions
diff --git a/src/client/util/RichTextMenu.scss b/src/client/util/RichTextMenu.scss
index 403e9f88a..9c5c55b62 100644
--- a/src/client/util/RichTextMenu.scss
+++ b/src/client/util/RichTextMenu.scss
@@ -1,3 +1,5 @@
+@import "../views/globalCssVariables";
+
.button-dropdown-wrapper {
position: relative;
diff --git a/src/client/util/RichTextMenu.tsx b/src/client/util/RichTextMenu.tsx
index ad7b3f2fc..a9de87572 100644
--- a/src/client/util/RichTextMenu.tsx
+++ b/src/client/util/RichTextMenu.tsx
@@ -8,7 +8,7 @@ import { EditorView } from "prosemirror-view";
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown } from "@fortawesome/free-solid-svg-icons";
+import { faBold, faItalic, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter } from "@fortawesome/free-solid-svg-icons";
import { MenuItem, Dropdown } from "prosemirror-menu";
import { updateBullets } from "./ProsemirrorExampleTransfer";
import { FieldViewProps } from "../views/nodes/FieldView";
@@ -16,10 +16,11 @@ import { NumCast } from "../../new_fields/Types";
import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
import { unimplementedFunction } from "../../Utils";
import { wrapInList } from "prosemirror-schema-list";
+import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField';
import "./RichTextMenu.scss";
const { toggleMark, setBlockType } = require("prosemirror-commands");
-library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown);
+library.add(faBold, faItalic, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faHighlighter);
@observer
export default class RichTextMenu extends AntimodeMenu {
@@ -34,6 +35,13 @@ export default class RichTextMenu extends AntimodeMenu {
@observable private brushIsEmpty: boolean = true;
@observable private brushMarks: Set<Mark> = new Set();
+ @observable private showBrushDropdown: boolean = false;
+
+ @observable private activeFontColor: string = "black";
+ @observable private showColorDropdown: boolean = false;
+
+ @observable private activeHighlightColor: string = "transparent";
+ @observable private showHighlightDropdown: boolean = false;
constructor(props: Readonly<{}>) {
super(props);
@@ -76,8 +84,8 @@ export default class RichTextMenu extends AntimodeMenu {
const activeFamilies = active && active.get("families");
const activeSizes = active && active.get("sizes");
- this.activeFontFamily = !activeFamilies || activeFamilies.length == 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
- this.activeFontSize = !activeSizes || activeSizes.length == 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
+ this.activeFontFamily = !activeFamilies || activeFamilies.length === 0 ? "default" : activeFamilies.length === 1 ? String(activeFamilies[0]) : "various";
+ this.activeFontSize = !activeSizes || activeSizes.length === 0 ? "default" : activeSizes.length === 1 ? String(activeSizes[0]) + "pt" : "various";
// this.update_mark_doms();
}
@@ -160,7 +168,7 @@ export default class RichTextMenu extends AntimodeMenu {
}
createMarksDropdown(activeOption: string, options: { mark: Mark | null, title: string, label: string, command: (mark: Mark, view: EditorView) => void, hidden?: boolean }[]): JSX.Element {
- let items = options.map(({ title, label, hidden }) => {
+ const items = options.map(({ title, label, hidden }) => {
if (hidden) {
return label === activeOption ?
<option value={label} title={title} selected hidden>{label}</option> :
@@ -183,7 +191,7 @@ export default class RichTextMenu extends AntimodeMenu {
}
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 }) => {
+ const items = options.map(({ title, label, hidden }) => {
if (hidden) {
return label === activeOption ?
<option value={label} title={title} selected hidden>{label}</option> :
@@ -270,25 +278,22 @@ export default class RichTextMenu extends AntimodeMenu {
return true;
}
+ @action
+ toggleBrushDropdown() { this.showBrushDropdown = !this.showBrushDropdown; }
+
createBrushButton() {
const self = this;
- function onClick(e: React.PointerEvent) {
- console.log("clicked button");
- // dom.addEventListener("pointerdown", e => {
+ function onBrushClick(e: React.PointerEvent) {
e.preventDefault();
- self.view && self.view.focus();
- // if (dom.contains(e.target as Node)) {
e.stopPropagation();
- // command(this.view.state, this.view.dispatch, this.view);
- // }
- // });
-
+ self.view && self.view.focus();
self.view && self.fillBrush(self.view.state, self.view.dispatch);
-
- // // update dropdown with marks
- // const newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom;
- // self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom);
- // self._brushDropdownDom = newBrushDropdowndom;
+ }
+ function onDropdownClick(e: React.PointerEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.toggleBrushDropdown();
}
let label = "Stored marks: ";
@@ -305,15 +310,17 @@ export default class RichTextMenu extends AntimodeMenu {
return (
<div className="button-dropdown-wrapper">
- <button className="antimodeMenu-button" title="" onPointerDown={onClick}>
+ <button className="antimodeMenu-button" title="" onPointerDown={onBrushClick}>
<FontAwesomeIcon icon="eye-dropper" size="lg" />
</button>
- <button className="dropdown-button antimodeMenu-button"><FontAwesomeIcon icon="caret-down" size="sm" /></button>
- <div className="dropdown">
- <p>{label}</p>
- <button onPointerDown={this.clearBrush}>Clear brush</button>
- {/* <input placeholder="Enter URL"></input> */}
- </div>
+ <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+ {this.showBrushDropdown ?
+ (<div className="dropdown">
+ <p>{label}</p>
+ <button onPointerDown={this.clearBrush}>Clear brush</button>
+ {/* <input placeholder="Enter URL"></input> */}
+ </div>)
+ : <></> }
</div>
);
}
@@ -330,20 +337,14 @@ export default class RichTextMenu extends AntimodeMenu {
if (this.brushIsEmpty) {
const selected_marks = this.getMarksInSelection(this.view.state);
- // if (this._brushdom) {
if (selected_marks.size >= 0) {
this.brushMarks = selected_marks;
- // const newbrush = this.createBrush(true).render(this.view).dom;
- // this.tooltip.replaceChild(newbrush, this._brushdom);
- // this._brushdom = newbrush;
this.brushIsEmpty = !this.brushIsEmpty;
- // TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
}
// }
}
else {
const { from, to, $from } = this.view.state.selection;
- // if (this._brushdom) {
if (!this.view.state.selection.empty && $from && $from.nodeAfter) {
if (this.brushMarks && to - from > 0) {
this.view.dispatch(this.view.state.tr.removeMark(from, to));
@@ -353,39 +354,135 @@ export default class RichTextMenu extends AntimodeMenu {
}
}
else {
- // const newbrush = this.createBrush(false).render(this.view).dom;
- // this.tooltip.replaceChild(newbrush, this._brushdom);
- // this._brushdom = newbrush;
this.brushIsEmpty = !this.brushIsEmpty;
- // TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty;
}
- // }
}
console.log("brush marks are ", this.brushMarks);
}
- // createColorButton() {
- // const self = this;
- // function onClick(e: React.PointerEvent) {
- // console.log("clicked button");
- // // dom.addEventListener("pointerdown", e => {
- // e.preventDefault();
- // self.view && self.view.focus();
- // // if (dom.contains(e.target as Node)) {
- // e.stopPropagation();
- // // command(this.view.state, this.view.dispatch, this.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 (
- // <button className="antimodeMenu-button" title="Set font color" onPointerDown={onClick}>
- // <FontAwesomeIcon icon={faIcon as IconProp} size="lg" />
- // </button>
- // );
- // }
+ @action toggleColorDropdown() { this.showColorDropdown = !this.showColorDropdown; }
+ @action setActiveColor(color: string) { this.activeFontColor = color; }
+
+ createColorButton() {
+ const self = this;
+ function onColorClick(e: React.PointerEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+ }
+ function onDropdownClick(e: React.PointerEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.toggleColorDropdown();
+ }
+ function changeColor(e: React.PointerEvent, color: string) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.setActiveColor(color);
+ self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch);
+ }
+
+ const colors = [
+ DarkPastelSchemaPalette.get("pink2"),
+ DarkPastelSchemaPalette.get("purple4"),
+ DarkPastelSchemaPalette.get("bluegreen1"),
+ DarkPastelSchemaPalette.get("yellow4"),
+ DarkPastelSchemaPalette.get("red2"),
+ DarkPastelSchemaPalette.get("bluegreen7"),
+ DarkPastelSchemaPalette.get("bluegreen5"),
+ DarkPastelSchemaPalette.get("orange1"),
+ "#757472",
+ "#000"
+ ];
+
+ return (
+ <div className="button-dropdown-wrapper">
+ <button className="antimodeMenu-button" title="" onPointerDown={onColorClick}>
+ <FontAwesomeIcon icon="palette" size="lg" />
+ </button>
+ <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+ {this.showColorDropdown ?
+ (<div className="dropdown">
+ {colors.map(color => {
+ return <button style={{backgroundColor: color}} onPointerDown={e => changeColor(e, color)}></button>;
+ })}
+ </div>)
+ : <></> }
+ </div>
+ );
+ }
+
+ public insertColor(color: String, state: EditorState<any>, dispatch: any) {
+ const colorMark = state.schema.mark(state.schema.marks.pFontColor, { color: color });
+ if (state.selection.empty) {
+ dispatch(state.tr.addStoredMark(colorMark));
+ return false;
+ }
+ this.setMark(colorMark, state, dispatch);
+ }
+
+ @action toggleHighlightDropdown() { this.showHighlightDropdown = !this.showHighlightDropdown; }
+ @action setActiveHighlight(color: string) { this.activeHighlightColor = color; }
+
+ createHighlighterButton() {
+ const self = this;
+ function onHighlightClick(e: React.PointerEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+ }
+ function onDropdownClick(e: React.PointerEvent) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.toggleHighlightDropdown();
+ }
+ function changeHighlight(e: React.PointerEvent, color: string) {
+ e.preventDefault();
+ e.stopPropagation();
+ self.view && self.view.focus();
+ self.setActiveHighlight(color);
+ self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch);
+ }
+
+ const colors = [
+ PastelSchemaPalette.get("pink2"),
+ PastelSchemaPalette.get("purple4"),
+ PastelSchemaPalette.get("bluegreen1"),
+ PastelSchemaPalette.get("yellow4"),
+ PastelSchemaPalette.get("red2"),
+ PastelSchemaPalette.get("bluegreen7"),
+ PastelSchemaPalette.get("bluegreen5"),
+ PastelSchemaPalette.get("orange1"),
+ "white",
+ "transparent"
+ ];
+
+ return (
+ <div className="button-dropdown-wrapper">
+ <button className="antimodeMenu-button" title="" onPointerDown={onHighlightClick}>
+ <FontAwesomeIcon icon="highlighter" size="lg" />
+ </button>
+ <button className="dropdown-button antimodeMenu-button" onPointerDown={onDropdownClick}><FontAwesomeIcon icon="caret-down" size="sm" /></button>
+ {this.showHighlightDropdown ?
+ (<div className="dropdown">
+ {colors.map(color => {
+ return <button style={{backgroundColor: color}} onPointerDown={e => changeHighlight(e, color)}></button>;
+ })}
+ </div>)
+ : <></> }
+ </div>
+ );
+ }
+
+ insertHighlight(color: String, state: EditorState<any>, dispatch: any) {
+ if (state.selection.empty) return false;
+ toggleMark(state.schema.marks.marker, { highlight: color })(state, dispatch);
+ }
reference_node(pos: ResolvedPos<any>): ProsNode | null {
if (!this.view) return null;
@@ -432,7 +529,7 @@ export default class RichTextMenu extends AntimodeMenu {
{ mark: schema.marks.pFontSize.create({ fontSize: 72 }), title: "Set font size", label: "72pt", command: this.changeFontSize },
{ mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
{ mark: null, title: "", label: "default", command: unimplementedFunction, hidden: true },
- ]
+ ];
const fontFamilyOptions = [
{ mark: schema.marks.pFontFamily.create({ family: "Times New Roman" }), title: "Set font family", label: "Times New Roman", command: this.changeFontFamily },
@@ -444,7 +541,7 @@ export default class RichTextMenu extends AntimodeMenu {
{ mark: schema.marks.pFontFamily.create({ family: "Crimson Text" }), title: "Set font family", label: "Crimson Text", command: this.changeFontFamily },
{ mark: null, title: "", label: "various", command: unimplementedFunction, hidden: true },
{ 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) {
@@ -471,7 +568,7 @@ export default class RichTextMenu extends AntimodeMenu {
{ 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 } []
@@ -482,6 +579,8 @@ 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.createColorButton(),
+ this.createHighlighterButton(),
this.createBrushButton(),
this.createButton("indent", "Summarize", undefined, this.insertSummarizer),
this.createMarksDropdown(this.activeFontSize, fontSizeOptions),