aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DocumentManager.ts2
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts217
-rw-r--r--src/client/util/RichTextRules.ts7
-rw-r--r--src/client/util/RichTextSchema.tsx230
-rw-r--r--src/client/util/TooltipLinkingMenu.tsx20
-rw-r--r--src/client/util/TooltipTextMenu.tsx49
-rw-r--r--src/client/util/prosemirrorPatches.js62
7 files changed, 478 insertions, 109 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 124faf266..ec731da84 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -204,4 +204,4 @@ export class DocumentManager {
}
}
}
-Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)) }) \ No newline at end of file
+Scripting.addGlobal(function focus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index c38f84551..419311df8 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -1,14 +1,11 @@
-import { Schema, NodeType } from "prosemirror-model";
-import {
- wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
- joinUp, joinDown, lift, selectParentNode
-} from "prosemirror-commands";
-import { wrapInList, splitListItem, liftListItem, sinkListItem } from "prosemirror-schema-list";
-import { undo, redo } from "prosemirror-history";
+import { chainCommands, exitCode, joinDown, joinUp, lift, selectParentNode, setBlockType, splitBlockKeepMarks, toggleMark, wrapIn } from "prosemirror-commands";
+import { redo, undo } from "prosemirror-history";
import { undoInputRule } from "prosemirror-inputrules";
-import { Transaction, EditorState } from "prosemirror-state";
+import { Schema } from "prosemirror-model";
+import { liftListItem, } from "./prosemirrorPatches.js";
+import { splitListItem, wrapInList, sinkListItem } from "prosemirror-schema-list";
+import { EditorState, Transaction, TextSelection, NodeSelection } from "prosemirror-state";
import { TooltipTextMenu } from "./TooltipTextMenu";
-import { Statement } from "../northstar/model/idea/idea";
const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
@@ -30,79 +27,161 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
bind("Shift-Mod-z", redo);
bind("Backspace", undoInputRule);
- if (!mac) {
- bind("Mod-y", redo);
- }
+ !mac && bind("Mod-y", redo);
bind("Alt-ArrowUp", joinUp);
bind("Alt-ArrowDown", joinDown);
bind("Mod-BracketLeft", lift);
bind("Escape", selectParentNode);
- if (type = schema.marks.strong) {
- bind("Mod-b", toggleMark(type));
- bind("Mod-B", toggleMark(type));
- }
- if (type = schema.marks.em) {
- bind("Mod-i", toggleMark(type));
- bind("Mod-I", toggleMark(type));
- }
- if (type = schema.marks.underline) {
- bind("Mod-u", toggleMark(type));
- bind("Mod-U", toggleMark(type));
- }
- if (type = schema.marks.code) {
- bind("Mod-`", toggleMark(type));
- }
+ bind("Mod-b", toggleMark(schema.marks.strong));
+ bind("Mod-B", toggleMark(schema.marks.strong));
- if (type = schema.nodes.bullet_list) {
- bind("Ctrl-.", wrapInList(type));
- }
- if (type = schema.nodes.ordered_list) {
- bind("Ctrl-n", wrapInList(type));
- }
- if (type = schema.nodes.blockquote) {
- bind("Ctrl->", wrapIn(type));
+ bind("Mod-e", toggleMark(schema.marks.em));
+ bind("Mod-E", toggleMark(schema.marks.em));
+
+ bind("Mod-u", toggleMark(schema.marks.underline));
+ bind("Mod-U", toggleMark(schema.marks.underline));
+
+ bind("Mod-`", toggleMark(schema.marks.code));
+
+ bind("Ctrl-.", wrapInList(schema.nodes.bullet_list));
+
+ bind("Ctrl-n", wrapInList(schema.nodes.ordered_list));
+
+ bind("Ctrl->", wrapIn(schema.nodes.blockquote));
+
+ bind("^", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ let newNode = schema.nodes.footnote.create({});
+ if (dispatch && state.selection.from === state.selection.to) {
+ let tr = state.tr;
+ tr.replaceSelectionWith(newNode); // replace insertion with a footnote.
+ dispatch(tr.setSelection(new NodeSelection( // select the footnote node to open its display
+ tr.doc.resolve( // get the location of the footnote node by subtracting the nodesize of the footnote from the current insertion point anchor (which will be immediately after the footnote node)
+ tr.selection.anchor - tr.selection.$anchor.nodeBefore!.nodeSize))));
+ return true;
+ }
+ return false;
+ })
+
+
+ let cmd = chainCommands(exitCode, (state, dispatch) => {
+ if (dispatch) {
+ dispatch(state.tr.replaceSelectionWith(schema.nodes.hard_break.create()).scrollIntoView());
+ return true;
+ }
+ return false;
+ });
+ bind("Mod-Enter", cmd);
+ bind("Shift-Enter", cmd);
+ mac && bind("Ctrl-Enter", cmd);
+
+
+ bind("Shift-Ctrl-0", setBlockType(schema.nodes.paragraph));
+
+ bind("Shift-Ctrl-\\", setBlockType(schema.nodes.code_block));
+
+ for (let i = 1; i <= 6; i++) {
+ bind("Shift-Ctrl-" + i, setBlockType(schema.nodes.heading, { level: i }));
}
- if (type = schema.nodes.hard_break) {
- let br = type, cmd = chainCommands(exitCode, (state, dispatch) => {
- if (dispatch) {
- dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView());
- return true;
+
+ let hr = schema.nodes.horizontal_rule;
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+ return true;
+ });
+
+ bind("Mod-s", TooltipTextMenu.insertStar);
+
+ let updateBullets = (tx2: Transaction, refStart: number, delta: number) => {
+ for (let i = refStart; i > 0; i--) {
+ let testPos = tx2.doc.nodeAt(i);
+ if (testPos && testPos.type === schema.nodes.list_item) {
+ let start = i;
+ let preve = i > 0 && tx2.doc.nodeAt(start - 1);
+ if (preve && preve.type === schema.nodes.ordered_list) {
+ start = start - 1;
+ }
+ let rangeStart = tx2.doc.nodeAt(start);
+ if (rangeStart && rangeStart.type === schema.nodes.ordered_list) {
+ tx2.setNodeMarkup(start, rangeStart.type, { ...rangeStart.attrs, bulletStyle: rangeStart.attrs.bulletStyle + delta }, rangeStart.marks);
+ }
+ rangeStart && rangeStart.descendants((node: any, offset: any, index: any) => {
+ if (node.type === schema.nodes.ordered_list) {
+ tx2.setNodeMarkup(start + offset + 1, node.type, { ...node.attrs, bulletStyle: node.attrs.bulletStyle + delta }, node.marks);
+ }
+ });
+ break;
}
- return false;
- });
- bind("Mod-Enter", cmd);
- bind("Shift-Enter", cmd);
- if (mac) {
- bind("Ctrl-Enter", cmd);
}
}
- if (type = schema.nodes.list_item) {
- bind("Enter", splitListItem(type));
- bind("Shift-Tab", liftListItem(type));
- bind("Tab", sinkListItem(type));
- }
- if (type = schema.nodes.paragraph) {
- bind("Shift-Ctrl-0", setBlockType(type));
- }
- if (type = schema.nodes.code_block) {
- bind("Shift-Ctrl-\\", setBlockType(type));
- }
- if (type = schema.nodes.heading) {
- for (let i = 1; i <= 6; i++) {
- bind("Shift-Ctrl-" + i, setBlockType(type, { level: i }));
+
+ bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ var ref = state.selection;
+ var range = ref.$from.blockRange(ref.$to);
+ var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ if (!sinkListItem(schema.nodes.list_item)(state, (tx2: Transaction) => {
+ updateBullets(tx2, range!.start, 1);
+ marks && tx2.ensureMarks([...marks]);
+ marks && tx2.setStoredMarks([...marks]);
+ dispatch(tx2);
+ })) { // couldn't sink into an existing list, so wrap in a new one
+ let sxf = state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end));
+ let newstate = state.applyTransaction(sxf);
+ if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
+ for (let i = range!.start; i >= 0; i--) {
+ let rangeStart = tx2.doc.nodeAt(i);
+ if (rangeStart && rangeStart.type === schema.nodes.ordered_list) {
+ tx2.setNodeMarkup(i, rangeStart.type, { ...rangeStart.attrs, bulletStyle: 1 }, rangeStart.marks);
+ break;
+ }
+ }
+ // when promoting to a list, assume list will format things so don't copy the stored marks.
+ marks && tx2.ensureMarks([...marks]);
+ marks && tx2.setStoredMarks([...marks]);
+ dispatch(tx2);
+ })) {
+ console.log("bullet promote fail");
+ }
}
- }
- if (type = schema.nodes.horizontal_rule) {
- let hr = type;
- bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
- dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
- return true;
- });
- }
+ });
+
+ bind("Shift-Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ var range = state.selection.$from.blockRange(state.selection.$to);
+ var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+
+ let tr = state.tr;
+ updateBullets(tr, range!.start, -1);
+
+ if (!liftListItem(schema.nodes.list_item)(tr, (tx2: Transaction) => {
+ marks && tx2.ensureMarks([...marks]);
+ marks && tx2.setStoredMarks([...marks]);
+ dispatch(tx2);
+ })) {
+ console.log("bullet demote fail");
+ }
+ });
+
+ bind("Enter", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
+ if (!splitListItem(schema.nodes.list_item)(state, (tx3: Transaction) => {
+ // marks && tx3.ensureMarks(marks);
+ // marks && tx3.setStoredMarks(marks);
+ dispatch(tx3);
+ })) {
+ if (!splitBlockKeepMarks(state, (tx3: Transaction) => {
+ marks && tx3.ensureMarks(marks);
+ marks && tx3.setStoredMarks(marks);
+ if (!liftListItem(schema.nodes.list_item)(tx3, dispatch as ((tx: Transaction<Schema<any, any>>) => void))) {
+ dispatch(tx3);
+ }
+ })) {
+ return false;
+ }
+ }
+ return true;
+ });
- bind("Mod-s", TooltipTextMenu.insertStar);
return keys;
}
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 3b8396510..8c4c76027 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -26,6 +26,13 @@ export const inpRules = {
match => ({ order: +match[1] }),
(match, node) => node.childCount + node.attrs.order === +match[1]
),
+ // a. alphabbetical list
+ wrappingInputRule(
+ /^([a-z]+)\.\s$/,
+ schema.nodes.alphabet_list,
+ match => ({ order: +match[1] }),
+ (match, node) => node.childCount + node.attrs.order === +match[1]
+ ),
// * bullet list
wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 9fdda4845..25d972857 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,6 +1,12 @@
import { DOMOutputSpecArray, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model";
import { bulletList, listItem, orderedList } from 'prosemirror-schema-list';
-import { TextSelection } from "prosemirror-state";
+import { TextSelection, EditorState } from "prosemirror-state";
+import { Doc } from "../../new_fields/Doc";
+import { StepMap } from "prosemirror-transform";
+import { EditorView } from "prosemirror-view";
+import { keymap } from "prosemirror-keymap";
+import { undo, redo } from "prosemirror-history";
+import { toggleMark, splitBlock, selectAll, baseKeymap } from "prosemirror-commands";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -13,6 +19,20 @@ export const nodes: { [index: string]: NodeSpec } = {
content: "block+"
},
+ footnote: {
+ group: "inline",
+ content: "inline*",
+ inline: true,
+ attrs: {
+ visibility: { default: false }
+ },
+ // This makes the view treat the node as a leaf, even though it
+ // technically has content
+ atom: true,
+ toDOM: () => ["footnote", 0],
+ parseDOM: [{ tag: "footnote" }]
+ },
+
// :: NodeSpec A plain paragraph textblock. Represented in the DOM
// as a `<p>` element.
paragraph: {
@@ -173,7 +193,21 @@ export const nodes: { [index: string]: NodeSpec } = {
ordered_list: {
...orderedList,
content: 'list_item+',
- group: 'block'
+ group: 'block',
+ attrs: {
+ bulletStyle: { default: 0 },
+ mapStyle: { default: "decimal" },
+ },
+ toDOM(node: Node<any>) {
+ const bs = node.attrs.bulletStyle;
+ const decMap = bs ? "decimal" + bs : "";
+ const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : "";
+ let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap;
+ for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = map;
+ return ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0];
+ //return node.attrs.bulletStyle < 2 ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
+ // ['ol', { class: `${node.attrs.bulletStyle}`, style: `list-style: ${node.attrs.bulletStyle}; font-size: 5px` }, "hello"];
+ }
},
//this doesn't currently work for some reason
bullet_list: {
@@ -181,7 +215,10 @@ export const nodes: { [index: string]: NodeSpec } = {
content: 'list_item+',
group: 'block',
// parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
- // toDOM() { return ulDOM }
+ toDOM(node: Node<any>) {
+ for (let i = 0; i < node.childCount; i++) node.child(i).attrs.className = "";
+ return ['ul', 0];
+ }
},
//bullet_list: {
@@ -193,8 +230,14 @@ export const nodes: { [index: string]: NodeSpec } = {
//select: state => true,
// },
list_item: {
+ attrs: {
+ className: { default: "" }
+ },
...listItem,
- content: 'paragraph block*'
+ content: 'paragraph block*',
+ toDOM(node: any) {
+ return ["li", { class: node.attrs.className }, 0];
+ }
},
};
@@ -226,7 +269,7 @@ export const marks: { [index: string]: MarkSpec } = {
// :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
// Has parse rules that also match `<i>` and `font-style: italic`.
em: {
- parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
+ parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style: italic" }],
toDOM() { return emDOM; }
},
@@ -278,11 +321,22 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM: () => ['sup']
},
+ mbulletType: {
+ attrs: {
+ bulletType: { default: "decimal" }
+ },
+ toDOM(node: any) {
+ return ['span', {
+ style: `background: ${node.attrs.bulletType === "decimal" ? "yellow" : node.attrs.bulletType === "upper-alpha" ? "blue" : "green"}`
+ }];
+ }
+ },
+
highlight: {
- parseDOM: [{ style: 'color: blue' }],
+ parseDOM: [{ style: 'text-decoration: underline' }],
toDOM() {
return ['span', {
- style: 'color: blue'
+ style: 'text-decoration: underline; text-decoration-color: rgba(204, 206, 210, 0.92)'
}];
}
},
@@ -296,6 +350,28 @@ export const marks: { [index: string]: MarkSpec } = {
}
},
+ // the id of the user who entered the text
+ user_mark: {
+ attrs: {
+ userid: { default: "" },
+ hide_users: { default: [] },
+ opened: { default: true },
+ modified: { default: "when?" }
+ },
+ group: "inline",
+ inclusive: false,
+ toDOM(node: any) {
+ let hideUsers = node.attrs.hide_users;
+ let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail);
+ return hidden ?
+ (node.attrs.opened ?
+ ['span', { class: "userMarkOpen" }, 0] :
+ ['span', { class: "userMark" }, ['span', 0]]
+ ) :
+ ['span', 0];
+ }
+ },
+
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
@@ -371,7 +447,6 @@ export const marks: { [index: string]: MarkSpec } = {
attrs: {
fontSize: { default: 10 }
},
- inclusive: false,
parseDOM: [{ style: 'font-size: 10px;' }],
toDOM: (node) => ['span', {
style: `font-size: ${node.attrs.fontSize}px;`
@@ -518,6 +593,141 @@ export class ImageResizeView {
}
}
+export class OrderedListView {
+ constructor(node: any, view: any, getPos: any) { }
+
+ update(node: any) {
+ return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update
+ }
+}
+
+export class FootnoteView {
+ innerView: any;
+ outerView: any;
+ node: any;
+ dom: any;
+ getPos: any;
+
+ constructor(node: any, view: any, getPos: any) {
+ // We'll need these later
+ this.node = node
+ this.outerView = view
+ this.getPos = getPos
+
+ // The node's representation in the editor (empty, for now)
+ this.dom = document.createElement("footnote");
+ this.dom.addEventListener("pointerup", this.toggle, true);
+ // These are used when the footnote is selected
+ this.innerView = null
+ }
+ selectNode() {
+ const attrs = { ...this.node.attrs };
+ attrs.visibility = true;
+ this.dom.classList.add("ProseMirror-selectednode")
+ if (!this.innerView) this.open()
+ }
+
+ deselectNode() {
+ const attrs = { ...this.node.attrs };
+ attrs.visibility = false;
+ this.dom.classList.remove("ProseMirror-selectednode")
+ if (this.innerView) this.close()
+ }
+ open() {
+ if (!(this.outerView as any).isOverlay) return;
+ // Append a tooltip to the outer node
+ let tooltip = this.dom.appendChild(document.createElement("div"))
+ tooltip.className = "footnote-tooltip";
+ // And put a sub-ProseMirror into that
+ this.innerView = new EditorView(tooltip, {
+ // You can use any node as an editor document
+ state: EditorState.create({
+ doc: this.node,
+ plugins: [keymap(baseKeymap),
+ keymap({
+ "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch),
+ "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch),
+ "Mod-b": toggleMark(schema.marks.strong)
+ })]
+ }),
+ // This is the magic part
+ dispatchTransaction: this.dispatchInner.bind(this),
+ handleDOMEvents: {
+ pointerdown: ((view: any, e: PointerEvent) => {
+ // Kludge to prevent issues due to the fact that the whole
+ // footnote is node-selected (and thus DOM-selected) when
+ // the parent editor is focused.
+ e.stopPropagation();
+ document.addEventListener("pointerup", this.ignore, true);
+ if (this.outerView.hasFocus()) this.innerView.focus();
+ }) as any
+ }
+
+ });
+ setTimeout(() => this.innerView && this.innerView.docView.setSelection(0, 0, this.innerView.root, true), 0);
+ }
+
+ ignore = (e: PointerEvent) => {
+ e.stopPropagation();
+ document.removeEventListener("pointerup", this.ignore, true);
+ }
+
+ toggle = () => {
+ if (this.innerView) this.close();
+ else {
+ this.open();
+
+ }
+ }
+ close() {
+ this.innerView && this.innerView.destroy()
+ this.innerView = null
+ this.dom.textContent = ""
+ }
+ dispatchInner(tr: any) {
+ let { state, transactions } = this.innerView.state.applyTransaction(tr)
+ this.innerView.updateState(state)
+
+ if (!tr.getMeta("fromOutside")) {
+ let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1)
+ for (let i = 0; i < transactions.length; i++) {
+ let steps = transactions[i].steps
+ for (let j = 0; j < steps.length; j++)
+ outerTr.step(steps[j].map(offsetMap))
+ }
+ if (outerTr.docChanged) this.outerView.dispatch(outerTr)
+ }
+ }
+ update(node: any) {
+ if (!node.sameMarkup(this.node)) return false
+ this.node = node
+ if (this.innerView) {
+ let state = this.innerView.state
+ let start = node.content.findDiffStart(state.doc.content)
+ if (start != null) {
+ let { a: endA, b: endB } = node.content.findDiffEnd(state.doc.content)
+ let overlap = start - Math.min(endA, endB)
+ if (overlap > 0) { endA += overlap; endB += overlap }
+ this.innerView.dispatch(
+ state.tr
+ .replace(start, endB, node.slice(start, endA))
+ .setMeta("fromOutside", true))
+ }
+ }
+ return true
+ }
+
+ destroy() {
+ if (this.innerView) this.close()
+ }
+
+ stopEvent(event: any) {
+ return this.innerView && this.innerView.dom.contains(event.target)
+ }
+
+ ignoreMutation() { return true }
+}
+
export class SummarizedView {
// TODO: highlight text that is summarized. to find end of region, walk along mark
_collapsed: HTMLElement;
@@ -550,6 +760,8 @@ export class SummarizedView {
attrs.textslice = newSelection.content().toJSON();
view.dispatch(view.state.tr.setNodeMarkup(y, undefined, attrs));
view.dispatch(view.state.tr.setSelection(newSelection).deleteSelection(view.state, () => { }));
+ let marks = view.state.storedMarks.filter((m: any) => m.type !== view.state.schema.marks.highlight);
+ view.state.storedMarks = marks;
self._collapsed.textContent = "㊉";
} else {
// node.attrs.visibility = !node.attrs.visibility;
@@ -583,7 +795,7 @@ export class SummarizedView {
let skip = false;
this._view.state.doc.nodesBetween(start, i, (node: Node, pos: number, parent: Node, index: number) => {
if (node.isLeaf && !visited.has(node) && !skip) {
- if (node.marks.includes(_mark)) {
+ if (node.marks.find((m: any) => m.type === _mark.type)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
}
diff --git a/src/client/util/TooltipLinkingMenu.tsx b/src/client/util/TooltipLinkingMenu.tsx
index 55e0eb909..e6d6c471f 100644
--- a/src/client/util/TooltipLinkingMenu.tsx
+++ b/src/client/util/TooltipLinkingMenu.tsx
@@ -1,23 +1,9 @@
-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 { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { schema } from "./RichTextSchema";
-import { Schema, NodeType, MarkType } from "prosemirror-model";
-import React = require("react");
+import { FieldViewProps } from "../views/nodes/FieldView";
import "./TooltipTextMenu.scss";
+import React = require("react");
const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
-import { library } from '@fortawesome/fontawesome-svg-core';
-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";
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 4672dd246..ce7b04e31 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -3,7 +3,7 @@ import { faListUl } from '@fortawesome/free-solid-svg-icons';
import { action, observable } from "mobx";
import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css
import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model";
-import { liftListItem, wrapInList } from 'prosemirror-schema-list';
+import { wrapInList } from 'prosemirror-schema-list';
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc, Field, Opt } from "../../new_fields/Doc";
@@ -28,11 +28,11 @@ export class TooltipTextMenu {
private view: EditorView;
private fontStyles: MarkType[];
private fontSizes: MarkType[];
- private listTypes: NodeType[];
+ private listTypes: (NodeType | any)[];
private editorProps: FieldViewProps & FormattedTextBoxProps;
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
- private listTypeToIcon: Map<NodeType, string>;
+ private listTypeToIcon: Map<NodeType | any, string>;
//private link: HTMLAnchorElement;
private wrapper: HTMLDivElement;
private extras: HTMLDivElement;
@@ -60,8 +60,6 @@ export class TooltipTextMenu {
@observable
private _storedMarks: Mark<any>[] | null | undefined;
- public HackToFixTextSelectionGlitch: boolean = false;
-
constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
this.view = view;
@@ -165,13 +163,22 @@ export class TooltipTextMenu {
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.fontSizeToNum.set(schema.marks.pFontSize, 12);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 14);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 16);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 18);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 20);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 24);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 32);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 48);
+ // this.fontSizeToNum.set(schema.marks.pFontSize, 72);
this.fontSizes = Array.from(this.fontSizeToNum.keys());
//list types
this.listTypeToIcon = new Map();
this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
- this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
+ this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1");
+ this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A");
// this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜");
this.listTypes = Array.from(this.listTypeToIcon.keys());
@@ -504,10 +511,28 @@ export class TooltipTextMenu {
//remove all node typeand apply the passed-in one to the selected text
changeToNodeType(nodeType: NodeType | undefined, view: EditorView) {
- //remove old
- liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
- if (nodeType) { //add new
+ //remove oldif (nodeType) { //add new
+ if (nodeType === schema.nodes.bullet_list) {
wrapInList(nodeType)(view.state, view.dispatch);
+ } else {
+ var ref = view.state.selection;
+ var range = ref.$from.blockRange(ref.$to);
+ var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks());
+ wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => {
+ const resolvedPos = tx2.doc.resolve(Math.round((range!.start + range!.end) / 2));
+ let path = resolvedPos.path;
+ for (let i = path.length - 1; i > 0; i--) {
+ if (path[i].type === schema.nodes.ordered_list) {
+ path[i].attrs.bulletStyle = 1;
+ path[i].attrs.mapStyle = (nodeType as any).attrs.mapStyle;
+ break;
+ }
+ }
+ marks && tx2.ensureMarks([...marks]);
+ marks && tx2.setStoredMarks([...marks]);
+
+ view.dispatch(tx2);
+ });
}
}
@@ -602,7 +627,7 @@ export class TooltipTextMenu {
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));
- this._brushMarks.forEach((mark: Mark) => {
+ Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => {
const markType = mark.type;
this.changeToMarkInGroup(markType, this.view, []);
@@ -849,8 +874,6 @@ export class TooltipTextMenu {
this.updateFontSizeDropdown("Various");
}
}
- !this.HackToFixTextSelectionGlitch &&
- this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks)); // bcz: what's the purpose of this line? It messes up text selection without the Hack.
this.update_mark_doms();
}
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
new file mode 100644
index 000000000..c273c2323
--- /dev/null
+++ b/src/client/util/prosemirrorPatches.js
@@ -0,0 +1,62 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+var prosemirrorTransform = require('prosemirror-transform');
+var prosemirrorModel = require('prosemirror-model');
+
+exports.liftListItem = liftListItem;
+// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
+// Create a command to lift the list item around the selection up into
+// a wrapping list.
+function liftListItem(itemType) {
+ return function (tx, dispatch) {
+ var ref = tx.selection;
+ var $from = ref.$from;
+ var $to = ref.$to;
+ var range = $from.blockRange($to, function (node) { return node.childCount && node.firstChild.type == itemType; });
+ if (!range) { return false }
+ if (!dispatch) { return true }
+ if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
+ { return liftToOuterList(tx, dispatch, itemType, range) }
+ else // Outer list node
+ { return liftOutOfList(tx, dispatch, range) }
+ }
+}
+
+function liftToOuterList(tr, dispatch, itemType, range) {
+ var end = range.end, endOfList = range.$to.end(range.depth);
+ if (end < endOfList) {
+ // There are siblings after the lifted items, which must become
+ // children of the last item
+ tr.step(new prosemirrorTransform.ReplaceAroundStep(end - 1, endOfList, end, endOfList,
+ new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true));
+ range = new prosemirrorModel.NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth);
+ }
+ dispatch(tr.lift(range, prosemirrorTransform.liftTarget(range)).scrollIntoView());
+ return true
+}
+
+function liftOutOfList(tr, dispatch, range) {
+ var list = range.parent;
+ // Merge the list items into a single big item
+ for (var pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
+ pos -= list.child(i).nodeSize;
+ tr.delete(pos - 1, pos + 1);
+ }
+ var $start = tr.doc.resolve(range.start), item = $start.nodeAfter;
+ var atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount;
+ var parent = $start.node(-1), indexBefore = $start.index(-1);
+ if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
+ item.content.append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list)))) { return false }
+ var start = $start.pos, end = start + item.nodeSize;
+ // Strip off the surrounding list. At the sides where we're not at
+ // the end of the list, the existing list is closed. At sides where
+ // this is the end, it is overwritten to its end.
+ tr.step(new prosemirrorTransform.ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
+ new prosemirrorModel.Slice((atStart ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty)))
+ .append(atEnd ? prosemirrorModel.Fragment.empty : prosemirrorModel.Fragment.from(list.copy(prosemirrorModel.Fragment.empty))),
+ atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1));
+ dispatch(tr.scrollIntoView());
+ return true
+} \ No newline at end of file