aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DocumentManager.ts99
-rw-r--r--src/client/util/DragManager.ts190
-rw-r--r--src/client/util/RichTextRules.ts43
-rw-r--r--src/client/util/RichTextSchema.tsx23
-rw-r--r--src/client/util/Scripting.ts10
-rw-r--r--src/client/util/SelectionManager.ts15
-rw-r--r--src/client/util/TooltipTextMenu.scss7
-rw-r--r--src/client/util/TooltipTextMenu.tsx38
-rw-r--r--src/client/util/UndoManager.ts57
-rw-r--r--src/client/util/jsx-decl.d.ts1
-rw-r--r--src/client/util/type_decls.d10
11 files changed, 386 insertions, 107 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
new file mode 100644
index 000000000..bf59fbb43
--- /dev/null
+++ b/src/client/util/DocumentManager.ts
@@ -0,0 +1,99 @@
+import React = require('react')
+import { observer } from 'mobx-react';
+import { observable, action, computed } from 'mobx';
+import { Document } from "../../fields/Document"
+import { DocumentView } from '../views/nodes/DocumentView';
+import { KeyStore } from '../../fields/KeyStore';
+import { FieldWaiting } from '../../fields/Field';
+import { ListField } from '../../fields/ListField';
+
+
+export class DocumentManager {
+
+ //global holds all of the nodes (regardless of which collection they're in)
+ @observable
+ public DocumentViews: DocumentView[] = [];
+
+ // singleton instance
+ private static _instance: DocumentManager;
+
+ // create one and only one instance of NodeManager
+ public static get Instance(): DocumentManager {
+ return this._instance || (this._instance = new this());
+ }
+
+ //private constructor so no other class can create a nodemanager
+ private constructor() {
+ // this.DocumentViews = new Array<DocumentView>();
+ }
+
+ public getAllDocumentViews(collection: Document) {
+ return this.DocumentViews.filter(dv =>
+ dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document == collection);
+ }
+
+ public getDocumentView(toFind: Document): DocumentView | null {
+
+ let toReturn: DocumentView | null;
+ toReturn = null;
+
+ //gets document view that is in a freeform canvas collection
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document;
+ // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
+
+ if (Object.is(doc, toFind)) {
+ toReturn = view;
+ return;
+ }
+ let docSrc = doc.GetT(KeyStore.Prototype, Document);
+ if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) {
+ toReturn = view;
+ }
+ })
+
+ return (toReturn);
+ }
+ public getDocumentViews(toFind: Document): DocumentView[] {
+
+ let toReturn: DocumentView[] = [];
+
+ //gets document view that is in a freeform canvas collection
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document;
+ // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
+
+ if (Object.is(doc, toFind)) {
+ toReturn.push(view);
+ } else {
+ let docSrc = doc.GetT(KeyStore.Prototype, Document);
+ if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) {
+ toReturn.push(view);
+ }
+ }
+ })
+
+ return (toReturn);
+ }
+
+ @computed
+ public get LinkedDocumentViews() {
+ return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
+ let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField);
+ if (linksList && linksList != FieldWaiting && linksList.Data.length) {
+ pairs.push(...linksList.Data.reduce((pairs, link) => {
+ if (link instanceof Document) {
+ let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document);
+ if (linkToDoc && linkToDoc != FieldWaiting) {
+ DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
+ pairs.push({ a: dv, b: docView1, l: link })
+ })
+ }
+ }
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Document }[]));
+ }
+ return pairs;
+ }, [] as { a: DocumentView, b: DocumentView, l: Document }[]);
+ }
+} \ No newline at end of file
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 513a6ac9e..9ffe964ef 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -2,18 +2,21 @@ import { DocumentDecorations } from "../views/DocumentDecorations";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { Document } from "../../fields/Document"
import { action } from "mobx";
-import { DocumentView } from "../views/nodes/DocumentView";
import { ImageField } from "../../fields/ImageField";
import { KeyStore } from "../../fields/KeyStore";
+import { CollectionView } from "../views/collections/CollectionView";
+import { DocumentView } from "../views/nodes/DocumentView";
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document) {
+export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) {
let onRowMove = action((e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- DragManager.StartDrag(_reference.current!, { document: docFunc() });
+ var dragData = new DragManager.DocumentDragData([docFunc()]);
+ dragData.removeDocument = removeFunc;
+ DragManager.StartDocumentDrag([_reference.current!], dragData);
});
let onRowUp = action((e: PointerEvent): void => {
document.removeEventListener("pointermove", onRowMove);
@@ -24,7 +27,7 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
if (e.button == 0) {
e.stopPropagation();
if (e.shiftKey) {
- CollectionDockingView.Instance.StartOtherDrag(docFunc(), e);
+ CollectionDockingView.Instance.StartOtherDrag([docFunc()], e);
} else {
document.addEventListener("pointermove", onRowMove);
document.addEventListener('pointerup', onRowUp);
@@ -70,11 +73,12 @@ export namespace DragManager {
export interface DropOptions {
handlers: DropHandlers;
}
-
export class DropEvent {
constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
}
+
+
export interface DropHandlers {
drop: (e: Event, de: DropEvent) => void;
}
@@ -96,46 +100,84 @@ export namespace DragManager {
};
}
- export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) {
- DocumentDecorations.Instance.Hidden = true;
+ export class DocumentDragData {
+ constructor(dragDoc: Document[]) {
+ this.draggedDocuments = dragDoc;
+ this.droppedDocuments = dragDoc;
+ }
+ draggedDocuments: Document[];
+ droppedDocuments: Document[];
+ xOffset?: number;
+ yOffset?: number;
+ aliasOnDrop?: boolean;
+ removeDocument?: (collectionDrop: CollectionView) => void;
+ [id: string]: any;
+ }
+
+ export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, options?: DragOptions) {
+ StartDrag(eles, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.draggedDocuments);
+ }
+
+ export class LinkDragData {
+ constructor(linkSourceDoc: DocumentView) {
+ this.linkSourceDocumentView = linkSourceDoc;
+ }
+ linkSourceDocumentView: DocumentView;
+ [id: string]: any;
+ }
+ export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) {
+ StartDrag([ele], dragData, options);
+ }
+ function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
if (!dragDiv) {
dragDiv = document.createElement("div");
+ dragDiv.className = "dragManager-dragDiv"
DragManager.Root().appendChild(dragDiv);
}
- const w = ele.offsetWidth, h = ele.offsetHeight;
- const rect = ele.getBoundingClientRect();
- const scaleX = rect.width / w, scaleY = rect.height / h;
- let x = rect.left, y = rect.top;
- // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
-
- let dragElement = ele.cloneNode(true) as HTMLElement;
- dragElement.style.opacity = "0.7";
- dragElement.style.position = "absolute";
- dragElement.style.bottom = "";
- dragElement.style.left = "";
- dragElement.style.transformOrigin = "0 0";
- dragElement.style.zIndex = "1000";
- dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
- dragElement.style.width = `${rect.width / scaleX}px`;
- dragElement.style.height = `${rect.height / scaleY}px`;
-
- // bcz: PDFs don't show up if you clone them because they contain a canvas.
- // however, PDF's have a thumbnail field that contains an image of their canvas.
- // So we replace the pdf's canvas with the image thumbnail
- const docView: DocumentView = dragData["documentView"];
- const doc: Document = docView ? docView.props.Document : dragData["document"];
- var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
- let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField);
- if (pdfBox && pdfBox.childElementCount && thumbnail) {
- let img = new Image();
- img!.src = thumbnail.toString();
- img!.style.position = "absolute";
- img!.style.width = `${rect.width / scaleX}px`;
- img!.style.height = `${rect.height / scaleY}px`;
- pdfBox.replaceChild(img!, pdfBox.children[0])
- }
- dragDiv.appendChild(dragElement);
+ let scaleXs: number[] = [];
+ let scaleYs: number[] = [];
+ let xs: number[] = [];
+ let ys: number[] = [];
+
+ const docs: Document[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
+ let dragElements = eles.map(ele => {
+ const w = ele.offsetWidth, h = ele.offsetHeight;
+ const rect = ele.getBoundingClientRect();
+ const scaleX = rect.width / w, scaleY = rect.height / h;
+ let x = rect.left, y = rect.top;
+ xs.push(x); ys.push(y);
+ scaleXs.push(scaleX); scaleYs.push(scaleY);
+ let dragElement = ele.cloneNode(true) as HTMLElement;
+ dragElement.style.opacity = "0.7";
+ dragElement.style.position = "absolute";
+ dragElement.style.bottom = "";
+ dragElement.style.left = "";
+ dragElement.style.transformOrigin = "0 0";
+ dragElement.style.zIndex = "1000";
+ dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragElement.style.width = `${rect.width / scaleX}px`;
+ dragElement.style.height = `${rect.height / scaleY}px`;
+
+ // bcz: PDFs don't show up if you clone them because they contain a canvas.
+ // however, PDF's have a thumbnail field that contains an image of their canvas.
+ // So we replace the pdf's canvas with the image thumbnail
+ if (docs.length) {
+ var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
+ let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField);
+ if (pdfBox && pdfBox.childElementCount && thumbnail) {
+ let img = new Image();
+ img!.src = thumbnail.toString();
+ img!.style.position = "absolute";
+ img!.style.width = `${rect.width / scaleX}px`;
+ img!.style.height = `${rect.height / scaleY}px`;
+ pdfBox.replaceChild(img!, pdfBox.children[0])
+ }
+ }
+
+ dragDiv.appendChild(dragElement);
+ return dragElement;
+ });
let hideSource = false;
if (options) {
@@ -145,58 +187,64 @@ export namespace DragManager {
hideSource = options.hideSource();
}
}
- const wasHidden = ele.hidden;
- if (hideSource) {
- ele.hidden = true;
- }
+ eles.map(ele => ele.hidden = hideSource);
+
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- x += e.movementX;
- y += e.movementY;
+ if (dragData instanceof DocumentDragData)
+ dragData.aliasOnDrop = e.ctrlKey || e.altKey;
if (e.shiftKey) {
abortDrag();
- CollectionDockingView.Instance.StartOtherDrag(doc, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
+ CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
}
- dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragElements.map((dragElement, i) => dragElement.style.transform = `translate(${xs[i] += e.movementX}px, ${ys[i] += e.movementY}px) scale(${scaleXs[i]}, ${scaleYs[i]})`);
};
const abortDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- dragDiv.removeChild(dragElement);
- if (hideSource && !wasHidden) {
- ele.hidden = false;
- }
+ dragElements.map(dragElement => dragDiv.removeChild(dragElement));
+ eles.map(ele => ele.hidden = false);
}
const upHandler = (e: PointerEvent) => {
abortDrag();
- FinishDrag(ele, e, dragData, options);
+ FinishDrag(eles, e, dragData, options, finishDrag);
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
- function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) {
- let parent = dragEle.parentElement;
- if (parent)
- parent.removeChild(dragEle);
+ function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
+ let removed = dragEles.map(dragEle => {
+ let parent = dragEle.parentElement;
+ if (parent)
+ parent.removeChild(dragEle);
+ return [dragEle, parent];
+ });
const target = document.elementFromPoint(e.x, e.y);
- if (parent)
- parent.appendChild(dragEle);
- if (!target) {
- return;
- }
- target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
- bubbles: true,
- detail: {
- x: e.x,
- y: e.y,
- data: dragData
+ removed.map(r => {
+ let dragEle: HTMLElement = r[0]!;
+ let parent: HTMLElement | null = r[1];
+ if (parent)
+ parent.appendChild(dragEle);
+ });
+ if (target) {
+ if (finishDrag)
+ finishDrag(dragData);
+
+ target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
+ bubbles: true,
+ detail: {
+ x: e.x,
+ y: e.y,
+ data: dragData
+ }
+ }));
+
+ if (options) {
+ options.handlers.dragComplete({});
}
- }));
- if (options) {
- options.handlers.dragComplete({});
}
DocumentDecorations.Instance.Hidden = false;
}
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
new file mode 100644
index 000000000..3b8396510
--- /dev/null
+++ b/src/client/util/RichTextRules.ts
@@ -0,0 +1,43 @@
+import {
+ inputRules,
+ wrappingInputRule,
+ textblockTypeInputRule,
+ smartQuotes,
+ emDash,
+ ellipsis
+} from "prosemirror-inputrules";
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+
+import { schema } from "./RichTextSchema";
+
+export const inpRules = {
+ rules: [
+ ...smartQuotes,
+ ellipsis,
+ emDash,
+
+ // > blockquote
+ wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
+
+ // 1. ordered list
+ wrappingInputRule(
+ /^(\d+)\.\s$/,
+ schema.nodes.ordered_list,
+ match => ({ order: +match[1] }),
+ (match, node) => node.childCount + node.attrs.order === +match[1]
+ ),
+
+ // * bullet list
+ wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
+
+ // ``` code block
+ textblockTypeInputRule(/^```$/, schema.nodes.code_block),
+
+ // # heading
+ textblockTypeInputRule(
+ new RegExp("^(#{1,6})\\s$"),
+ schema.nodes.heading,
+ match => ({ level: match[1].length })
+ )
+ ]
+};
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index abf448c9f..2a3c1da6e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,12 +1,15 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray } from "prosemirror-model"
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"
import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'
import { redo, undo } from 'prosemirror-history'
-import { orderedList, bulletList, listItem } from 'prosemirror-schema-list'
+import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list'
+import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
+import { EditorView, } from "prosemirror-view";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]
+
// :: Object
// [Specs](#model.NodeSpec) for the nodes defined in this schema.
export const nodes: { [index: string]: NodeSpec } = {
@@ -113,12 +116,22 @@ export const nodes: { [index: string]: NodeSpec } = {
content: 'list_item+',
group: 'block'
},
+ //this doesn't currently work for some reason
bullet_list: {
+ ...bulletList,
content: 'list_item+',
group: 'block',
- parseDOM: [{ tag: "ul" }, { style: "list-style-type=disc;" }],
- toDOM() { return ulDOM }
- },
+ // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
+ // toDOM() { return ulDOM }
+ },
+ //bullet_list: {
+ // content: 'list_item+',
+ // group: 'block',
+ //active: blockActive(schema.nodes.bullet_list),
+ //enable: wrapInList(schema.nodes.bullet_list),
+ //run: wrapInList(schema.nodes.bullet_list),
+ //select: state => true,
+ // },
list_item: {
...listItem,
content: 'paragraph block*'
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 46bd1a206..4e97b9401 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -15,6 +15,8 @@ import { ListField } from "../../fields/ListField";
// @ts-ignore
import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import { Documents } from "../documents/Documents";
+import { Key } from "../../fields/Key";
export interface ExecutableScript {
@@ -28,9 +30,9 @@ function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [
let func: () => Opt<Field>;
if (compiled && script) {
- let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField];
- let paramNames = ["KeyStore", ...fieldTypes.map(fn => fn.name)];
- let params: any[] = [KeyStore, ...fieldTypes]
+ let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
+ let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
+ let params: any[] = [KeyStore, Documents, ...fieldTypes]
for (let prop in scope) {
if (prop === "this") {
continue;
@@ -110,7 +112,7 @@ export function CompileScript(script: string, scope?: { [name: string]: any }, a
let host = new ScriptingCompilerHost;
let funcScript = `(function() {
${addReturn ? `return ${script};` : script}
- })()`
+ }).apply(this)`
host.writeFile("file.ts", funcScript);
host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
let program = ts.createProgram(["file.ts"], {}, host);
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1a711ae64..1354e32e1 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,5 +1,6 @@
import { observable, action } from "mobx";
import { DocumentView } from "../views/nodes/DocumentView";
+import { Document } from "../../fields/Document"
export namespace SelectionManager {
class Manager {
@@ -29,8 +30,18 @@ export namespace SelectionManager {
return manager.SelectedDocuments.indexOf(doc) !== -1;
}
- export function DeselectAll(): void {
- manager.SelectedDocuments = []
+ export function DeselectAll(except?: Document): void {
+ let found: DocumentView | undefined = undefined;
+ if (except) {
+ for (let i = 0; i < manager.SelectedDocuments.length; i++) {
+ let view = manager.SelectedDocuments[i];
+ if (view.props.Document == except)
+ found = view;
+ }
+ }
+ manager.SelectedDocuments.length = 0;
+ if (found)
+ manager.SelectedDocuments.push(found);
}
export function SelectedDocuments(): Array<DocumentView> {
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index fa43f5326..ea580d104 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,8 +1,9 @@
+@import "../views/global_variables";
.tooltipMenu {
position: absolute;
z-index: 20;
- background: rgb(19, 18, 18);
+ background: $dark-color;
border: 1px solid silver;
border-radius: 4px;
padding: 2px 10px;
@@ -31,14 +32,14 @@
bottom: -4.5px;
border: 5px solid transparent;
border-bottom-width: 0;
- border-top-color: black;
+ border-top-color: $dark-color;
}
.menuicon {
display: inline-block;
border-right: 1px solid rgba(0, 0, 0, 0.2);
//color: rgb(19, 18, 18);
- color: white;
+ color: $light-color;
line-height: 1;
padding: 0px 2px;
margin: 1px;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 3b87fe9de..2a613ba8b 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -2,10 +2,10 @@ import { action, IReactionDisposer, reaction } from "mobx";
import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-const { exampleSetup } = require("prosemirror-example-setup")
-import { EditorState, Transaction, } from "prosemirror-state";
+import { EditorState, Transaction, NodeSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
+import { Schema, NodeType } from "prosemirror-model"
import React = require("react")
import "./TooltipTextMenu.scss";
const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
@@ -16,7 +16,7 @@ import {
} from '@fortawesome/free-solid-svg-icons';
-
+//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
private tooltip: HTMLElement;
@@ -39,7 +39,8 @@ 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") },
- { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }
+ //this doesn't work currently - look into notion of active block
+ { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
]
items.forEach(({ dom }) => this.tooltip.appendChild(dom));
@@ -49,7 +50,9 @@ export class TooltipTextMenu {
view.focus();
items.forEach(({ command, dom }) => {
if (dom.contains(e.srcElement)) {
- command(view.state, view.dispatch, view)
+ 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"
}
})
})
@@ -66,13 +69,25 @@ export class TooltipTextMenu {
return span;
}
- blockActive(view: EditorView) {
- const { $from, to } = view.state.selection
+ //adapted this method - use it to check if block has a tag (ie bulleting)
+ blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
+ let attrs = {};
+
+ if (state.selection instanceof NodeSelection) {
+ const sel: NodeSelection = state.selection;
+ let $from = sel.$from;
+ let to = sel.to;
+ let node = sel.node;
+
+ if (node) {
+ return node.hasMarkup(type, attrs);
+ }
- return to <= $from.end() && $from.parent.hasMarkup(schema.nodes.bulletList);
+ return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
+ }
}
- //this doesn't currently work but hopefully will soon
+ //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");
@@ -105,8 +120,6 @@ export class TooltipTextMenu {
// Otherwise, reposition it and update its content
this.tooltip.style.display = ""
let { from, to } = state.selection
- // These are in screen coordinates
- //check this - tranform
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()
@@ -116,8 +129,9 @@ export class TooltipTextMenu {
this.tooltip.style.left = (left - box.left) + "px"
let width = Math.abs(start.left - end.left) / 2;
let mid = Math.min(start.left, end.left) + width;
+
//THIS WIDTH IS 15 * NUMBER OF ICONS + 15
- this.tooltip.style.width = 120 + "px";
+ this.tooltip.style.width = 122 + "px";
this.tooltip.style.bottom = (box.bottom - start.top) + "px";
}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 46ad558f3..6d1b2f1b8 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,4 +1,14 @@
import { observable, action } from "mobx";
+import 'source-map-support/register'
+import { Without } from "../../Utils";
+
+function getBatchName(target: any, key: string | symbol): string {
+ let keyName = key.toString();
+ if (target && target.constructor && target.constructor.name) {
+ return `${target.constructor.name}.${keyName}`;
+ }
+ return keyName;
+}
function propertyDecorator(target: any, key: string | symbol) {
Object.defineProperty(target, key, {
@@ -13,11 +23,11 @@ function propertyDecorator(target: any, key: string | symbol) {
writable: true,
configurable: true,
value: function (...args: any[]) {
+ let batch = UndoManager.StartBatch(getBatchName(target, key));
try {
- UndoManager.StartBatch();
return value.apply(this, args);
} finally {
- UndoManager.EndBatch();
+ batch.end();
}
}
})
@@ -32,11 +42,11 @@ export function undoBatch(target: any, key: string | symbol, descriptor?: TypedP
const oldFunction = descriptor.value;
descriptor.value = function (...args: any[]) {
+ let batch = UndoManager.StartBatch(getBatchName(target, key));
try {
- UndoManager.StartBatch()
return oldFunction.apply(this, args)
} finally {
- UndoManager.EndBatch()
+ batch.end();
}
}
@@ -70,26 +80,53 @@ export namespace UndoManager {
return redoStack.length > 0;
}
- export function StartBatch(): void {
+ let openBatches: Batch[] = [];
+ export function GetOpenBatches(): Without<Batch, 'end'>[] {
+ return openBatches;
+ }
+ export class Batch {
+ private disposed: boolean = false;
+
+ constructor(readonly batchName: string) {
+ openBatches.push(this);
+ }
+
+ private dispose = (cancel: boolean) => {
+ if (this.disposed) {
+ throw new Error("Cannot dispose an already disposed batch");
+ }
+ this.disposed = true;
+ openBatches.splice(openBatches.indexOf(this));
+ EndBatch(cancel);
+ }
+
+ end = () => { this.dispose(false); }
+ cancel = () => { this.dispose(true); }
+ }
+
+ export function StartBatch(batchName: string): Batch {
batchCounter++;
if (batchCounter > 0) {
currentBatch = [];
}
+ return new Batch(batchName);
}
- export const EndBatch = action(() => {
+ const EndBatch = action((cancel: boolean = false) => {
batchCounter--;
if (batchCounter === 0 && currentBatch && currentBatch.length) {
- undoStack.push(currentBatch);
+ if (!cancel) {
+ undoStack.push(currentBatch);
+ }
redoStack.length = 0;
currentBatch = undefined;
}
})
- export function RunInBatch(fn: () => void) {
- StartBatch();
+ export function RunInBatch(fn: () => void, batchName: string) {
+ let batch = StartBatch(batchName);
fn();
- EndBatch();
+ batch.end();
}
export const Undo = action(() => {
diff --git a/src/client/util/jsx-decl.d.ts b/src/client/util/jsx-decl.d.ts
new file mode 100644
index 000000000..532f06178
--- /dev/null
+++ b/src/client/util/jsx-decl.d.ts
@@ -0,0 +1 @@
+declare module 'react-jsx-parser';
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 679f73f42..4f69053b1 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -174,6 +174,7 @@ declare class ListField<T> extends BasicField<T[]>{
Copy(): Field;
}
declare class Key extends Field {
+ constructor(name:string);
Name: string;
TrySetValue(value: any): boolean;
GetValue(): any;
@@ -213,3 +214,12 @@ declare class Document extends Field {
GetAllPrototypes(): Document[];
MakeDelegate(): Document;
}
+
+declare const KeyStore: {
+ [name: string]: Key;
+}
+
+// @ts-ignore
+declare const console: any;
+
+declare const Documents: any;