aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DocumentManager.ts26
-rw-r--r--src/client/util/DragManager.ts183
-rw-r--r--src/client/util/RichTextSchema.tsx434
-rw-r--r--src/client/util/Scripting.ts114
-rw-r--r--src/client/util/ScrollBox.tsx4
-rw-r--r--src/client/util/SelectionManager.ts33
-rw-r--r--src/client/util/TooltipTextMenu.tsx260
-rw-r--r--src/client/util/Transform.ts56
-rw-r--r--src/client/util/TypedEvent.ts4
-rw-r--r--src/client/util/UndoManager.ts50
-rw-r--r--src/client/util/type_decls.d2
11 files changed, 610 insertions, 556 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index bf59fbb43..f38b8ca75 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
-import React = require('react')
+import React = require('react');
import { observer } from 'mobx-react';
import { observable, action, computed } from 'mobx';
-import { Document } from "../../fields/Document"
+import { Document } from "../../fields/Document";
import { DocumentView } from '../views/nodes/DocumentView';
import { KeyStore } from '../../fields/KeyStore';
import { FieldWaiting } from '../../fields/Field';
@@ -29,7 +29,7 @@ export class DocumentManager {
public getAllDocumentViews(collection: Document) {
return this.DocumentViews.filter(dv =>
- dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document == collection);
+ dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection);
}
public getDocumentView(toFind: Document): DocumentView | null {
@@ -42,15 +42,15 @@ export class DocumentManager {
let doc = view.props.Document;
// if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- if (Object.is(doc, toFind)) {
+ if (doc === toFind) {
toReturn = view;
return;
}
let docSrc = doc.GetT(KeyStore.Prototype, Document);
- if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) {
+ if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
toReturn = view;
}
- })
+ });
return (toReturn);
}
@@ -63,15 +63,15 @@ export class DocumentManager {
let doc = view.props.Document;
// if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) {
- if (Object.is(doc, toFind)) {
+ if (doc === toFind) {
toReturn.push(view);
} else {
let docSrc = doc.GetT(KeyStore.Prototype, Document);
- if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) {
+ if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
toReturn.push(view);
}
}
- })
+ });
return (toReturn);
}
@@ -80,14 +80,14 @@ export class DocumentManager {
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) {
+ 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) {
+ if (linkToDoc && linkToDoc !== FieldWaiting) {
DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => {
- pairs.push({ a: dv, b: docView1, l: link })
- })
+ pairs.push({ a: dv, b: docView1, l: link });
+ });
}
}
return pairs;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9ffe964ef..4849ae9f7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,13 +1,12 @@
-import { DocumentDecorations } from "../views/DocumentDecorations";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
-import { Document } from "../../fields/Document"
import { action } from "mobx";
-import { ImageField } from "../../fields/ImageField";
-import { KeyStore } from "../../fields/KeyStore";
+import { Document } from "../../fields/Document";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { CollectionView } from "../views/collections/CollectionView";
+import { DocumentDecorations } from "../views/DocumentDecorations";
import { DocumentView } from "../views/nodes/DocumentView";
+import { returnFalse } from "../../Utils";
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) {
+export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) {
let onRowMove = action((e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
@@ -15,8 +14,9 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
var dragData = new DragManager.DocumentDragData([docFunc()]);
- dragData.removeDocument = removeFunc;
- DragManager.StartDocumentDrag([_reference.current!], dragData);
+ dragData.copyOnDrop = copyOnDrop;
+ dragData.moveDocument = moveFunc;
+ DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y);
});
let onRowUp = action((e: PointerEvent): void => {
document.removeEventListener("pointermove", onRowMove);
@@ -24,17 +24,17 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
});
let onItemDown = (e: React.PointerEvent) => {
// if (this.props.isSelected() || this.props.isTopMost) {
- if (e.button == 0) {
+ if (e.button === 0) {
e.stopPropagation();
if (e.shiftKey) {
CollectionDockingView.Instance.StartOtherDrag([docFunc()], e);
} else {
document.addEventListener("pointermove", onRowMove);
- document.addEventListener('pointerup', onRowUp);
+ document.addEventListener("pointerup", onRowUp);
}
}
//}
- }
+ };
return onItemDown;
}
@@ -50,7 +50,9 @@ export namespace DragManager {
let dragDiv: HTMLDivElement;
export enum DragButtons {
- Left = 1, Right = 2, Both = Left | Right
+ Left = 1,
+ Right = 2,
+ Both = Left | Right
}
interface DragOptions {
@@ -63,8 +65,7 @@ export namespace DragManager {
(): void;
}
- export class DragCompleteEvent {
- }
+ export class DragCompleteEvent { }
export interface DragHandlers {
dragComplete: (e: DragCompleteEvent) => void;
@@ -74,21 +75,27 @@ export namespace DragManager {
handlers: DropHandlers;
}
export class DropEvent {
- constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
+ constructor(
+ readonly x: number,
+ readonly y: number,
+ readonly data: { [id: string]: any }
+ ) { }
}
-
-
export interface DropHandlers {
drop: (e: Event, de: DropEvent) => void;
}
-
- export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
+ export function MakeDropTarget(
+ element: HTMLElement,
+ options: DropOptions
+ ): DragDropDisposer {
if ("canDrop" in element.dataset) {
- throw new Error("Element is already droppable, can't make it droppable again");
+ throw new Error(
+ "Element is already droppable, can't make it droppable again"
+ );
}
- element.dataset["canDrop"] = "true";
+ element.dataset.canDrop = "true";
const handler = (e: Event) => {
const ce = e as CustomEvent<DropEvent>;
options.handlers.drop(e, ce.detail);
@@ -96,10 +103,11 @@ export namespace DragManager {
element.addEventListener("dashOnDrop", handler);
return () => {
element.removeEventListener("dashOnDrop", handler);
- delete element.dataset["canDrop"]
+ delete element.dataset.canDrop;
};
}
+ export type MoveFunction = (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
export class DocumentDragData {
constructor(dragDoc: Document[]) {
this.draggedDocuments = dragDoc;
@@ -110,28 +118,33 @@ export namespace DragManager {
xOffset?: number;
yOffset?: number;
aliasOnDrop?: boolean;
- removeDocument?: (collectionDrop: CollectionView) => void;
+ copyOnDrop?: boolean;
+ moveDocument?: MoveFunction;
[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 function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag(eles, dragData, downX, downY, options,
+ (dropData: { [id: string]: any }) => (dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => d.Copy(true) as Document) : dragData.draggedDocuments));
}
export class LinkDragData {
constructor(linkSourceDoc: DocumentView) {
this.linkSourceDocumentView = linkSourceDoc;
}
+ droppedDocuments: Document[] = [];
linkSourceDocumentView: DocumentView;
[id: string]: any;
}
- export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) {
- StartDrag([ele], dragData, options);
+
+ export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag([ele], dragData, downX, downY, options);
}
- function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
+
+ function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
if (!dragDiv) {
dragDiv = document.createElement("div");
- dragDiv.className = "dragManager-dragDiv"
+ dragDiv.className = "dragManager-dragDiv";
DragManager.Root().appendChild(dragDiv);
}
@@ -140,40 +153,49 @@ export namespace DragManager {
let xs: number[] = [];
let ys: number[] = [];
- const docs: Document[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
+ const docs: Document[] =
+ dragData instanceof DocumentDragData ? dragData.draggedDocuments : [];
let dragElements = eles.map(ele => {
- const w = ele.offsetWidth, h = ele.offsetHeight;
+ 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);
+ 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.margin = "0";
+ dragElement.style.top = "0";
dragElement.style.bottom = "";
- dragElement.style.left = "";
+ dragElement.style.left = "0";
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.
+ // bcz: if PDFs are rendered with svg's, then this code isn't needed
+ // bcz: PDFs don't show up if you clone them when rendered using 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])
- }
- }
+ // 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;
@@ -187,26 +209,42 @@ export namespace DragManager {
hideSource = options.hideSource();
}
}
- eles.map(ele => ele.hidden = hideSource);
+ eles.map(ele => (ele.hidden = hideSource));
+ let lastX = downX;
+ let lastY = downY;
const moveHandler = (e: PointerEvent) => {
e.stopPropagation();
e.preventDefault();
- if (dragData instanceof DocumentDragData)
+ if (dragData instanceof DocumentDragData) {
dragData.aliasOnDrop = e.ctrlKey || e.altKey;
+ }
if (e.shiftKey) {
abortDrag();
- CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 });
+ CollectionDockingView.Instance.StartOtherDrag(docs, {
+ pageX: e.pageX,
+ pageY: e.pageY,
+ preventDefault: () => { },
+ button: 0
+ });
}
- dragElements.map((dragElement, i) => dragElement.style.transform = `translate(${xs[i] += e.movementX}px, ${ys[i] += e.movementY}px) scale(${scaleXs[i]}, ${scaleYs[i]})`);
+ //TODO: Why can't we use e.movementX and e.movementY?
+ let moveX = e.pageX - lastX;
+ let moveY = e.pageY - lastY;
+ lastX = e.pageX;
+ lastY = e.pageY;
+ dragElements.map((dragElement, i) => (dragElement.style.transform =
+ `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)
+ scale(${scaleXs[i]}, ${scaleYs[i]})`)
+ );
};
const abortDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
dragElements.map(dragElement => dragDiv.removeChild(dragElement));
- eles.map(ele => ele.hidden = false);
- }
+ eles.map(ele => (ele.hidden = false));
+ };
const upHandler = (e: PointerEvent) => {
abortDrag();
FinishDrag(eles, e, dragData, options, finishDrag);
@@ -218,29 +256,28 @@ export namespace DragManager {
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);
+ if (parent) parent.removeChild(dragEle);
return [dragEle, parent];
});
const target = document.elementFromPoint(e.x, e.y);
removed.map(r => {
- let dragEle: HTMLElement = r[0]!;
- let parent: HTMLElement | null = r[1];
- if (parent)
- parent.appendChild(dragEle);
+ let dragEle = r[0];
+ let parent = r[1];
+ if (parent && dragEle) 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 (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({});
@@ -248,4 +285,4 @@ export namespace DragManager {
}
DocumentDecorations.Instance.Hidden = false;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 489c22a57..290e26dc3 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,141 +1,141 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-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 { 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 { 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]
+ 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 } = {
- // :: NodeSpec The top level document node.
- doc: {
- content: "block+"
- },
-
- // :: NodeSpec A plain paragraph textblock. Represented in the DOM
- // as a `<p>` element.
- paragraph: {
- content: "inline*",
- group: "block",
- parseDOM: [{ tag: "p" }],
- toDOM() { return pDOM }
- },
-
- // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
- blockquote: {
- content: "block+",
- group: "block",
- defining: true,
- parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM }
- },
-
- // :: NodeSpec A horizontal rule (`<hr>`).
- horizontal_rule: {
- group: "block",
- parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM }
- },
-
- // :: NodeSpec A heading textblock, with a `level` attribute that
- // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
- // `<h6>` elements.
- heading: {
- attrs: { level: { default: 1 } },
- content: "inline*",
- group: "block",
- defining: true,
- parseDOM: [{ tag: "h1", attrs: { level: 1 } },
- { tag: "h2", attrs: { level: 2 } },
- { tag: "h3", attrs: { level: 3 } },
- { tag: "h4", attrs: { level: 4 } },
- { tag: "h5", attrs: { level: 5 } },
- { tag: "h6", attrs: { level: 6 } }],
- toDOM(node: any) { return ["h" + node.attrs.level, 0] }
- },
-
- // :: NodeSpec A code listing. Disallows marks or non-text inline
- // nodes by default. Represented as a `<pre>` element with a
- // `<code>` element inside of it.
- code_block: {
- content: "text*",
- marks: "",
- group: "block",
- code: true,
- defining: true,
- parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM }
- },
-
- // :: NodeSpec The text node.
- text: {
- group: "inline"
- },
-
- // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
- // `alt`, and `href` attributes. The latter two default to the empty
- // string.
- image: {
- inline: true,
- attrs: {
- src: {},
- alt: { default: null },
- title: { default: null }
- },
- group: "inline",
- draggable: true,
- parseDOM: [{
- tag: "img[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt")
- }
- }
- }],
- toDOM(node: any) { return ["img", node.attrs] }
- },
-
- // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
- hard_break: {
- inline: true,
- group: "inline",
- selectable: false,
- parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM }
- },
-
- ordered_list: {
- ...orderedList,
- 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 }
- },
- //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*'
- }
-}
+ // :: NodeSpec The top level document node.
+ doc: {
+ content: "block+"
+ },
+
+ // :: NodeSpec A plain paragraph textblock. Represented in the DOM
+ // as a `<p>` element.
+ paragraph: {
+ content: "inline*",
+ group: "block",
+ parseDOM: [{ tag: "p" }],
+ toDOM() { return pDOM; }
+ },
+
+ // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
+ blockquote: {
+ content: "block+",
+ group: "block",
+ defining: true,
+ parseDOM: [{ tag: "blockquote" }],
+ toDOM() { return blockquoteDOM; }
+ },
+
+ // :: NodeSpec A horizontal rule (`<hr>`).
+ horizontal_rule: {
+ group: "block",
+ parseDOM: [{ tag: "hr" }],
+ toDOM() { return hrDOM; }
+ },
+
+ // :: NodeSpec A heading textblock, with a `level` attribute that
+ // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
+ // `<h6>` elements.
+ heading: {
+ attrs: { level: { default: 1 } },
+ content: "inline*",
+ group: "block",
+ defining: true,
+ parseDOM: [{ tag: "h1", attrs: { level: 1 } },
+ { tag: "h2", attrs: { level: 2 } },
+ { tag: "h3", attrs: { level: 3 } },
+ { tag: "h4", attrs: { level: 4 } },
+ { tag: "h5", attrs: { level: 5 } },
+ { tag: "h6", attrs: { level: 6 } }],
+ toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
+ },
+
+ // :: NodeSpec A code listing. Disallows marks or non-text inline
+ // nodes by default. Represented as a `<pre>` element with a
+ // `<code>` element inside of it.
+ code_block: {
+ content: "text*",
+ marks: "",
+ group: "block",
+ code: true,
+ defining: true,
+ parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+ toDOM() { return preDOM; }
+ },
+
+ // :: NodeSpec The text node.
+ text: {
+ group: "inline"
+ },
+
+ // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
+ // `alt`, and `href` attributes. The latter two default to the empty
+ // string.
+ image: {
+ inline: true,
+ attrs: {
+ src: {},
+ alt: { default: null },
+ title: { default: null }
+ },
+ group: "inline",
+ draggable: true,
+ parseDOM: [{
+ tag: "img[src]", getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute("src"),
+ title: dom.getAttribute("title"),
+ alt: dom.getAttribute("alt")
+ };
+ }
+ }],
+ toDOM(node: any) { return ["img", node.attrs]; }
+ },
+
+ // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
+ hard_break: {
+ inline: true,
+ group: "inline",
+ selectable: false,
+ parseDOM: [{ tag: "br" }],
+ toDOM() { return brDOM; }
+ },
+
+ ordered_list: {
+ ...orderedList,
+ 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 }
+ },
+ //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*'
+ }
+};
const emDOM: DOMOutputSpecArray = ["em", 0];
const strongDOM: DOMOutputSpecArray = ["strong", 0];
@@ -144,93 +144,93 @@ const underlineDOM: DOMOutputSpecArray = ["underline", 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
- // :: MarkSpec A link. Has `href` and `title` attributes. `title`
- // defaults to the empty string. Rendered and parsed as an `<a>`
- // element.
- link: {
- attrs: {
- href: {},
- title: { default: null }
- },
- inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }
- }
- }],
- toDOM(node: any) { return ["a", node.attrs, 0] }
- },
-
- // :: 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" }],
- toDOM() { return emDOM }
- },
-
- // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
- // also match `<b>` and `font-weight: bold`.
- strong: {
- parseDOM: [{ tag: "strong" },
- { tag: "b" },
- { style: "font-weight" }],
- toDOM() { return strongDOM }
- },
-
- underline: {
- parseDOM: [
- { tag: 'u' },
- { style: 'text-decoration=underline' }
- ],
- toDOM: () => ['span', {
- style: 'text-decoration:underline'
- }]
- },
-
- strikethrough: {
- parseDOM: [
- { tag: 'strike' },
- { style: 'text-decoration=line-through' },
- { style: 'text-decoration-line=line-through' }
- ],
- toDOM: () => ['span', {
- style: 'text-decoration-line:line-through'
- }]
- },
-
- subscript: {
- excludes: 'superscript',
- parseDOM: [
- { tag: 'sub' },
- { style: 'vertical-align=sub' }
- ],
- toDOM: () => ['sub']
- },
-
- superscript: {
- excludes: 'subscript',
- parseDOM: [
- { tag: 'sup' },
- { style: 'vertical-align=super' }
- ],
- toDOM: () => ['sup']
- },
-
-
- // :: MarkSpec Code font mark. Represented as a `<code>` element.
- code: {
- parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM }
- },
-
-
- timesNewRoman: {
- parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }],
- toDOM: () => ['span', {
- style: 'font-family: "Times New Roman", Times, serif;'
- }]
- },
-}
+ // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+ // defaults to the empty string. Rendered and parsed as an `<a>`
+ // element.
+ link: {
+ attrs: {
+ href: {},
+ title: { default: null }
+ },
+ inclusive: false,
+ parseDOM: [{
+ tag: "a[href]", getAttrs(dom: any) {
+ return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
+ }
+ }],
+ toDOM(node: any) { return ["a", node.attrs, 0]; }
+ },
+
+ // :: 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" }],
+ toDOM() { return emDOM; }
+ },
+
+ // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
+ // also match `<b>` and `font-weight: bold`.
+ strong: {
+ parseDOM: [{ tag: "strong" },
+ { tag: "b" },
+ { style: "font-weight" }],
+ toDOM() { return strongDOM; }
+ },
+
+ underline: {
+ parseDOM: [
+ { tag: 'u' },
+ { style: 'text-decoration=underline' }
+ ],
+ toDOM: () => ['span', {
+ style: 'text-decoration:underline'
+ }]
+ },
+
+ strikethrough: {
+ parseDOM: [
+ { tag: 'strike' },
+ { style: 'text-decoration=line-through' },
+ { style: 'text-decoration-line=line-through' }
+ ],
+ toDOM: () => ['span', {
+ style: 'text-decoration-line:line-through'
+ }]
+ },
+
+ subscript: {
+ excludes: 'superscript',
+ parseDOM: [
+ { tag: 'sub' },
+ { style: 'vertical-align=sub' }
+ ],
+ toDOM: () => ['sub']
+ },
+
+ superscript: {
+ excludes: 'subscript',
+ parseDOM: [
+ { tag: 'sup' },
+ { style: 'vertical-align=super' }
+ ],
+ toDOM: () => ['sup']
+ },
+
+
+ // :: MarkSpec Code font mark. Represented as a `<code>` element.
+ code: {
+ parseDOM: [{ tag: "code" }],
+ toDOM() { return codeDOM; }
+ },
+
+
+ timesNewRoman: {
+ parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Times New Roman", Times, serif;'
+ }]
+ },
+};
// :: Schema
// This schema rougly corresponds to the document schema used by
@@ -240,4 +240,4 @@ export const marks: { [index: string]: MarkSpec } = {
//
// To reuse elements from this schema, extend or read from its
// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
-export const schema = new Schema({ nodes, marks }) \ No newline at end of file
+export const schema = new Schema({ nodes, marks }); \ No newline at end of file
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 4e97b9401..9015f21cf 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -14,45 +14,63 @@ import { ListField } from "../../fields/ListField";
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
-import * as typescriptlib from '!!raw-loader!./type_decls.d'
+import * as typescriptlib from '!!raw-loader!./type_decls.d';
import { Documents } from "../documents/Documents";
import { Key } from "../../fields/Key";
+export interface ScriptSucccess {
+ success: true;
+ result: any;
+}
+
+export interface ScriptError {
+ success: false;
+ error: any;
+}
+
+export type ScriptResult = ScriptSucccess | ScriptError;
-export interface ExecutableScript {
- (): any;
+export interface CompiledScript {
+ readonly compiled: true;
+ readonly originalScript: string;
+ readonly options: Readonly<ScriptOptions>;
+ run(args?: { [name: string]: any }): ScriptResult;
+}
- compiled: boolean;
+export interface CompileError {
+ compiled: false;
+ errors: any[];
}
-function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript {
- const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+export type CompileResult = CompiledScript | CompileError;
+
+function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
+ const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
+ if (errors || !script) {
+ return { compiled: false, errors: diagnostics };
+ }
- let func: () => Opt<Field>;
- if (compiled && script) {
- 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") {
+ let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key];
+ let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)];
+ let params: any[] = [KeyStore, Documents, ...fieldTypes];
+ let compiledFunction = new Function(...paramNames, `return ${script}`);
+ let run = (args: { [name: string]: any } = {}): ScriptResult => {
+ let argsArray: any[] = [];
+ for (let name of customParams) {
+ if (name === "this") {
continue;
}
- paramNames.push(prop);
- params.push(scope[prop]);
+ argsArray.push(args[name]);
}
- let thisParam = scope["this"];
- let compiledFunction = new Function(...paramNames, script);
- func = function (): Opt<Field> {
- return compiledFunction.apply(thisParam, params)
- };
- } else {
- func = () => undefined;
- }
-
- return Object.assign(func,
- {
- compiled
- });
+ let thisParam = args.this;
+ try {
+ const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
+ return { success: true, result };
+ } catch (error) {
+ return { success: false, error };
+ }
+ };
+ return { compiled: true, run, originalScript, options };
}
interface File {
@@ -74,14 +92,14 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
getDefaultLibFileName(options: any): string {
- return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means...
+ return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
const file = this.files.find(file => file.fileName === fileName);
if (file) {
file.content = content;
} else {
- this.files.push({ fileName, content })
+ this.files.push({ fileName, content });
}
}
getCurrentDirectory(): string {
@@ -108,26 +126,48 @@ class ScriptingCompilerHost {
}
}
-export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript {
+export interface ScriptOptions {
+ requiredType?: string;
+ addReturn?: boolean;
+ params?: { [name: string]: string };
+}
+
+export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult {
let host = new ScriptingCompilerHost;
- let funcScript = `(function() {
+ let paramArray: string[] = [];
+ if ("this" in params) {
+ paramArray.push("this");
+ }
+ for (const key in params) {
+ if (key === "this") continue;
+ paramArray.push(key);
+ }
+ let paramString = paramArray.map(key => {
+ const val = params[key];
+ return `${key}: ${val}`;
+ }).join(", ");
+ let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} {
${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);
let testResult = program.emit();
- let outputText = "return " + host.readFile("file.js");
+ let outputText = host.readFile("file.js");
let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
- return Compile(outputText, diagnostics, scope || {});
+ return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params });
+}
+
+export function OrLiteralType(returnType: string): string {
+ return `${returnType} | string | number`;
}
export function ToField(data: any): Opt<Field> {
- if (typeof data == "string") {
+ if (typeof data === "string") {
return new TextField(data);
- } else if (typeof data == "number") {
+ } else if (typeof data === "number") {
return new NumberField(data);
}
return undefined;
diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx
index b6b088170..a209874a3 100644
--- a/src/client/util/ScrollBox.tsx
+++ b/src/client/util/ScrollBox.tsx
@@ -1,4 +1,4 @@
-import React = require("react")
+import React = require("react");
export class ScrollBox extends React.Component {
onWheel = (e: React.WheelEvent) => {
@@ -16,6 +16,6 @@ export class ScrollBox extends React.Component {
}} onWheel={this.onWheel}>
{this.props.children}
</div>
- )
+ );
}
} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 1354e32e1..5ddaafc72 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,7 @@
import { observable, action } from "mobx";
import { DocumentView } from "../views/nodes/DocumentView";
-import { Document } from "../../fields/Document"
+import { Document } from "../../fields/Document";
+import { Main } from "../views/Main";
export namespace SelectionManager {
class Manager {
@@ -11,19 +12,26 @@ export namespace SelectionManager {
SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
if (!ctrlPressed) {
- manager.SelectedDocuments = [];
+ this.DeselectAll();
}
if (manager.SelectedDocuments.indexOf(doc) === -1) {
- manager.SelectedDocuments.push(doc)
+ manager.SelectedDocuments.push(doc);
+ doc.props.onActiveChanged(true);
}
}
+
+ @action
+ DeselectAll(): void {
+ manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
+ manager.SelectedDocuments = [];
+ }
}
- const manager = new Manager;
+ const manager = new Manager();
export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
- manager.SelectDoc(doc, ctrlPressed)
+ manager.SelectDoc(doc, ctrlPressed);
}
export function IsSelected(doc: DocumentView): boolean {
@@ -33,18 +41,17 @@ export namespace SelectionManager {
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;
+ for (const view of manager.SelectedDocuments) {
+ if (view.props.Document === except) found = view;
}
}
- manager.SelectedDocuments.length = 0;
- if (found)
- manager.SelectedDocuments.push(found);
+
+ manager.DeselectAll();
+ if (found) manager.SelectDoc(found, false);
+ Main.Instance.SetTextDoc(undefined, undefined);
}
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
-} \ No newline at end of file
+}
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index e84d48ce3..19affdf97 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -10,159 +10,131 @@ import { Schema, NodeType } 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, liftListItem, listItem } from 'prosemirror-schema-list'
-import {
- faListUl,
-} from '@fortawesome/free-solid-svg-icons';
+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';
-const SVG = "http://www.w3.org/2000/svg"
+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;
-
- constructor(view: EditorView) {
- this.tooltip = document.createElement("div");
- this.tooltip.className = "tooltipMenu";
-
- //add the div which is the tooltip
- view.dom.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: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
- { command: toggleMark(schema.marks.timesNewRoman), dom: this.icon("x", "TNR") },
- { command: undo, dom: this.icon("<", "lift") },
- ]
- //add menu items
- items.forEach(({ dom, command }) => {
- this.tooltip.appendChild(dom);
- });
-
- //add dropdowns
-
- //pointer down handler to activate button effects
- this.tooltip.addEventListener("pointerdown", e => {
- e.preventDefault();
- view.focus();
- //update view of icons
- this.num_icons = 0;
- items.forEach(({ command, dom }) => {
- if (e.srcElement && dom.contains(e.srcElement as Node)) {
- //let active = 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";
+ private tooltip: HTMLElement;
+
+ constructor(view: EditorView) {
+ this.tooltip = document.createElement("div");
+ this.tooltip.className = "tooltipMenu";
+
+ //add the div which is the tooltip
+ view.dom.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") },
+ //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));
+
+ //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"
+ }
+ });
+ });
+
+ this.update(view, undefined);
+ }
+
+ // Helper function to create menu icons
+ icon(text: string, name: string) {
+ let span = document.createElement("span");
+ span.className = "menuicon " + name;
+ span.title = name;
+ span.textContent = text;
+ return span;
+ }
+
+ //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(type, attrs);
}
- })
- })
-
- this.update(view, undefined);
- }
-
- // Helper function to create menu icons
- icon(text: string, name: string) {
- let span = document.createElement("span");
- span.className = "menuicon " + name;
- span.title = name;
- span.textContent = text;
- return span;
- }
-
- //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(type, attrs);
}
- }
-
- //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";
- //icon.style.color = "white";
- //span.appendChild(<i style={{ color: "white" }} icon="list-ul" size="lg" />);
- let i = document.createElement("i");
- i.className = "fa falist-ul";
- span.appendChild(i);
- //span.appendChild(icon);
- //return liftItem.spec.icon.sty
-
- //let sym = document.createElementNS(SVG, "symbol")
- // sym.id = name
- //sym.style.color = "white";
- //width then height
- //sym.setAttribute("viewBox", "0 0 " + 1024 + " " + 1024);
- //let path = sym.appendChild(document.createElementNS(SVG, "path"));
- //path.setAttribute("d", "M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z");
- //span.appendChild(sym);
- return span;
- }
-
- // Create an icon for a heading at the given level
- heading(level: number) {
- return {
- command: setBlockType(schema.nodes.heading, { level }),
- dom: this.icon("H" + level, "heading")
+
+ //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 {
+ command: setBlockType(schema.nodes.heading, { level }),
+ dom: this.icon("H" + level, "heading")
+ };
}
- }
-
- //updates the tooltip menu when the selection changes
- update(view: EditorView, lastState: EditorState | undefined) {
- let state = view.state
- // Don't do anything if the document/selection didn't change
- if (lastState && lastState.doc.eq(state.doc) &&
- lastState.selection.eq(state.selection)) return
-
- // Hide the tooltip if the selection is empty
- if (state.selection.empty) {
- this.tooltip.style.display = "none"
- return
+
+ //updates the tooltip menu when the selection changes
+ update(view: EditorView, lastState: EditorState | undefined) {
+ let state = view.state;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ // Hide the tooltip if the selection is empty
+ if (state.selection.empty) {
+ 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) + "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 = 122 + "px";
+ this.tooltip.style.bottom = (box.bottom - start.top) + "px";
}
- // 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) + "px"
- //let width = Math.abs(start.left - end.left) / 2;
- let width = 8 * 16 + 15;
- let mid = Math.min(start.left, end.left) + width;
-
- //THIS WIDTH IS 15 * NUMBER OF ICONS + 15
- this.tooltip.style.width = width + "px";
- this.tooltip.style.bottom = (box.bottom - start.top) + "px";
- }
-
- destroy() { this.tooltip.remove() }
+ destroy() { this.tooltip.remove(); }
} \ No newline at end of file
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index 3e1039166..e9170ec36 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -3,7 +3,7 @@ export class Transform {
private _translateY: number = 0;
private _scale: number = 1;
- static get Identity(): Transform {
+ static Identity(): Transform {
return new Transform(0, 0, 1);
}
@@ -17,78 +17,64 @@ export class Transform {
this._scale = scale;
}
- translate = (x: number, y: number): Transform => {
+ translate = (x: number, y: number): this => {
this._translateX += x;
this._translateY += y;
return this;
}
- scale = (scale: number): Transform => {
+ scale = (scale: number): this => {
this._scale *= scale;
this._translateX *= scale;
this._translateY *= scale;
return this;
}
- scaleAbout = (scale: number, x: number, y: number): Transform => {
+ scaleAbout = (scale: number, x: number, y: number): this => {
this._translateX += x * this._scale - x * this._scale * scale;
this._translateY += y * this._scale - y * this._scale * scale;
this._scale *= scale;
return this;
}
- transform = (transform: Transform): Transform => {
+ transform = (transform: Transform): this => {
this._translateX = transform._translateX + transform._scale * this._translateX;
this._translateY = transform._translateY + transform._scale * this._translateY;
this._scale *= transform._scale;
return this;
}
- preTranslate = (x: number, y: number): Transform => {
+ preTranslate = (x: number, y: number): this => {
this._translateX += this._scale * x;
this._translateY += this._scale * y;
return this;
}
- preScale = (scale: number): Transform => {
+ preScale = (scale: number): this => {
this._scale *= scale;
return this;
}
- preTransform = (transform: Transform): Transform => {
+ preTransform = (transform: Transform): this => {
this._translateX += transform._translateX * this._scale;
this._translateY += transform._translateY * this._scale;
this._scale *= transform._scale;
return this;
}
- translated = (x: number, y: number): Transform => {
- return this.copy().translate(x, y);
- }
+ translated = (x: number, y: number): Transform => this.copy().translate(x, y);
- preTranslated = (x: number, y: number): Transform => {
- return this.copy().preTranslate(x, y);
- }
+ preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y);
- scaled = (scale: number): Transform => {
- return this.copy().scale(scale);
- }
+ scaled = (scale: number): Transform => this.copy().scale(scale);
- scaledAbout = (scale: number, x: number, y: number): Transform => {
- return this.copy().scaleAbout(scale, x, y);
- }
+ scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y);
- preScaled = (scale: number): Transform => {
- return this.copy().preScale(scale);
- }
+ preScaled = (scale: number): Transform => this.copy().preScale(scale);
- transformed = (transform: Transform): Transform => {
- return this.copy().transform(transform);
- }
+ transformed = (transform: Transform): Transform => this.copy().transform(transform);
- preTransformed = (transform: Transform): Transform => {
- return this.copy().preTransform(transform);
- }
+ preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
transformPoint = (x: number, y: number): [number, number] => {
x *= this._scale;
@@ -98,9 +84,7 @@ export class Transform {
return [x, y];
}
- transformDirection = (x: number, y: number): [number, number] => {
- return [x * this._scale, y * this._scale];
- }
+ transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } {
[x, y] = this.transformPoint(x, y);
@@ -108,12 +92,8 @@ export class Transform {
return { x, y, width, height };
}
- inverse = () => {
- return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale)
- }
+ inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale);
- copy = () => {
- return new Transform(this._translateX, this._translateY, this._scale);
- }
+ copy = () => new Transform(this._translateX, this._translateY, this._scale);
} \ No newline at end of file
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 0714a7f5c..532ba78eb 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -36,7 +36,5 @@ export class TypedEvent<T> {
this.listenersOncer = [];
}
- pipe = (te: TypedEvent<T>): Disposable => {
- return this.on((e) => te.emit(e));
- }
+ pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
} \ No newline at end of file
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 6d1b2f1b8..27aed4bac 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,6 +1,7 @@
import { observable, action } from "mobx";
-import 'source-map-support/register'
+import 'source-map-support/register';
import { Without } from "../../Utils";
+import { string } from "prop-types";
function getBatchName(target: any, key: string | symbol): string {
let keyName = key.toString();
@@ -30,11 +31,24 @@ function propertyDecorator(target: any, key: string | symbol) {
batch.end();
}
}
- })
+ });
}
- })
+ });
}
-export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+
+export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
+ if (!key) {
+ return function () {
+ let batch = UndoManager.StartBatch("");
+ try {
+ return target.apply(undefined, arguments);
+ } finally {
+ batch.end();
+ }
+ };
+ }
if (!descriptor) {
propertyDecorator(target, key);
return;
@@ -44,11 +58,11 @@ export function undoBatch(target: any, key: string | symbol, descriptor?: TypedP
descriptor.value = function (...args: any[]) {
let batch = UndoManager.StartBatch(getBatchName(target, key));
try {
- return oldFunction.apply(this, args)
+ return oldFunction.apply(this, args);
} finally {
batch.end();
}
- }
+ };
return descriptor;
}
@@ -84,6 +98,9 @@ export namespace UndoManager {
export function GetOpenBatches(): Without<Batch, 'end'>[] {
return openBatches;
}
+ export function TraceOpenBatches() {
+ console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`);
+ }
export class Batch {
private disposed: boolean = false;
@@ -100,8 +117,8 @@ export namespace UndoManager {
EndBatch(cancel);
}
- end = () => { this.dispose(false); }
- cancel = () => { this.dispose(true); }
+ end = () => { this.dispose(false); };
+ cancel = () => { this.dispose(true); };
}
export function StartBatch(batchName: string): Batch {
@@ -121,12 +138,15 @@ export namespace UndoManager {
redoStack.length = 0;
currentBatch = undefined;
}
- })
+ });
export function RunInBatch(fn: () => void, batchName: string) {
let batch = StartBatch(batchName);
- fn();
- batch.end();
+ try {
+ fn();
+ } finally {
+ batch.end();
+ }
}
export const Undo = action(() => {
@@ -146,7 +166,7 @@ export namespace UndoManager {
undoing = false;
redoStack.push(commands);
- })
+ });
export const Redo = action(() => {
if (redoStack.length === 0) {
@@ -159,12 +179,12 @@ export namespace UndoManager {
}
undoing = true;
- for (let i = 0; i < commands.length; i++) {
- commands[i].redo();
+ for (const command of commands) {
+ command.redo();
}
undoing = false;
undoStack.push(commands);
- })
+ });
} \ No newline at end of file
diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d
index 4f69053b1..47c3481b2 100644
--- a/src/client/util/type_decls.d
+++ b/src/client/util/type_decls.d
@@ -181,7 +181,7 @@ declare class Key extends Field {
Copy(): Field;
ToScriptString(): string;
}
-declare type FIELD_WAITING = "<Waiting>";
+declare type FIELD_WAITING = null;
declare type Opt<T> = T | undefined;
declare type FieldValue<T> = Opt<T> | FIELD_WAITING;
// @ts-ignore