aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DictationManager.ts48
-rw-r--r--src/client/util/DocumentManager.ts59
-rw-r--r--src/client/util/DragManager.ts93
-rw-r--r--src/client/util/History.ts10
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.scss6
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx151
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts52
-rw-r--r--src/client/util/RichTextRules.ts24
-rw-r--r--src/client/util/RichTextSchema.tsx33
-rw-r--r--src/client/util/Scripting.ts8
-rw-r--r--src/client/util/SearchUtil.ts27
-rw-r--r--src/client/util/SelectionManager.ts20
-rw-r--r--src/client/util/SharingManager.scss136
-rw-r--r--src/client/util/SharingManager.tsx293
-rw-r--r--src/client/util/TooltipTextMenu.tsx58
-rw-r--r--src/client/util/UndoManager.ts2
-rw-r--r--src/client/util/prosemirrorPatches.js2
17 files changed, 778 insertions, 244 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index fb3c15cea..cebb56bbe 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -3,7 +3,7 @@ import { DocumentView } from "../views/nodes/DocumentView";
import { UndoManager } from "./UndoManager";
import * as interpreter from "words-to-numbers";
import { DocumentType } from "../documents/DocumentTypes";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, Opt } from "../../new_fields/Doc";
import { List } from "../../new_fields/List";
import { Docs } from "../documents/Documents";
import { CollectionViewType } from "../views/collections/CollectionBaseView";
@@ -40,12 +40,26 @@ export namespace DictationManager {
webkitSpeechRecognition: any;
}
}
- const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
+ const { webkitSpeechRecognition }: CORE.IWindow = window as any as CORE.IWindow;
export const placeholder = "Listening...";
export namespace Controls {
export const Infringed = "unable to process: dictation manager still involved in previous session";
+ const browser = (() => {
+ let identifier = navigator.userAgent.toLowerCase();
+ if (identifier.indexOf("safari") >= 0) {
+ return "Safari";
+ }
+ if (identifier.indexOf("chrome") >= 0) {
+ return "Chrome";
+ }
+ if (identifier.indexOf("firefox") >= 0) {
+ return "Firefox";
+ }
+ return "Unidentified Browser";
+ })();
+ const unsupported = `listening is not supported in ${browser}`;
const intraSession = ". ";
const interSession = " ... ";
@@ -55,8 +69,7 @@ export namespace DictationManager {
let current: string | undefined = undefined;
let sessionResults: string[] = [];
- const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition();
- recognizer.onstart = () => console.log("initiating speech recognition session...");
+ const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
export type InterimResultHandler = (results: string) => any;
export type ContinuityArgs = { indefinite: boolean } | false;
@@ -109,6 +122,10 @@ export namespace DictationManager {
};
const listenImpl = (options?: Partial<ListeningOptions>) => {
+ if (!recognizer) {
+ console.log(unsupported);
+ return unsupported;
+ }
if (isListening) {
return Infringed;
}
@@ -121,6 +138,7 @@ export namespace DictationManager {
let intra = options && options.delimiters ? options.delimiters.intra : undefined;
let inter = options && options.delimiters ? options.delimiters.inter : undefined;
+ recognizer.onstart = () => console.log("initiating speech recognition session...");
recognizer.interimResults = handler !== undefined;
recognizer.continuous = continuous === undefined ? false : continuous !== false;
recognizer.lang = language === undefined ? "en-US" : language;
@@ -167,14 +185,20 @@ export namespace DictationManager {
} else {
resolve(current);
}
- reset();
+ current = undefined;
+ sessionResults = [];
+ isListening = false;
+ isManuallyStopped = false;
+ recognizer.onresult = null;
+ recognizer.onerror = null;
+ recognizer.onend = null;
};
});
};
export const stop = (salvageSession = true) => {
- if (!isListening) {
+ if (!isListening || !recognizer) {
return;
}
isManuallyStopped = true;
@@ -197,16 +221,6 @@ export namespace DictationManager {
return transcripts.join(delimiter || intraSession);
};
- const reset = () => {
- current = undefined;
- sessionResults = [];
- isListening = false;
- isManuallyStopped = false;
- recognizer.onresult = null;
- recognizer.onerror = null;
- recognizer.onend = null;
- };
-
}
export namespace Commands {
@@ -313,7 +327,7 @@ export namespace DictationManager {
["open fields", {
action: (target: DocumentView) => {
let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
- target.props.addDocTab(kvp, target.dataDoc, "onRight");
+ target.props.addDocTab(kvp, target.props.DataDoc, "onRight");
}
}],
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index ec731da84..c048125c5 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -10,6 +10,7 @@ import { DocumentView } from '../views/nodes/DocumentView';
import { LinkManager } from './LinkManager';
import { undoBatch, UndoManager } from './UndoManager';
import { Scripting } from './Scripting';
+import { List } from '../../new_fields/List';
export class DocumentManager {
@@ -131,9 +132,7 @@ export class DocumentManager {
let doc = Doc.GetProto(docDelegate);
const contextDoc = await Cast(doc.annotationOn, Doc);
if (contextDoc) {
- const page = NumCast(doc.page, linkPage || 0);
- const curPage = NumCast(contextDoc.curPage, page);
- if (page !== curPage) contextDoc.curPage = page;
+ contextDoc.scrollY = NumCast(doc.y) - NumCast(contextDoc.height) / 2;
}
let docView: DocumentView | null;
@@ -146,6 +145,7 @@ export class DocumentManager {
if (!contextDoc) {
let docs = docContext ? await DocListCastAsync(docContext.data) : undefined;
let found = false;
+ // bcz: this just searches within the context for the target -- perhaps it should recursively search through all children?
docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate));
if (docContext && found) {
let targetContextView: DocumentView | null;
@@ -154,16 +154,19 @@ export class DocumentManager {
docContext.panTransformType = "Ease";
targetContextView.props.focus(docDelegate, willZoom);
} else {
- (dockFunc || CollectionDockingView.Instance.AddRightSplit)(docContext, undefined);
+ (dockFunc || CollectionDockingView.AddRightSplit)(docContext, undefined);
setTimeout(() => {
- this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
- }, 10);
+ let dv = DocumentManager.Instance.getDocumentView(docContext);
+ dv && this.jumpToDocument(docDelegate, willZoom, forceDockFunc,
+ doc => dv!.props.focus(dv!.props.Document, true, 1, () => dv!.props.addDocTab(doc, undefined, "inPlace")),
+ linkPage);
+ }, 1050);
}
} else {
const actualDoc = Doc.MakeAlias(docDelegate);
Doc.BrushDoc(actualDoc);
if (linkPage !== undefined) actualDoc.curPage = linkPage;
- (dockFunc || CollectionDockingView.Instance.AddRightSplit)(actualDoc, undefined);
+ (dockFunc || CollectionDockingView.AddRightSplit)(actualDoc, undefined);
}
} else {
let contextView: DocumentView | null;
@@ -172,10 +175,10 @@ export class DocumentManager {
contextDoc.panTransformType = "Ease";
contextView.props.focus(docDelegate, willZoom);
} else {
- (dockFunc || CollectionDockingView.Instance.AddRightSplit)(contextDoc, undefined);
+ (dockFunc || CollectionDockingView.AddRightSplit)(contextDoc, undefined);
setTimeout(() => {
this.jumpToDocument(docDelegate, willZoom, forceDockFunc, dockFunc, linkPage);
- }, 10);
+ }, 1000);
}
}
}
@@ -183,13 +186,8 @@ export class DocumentManager {
@action
zoomIntoScale = (docDelegate: Doc, scale: number) => {
- let doc = Doc.GetProto(docDelegate);
-
- let docView: DocumentView | null;
- docView = DocumentManager.Instance.getDocumentView(doc);
- if (docView) {
- docView.props.zoomToScale(scale);
- }
+ let docView = DocumentManager.Instance.getDocumentView(Doc.GetProto(docDelegate));
+ docView && docView.props.zoomToScale(scale);
}
getScaleOfDocView = (docDelegate: Doc) => {
@@ -203,5 +201,34 @@ export class DocumentManager {
return 1;
}
}
+
+ @action
+ animateBetweenPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
+ expandedDocs && expandedDocs.map(expDoc => {
+ if (expDoc.isMinimized || expDoc.isAnimating === "min") { // MAXIMIZE DOC
+ if (expDoc.isMinimized) { // docs are never actaully at the minimized location. so when we unminimize one, we have to set our overrides to make it look like it was at the minimize location
+ expDoc.isMinimized = false;
+ expDoc.animateToPos = new List<number>([...scrpt, 0]);
+ expDoc.animateToDimensions = new List<number>([0, 0]);
+ }
+ setTimeout(() => {
+ expDoc.isAnimating = "max";
+ expDoc.animateToPos = new List<number>([0, 0, 1]);
+ expDoc.animateToDimensions = new List<number>([NumCast(expDoc.width), NumCast(expDoc.height)]);
+ setTimeout(() => expDoc.isAnimating === "max" && (expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined), 600);
+ }, 0);
+ } else { // MINIMIZE DOC
+ expDoc.isAnimating = "min";
+ expDoc.animateToPos = new List<number>([...scrpt, 0]);
+ expDoc.animateToDimensions = new List<number>([0, 0]);
+ setTimeout(() => {
+ if (expDoc.isAnimating === "min") {
+ expDoc.isMinimized = true;
+ expDoc.isAnimating = expDoc.animateToPos = expDoc.animateToDimensions = undefined;
+ }
+ }, 600);
+ }
+ });
+ }
}
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/DragManager.ts b/src/client/util/DragManager.ts
index 4c9c9c17c..ddc8fb62c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -32,7 +32,7 @@ export function SetupDrag(
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
let doc = await docFunc();
- var dragData = new DragManager.DocumentDragData([doc], [undefined]);
+ var dragData = new DragManager.DocumentDragData([doc]);
dragData.dropAction = dropAction;
dragData.moveDocument = moveFunc;
dragData.options = options;
@@ -66,7 +66,7 @@ export function SetupDrag(
function moveLinkedDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
const document = SelectionManager.SelectedDocuments()[0];
- document.props.removeDocument && document.props.removeDocument(doc);
+ document && document.props.removeDocument && document.props.removeDocument(doc);
addDocument(doc);
return true;
}
@@ -76,7 +76,7 @@ export async function DragLinkAsDocument(dragEle: HTMLElement, x: number, y: num
if (draggeddoc) {
let moddrag = await Cast(draggeddoc.annotationOn, Doc);
let dragdocs = moddrag ? [moddrag] : [draggeddoc];
- let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+ let dragData = new DragManager.DocumentDragData(dragdocs);
dragData.moveDocument = moveLinkedDocument;
DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
handlers: {
@@ -107,7 +107,7 @@ export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: n
if (doc) moddrag.push(doc);
}
let dragdocs = moddrag.length ? moddrag : draggedDocs;
- let dragData = new DragManager.DocumentDragData(dragdocs, dragdocs);
+ let dragData = new DragManager.DocumentDragData(dragdocs);
dragData.moveDocument = moveLinkedDocument;
DragManager.StartLinkedDocumentDrag([dragEle], dragData, x, y, {
handlers: {
@@ -201,18 +201,14 @@ export namespace DragManager {
export type MoveFunction = (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
export class DocumentDragData {
- constructor(dragDoc: Doc[], dragDataDocs: (Doc | undefined)[]) {
+ constructor(dragDoc: Doc[]) {
this.draggedDocuments = dragDoc;
- this.draggedDataDocs = dragDataDocs;
this.droppedDocuments = dragDoc;
- this.xOffset = 0;
- this.yOffset = 0;
+ this.offset = [0, 0];
}
draggedDocuments: Doc[];
- draggedDataDocs: (Doc | undefined)[];
droppedDocuments: Doc[];
- xOffset: number;
- yOffset: number;
+ offset: number[];
dropAction: dropActionType;
userDropAction: dropActionType;
moveDocument?: MoveFunction;
@@ -225,14 +221,13 @@ export namespace DragManager {
this.dragDocument = dragDoc;
this.dropDocument = dropDoc;
this.annotationDocument = annotationDoc;
- this.xOffset = this.yOffset = 0;
+ this.offset = [0, 0];
}
targetContext: Doc | undefined;
dragDocument: Doc;
annotationDocument: Doc;
dropDocument: Doc;
- xOffset: number;
- yOffset: number;
+ offset: number[];
dropAction: dropActionType;
userDropAction: dropActionType;
}
@@ -252,21 +247,13 @@ export namespace DragManager {
});
}
- export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize?: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
- let dragData = new DragManager.DocumentDragData([], [undefined]);
+ export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ let dragData = new DragManager.DocumentDragData([]);
runInAction(() => StartDragFunctions.map(func => func()));
StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag :
(dropData: { [id: string]: any }) => {
let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: title });
- let compiled = CompileScript(script, {
- params: { doc: Doc.name },
- typecheck: false,
- editable: true
- });
- if (compiled.compiled) {
- let scriptField = new ScriptField(compiled);
- bd.onClick = scriptField;
- }
+ bd.onClick = ScriptField.MakeScript(script);
params.map(p => Object.keys(vars).indexOf(p) !== -1 && (Doc.GetProto(bd)[p] = new PrefetchProxy(vars[p] as Doc)));
initialize && initialize(bd);
bd.buttonParams = new List<string>(params);
@@ -283,7 +270,8 @@ export namespace DragManager {
let droppedDocuments: Doc[] = dragData.draggedDocuments.reduce((droppedDocs: Doc[], d) => {
let dvs = DocumentManager.Instance.getDocumentViews(d);
if (dvs.length) {
- let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView);
+ let containingView = SelectionManager.SelectedDocuments()[0] ? SelectionManager.SelectedDocuments()[0].props.ContainingCollectionView : undefined;
+ let inContext = dvs.filter(dv => dv.props.ContainingCollectionView === containingView);
if (inContext.length) {
inContext.forEach(dv => droppedDocs.push(dv.props.Document));
} else {
@@ -361,10 +349,8 @@ export namespace DragManager {
let xs: number[] = [];
let ys: number[] = [];
- const docs: Doc[] =
- dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
- const datadocs: (Doc | undefined)[] =
- dragData instanceof DocumentDragData ? dragData.draggedDataDocs : dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
+ const docs = dragData instanceof DocumentDragData ? dragData.draggedDocuments :
+ dragData instanceof AnnotationDragData ? [dragData.dragDocument] : [];
let dragElements = eles.map(ele => {
const w = ele.offsetWidth,
h = ele.offsetHeight;
@@ -393,22 +379,20 @@ export namespace DragManager {
dragElement.style.width = `${rect.width / scaleX}px`;
dragElement.style.height = `${rect.height / scaleY}px`;
- // 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.getElementsByTagName("canvas");
+ var pdfBoxSrc = ele.getElementsByTagName("canvas");
+ Array.from(pdfBox).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
+ var pdfView = dragElement.getElementsByClassName("pdfViewer-viewer");
+ var pdfViewSrc = ele.getElementsByClassName("pdfViewer-viewer");
+ let tops = Array.from(pdfViewSrc).map(p => p.scrollTop);
+ let oldopacity = dragElement.style.opacity;
+ dragElement.style.opacity = "0";
+ setTimeout(() => {
+ dragElement.style.opacity = oldopacity;
+ Array.from(pdfView).map((v, i) => v.scrollTo({ top: tops[i] }));
+ }, 0);
+ }
let set = dragElement.getElementsByTagName('*');
if (dragElement.hasAttribute("style")) (dragElement as any).style.pointerEvents = "none";
// tslint:disable-next-line: prefer-for-of
@@ -432,8 +416,8 @@ export namespace DragManager {
hideSource = options.hideSource();
}
}
- eles.map(ele => (ele.hidden = hideSource) &&
- (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = hideSource)));
+
+ eles.map(ele => ele.hidden = hideSource);
let lastX = downX;
let lastY = downY;
@@ -449,7 +433,7 @@ export namespace DragManager {
pageY: e.pageY,
preventDefault: emptyFunction,
button: 0
- }, docs, datadocs);
+ }, docs);
}
//TODO: Why can't we use e.movementX and e.movementY?
let moveX = e.pageX - lastX;
@@ -461,12 +445,9 @@ export namespace DragManager {
);
};
- let hideDragElements = () => {
+ let hideDragShowOriginalElements = () => {
dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- eles.map(ele => {
- ele.hidden = false;
- (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = false));
- });
+ eles.map(ele => ele.hidden = false);
};
let endDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
@@ -477,12 +458,12 @@ export namespace DragManager {
};
AbortDrag = () => {
- hideDragElements();
+ hideDragShowOriginalElements();
SelectionManager.SetIsDragging(false);
endDrag();
};
const upHandler = (e: PointerEvent) => {
- hideDragElements();
+ hideDragShowOriginalElements();
dispatchDrag(eles, e, dragData, options, finishDrag);
SelectionManager.SetIsDragging(false);
endDrag();
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index e9ff21b22..899abbe40 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -16,8 +16,10 @@ export namespace HistoryUtil {
initializers?: {
[docId: string]: DocInitializerList;
};
+ safe?: boolean;
readonly?: boolean;
nro?: boolean;
+ sharing?: boolean;
}
export type ParsedUrl = DocUrl;
@@ -52,7 +54,9 @@ export namespace HistoryUtil {
}
export function getState(): ParsedUrl {
- return copyState(history.state);
+ let state = copyState(history.state);
+ state.initializers = state.initializers || {};
+ return state;
}
// export function addHandler(handler: (state: ParsedUrl | null) => void) {
@@ -141,7 +145,7 @@ export namespace HistoryUtil {
};
}
- addParser("doc", {}, { readonly: true, initializers: true, nro: true }, (pathname, opts, current) => {
+ addParser("doc", {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
if (pathname.length !== 2) return undefined;
current.initializers = current.initializers || {};
@@ -156,7 +160,7 @@ export namespace HistoryUtil {
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
const search = location.search;
- const opts = qs.parse(search, { sort: false });
+ const opts = search.length ? qs.parse(search, { sort: false }) : {};
let pathnameSplit = pathname.split("/");
const type = pathnameSplit[0];
diff --git a/src/client/util/Import & Export/DirectoryImportBox.scss b/src/client/util/Import & Export/DirectoryImportBox.scss
new file mode 100644
index 000000000..d33cb524b
--- /dev/null
+++ b/src/client/util/Import & Export/DirectoryImportBox.scss
@@ -0,0 +1,6 @@
+.phase {
+ position: absolute;
+ top: 15px;
+ left: 15px;
+ font-style: italic;
+} \ No newline at end of file
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 75b0b52a7..d3f81b992 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -1,9 +1,8 @@
import "fs";
import React = require("react");
-import { Doc, Opt, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
-import { DocServer } from "../../DocServer";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../new_fields/Doc";
import { RouteStore } from "../../../server/RouteStore";
-import { action, observable, autorun, runInAction, computed } from "mobx";
+import { action, observable, autorun, runInAction, computed, reaction, IReactionDisposer } from "mobx";
import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
import Measure, { ContentRect } from "react-measure";
import { library } from '@fortawesome/fontawesome-svg-core';
@@ -18,20 +17,33 @@ import { Id } from "../../../new_fields/FieldSymbols";
import { List } from "../../../new_fields/List";
import { Cast, BoolCast, NumCast } from "../../../new_fields/Types";
import { listSpec } from "../../../new_fields/Schema";
+import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import "./DirectoryImportBox.scss";
+import { Identified } from "../../Network";
+import { BatchedArray } from "array-batcher";
const unsupported = ["text/html", "text/plain"];
+interface FileResponse {
+ name: string;
+ path: string;
+ type: string;
+}
+
@observer
export default class DirectoryImportBox extends React.Component<FieldViewProps> {
private selector = React.createRef<HTMLInputElement>();
@observable private top = 0;
@observable private left = 0;
private dimensions = 50;
+ @observable private phase = "";
+ private disposer: Opt<IReactionDisposer>;
@observable private entries: ImportMetadataEntry[] = [];
@observable private quota = 1;
- @observable private remaining = 1;
+ @observable private completed = 0;
@observable private uploading = false;
@observable private removeHover = false;
@@ -66,15 +78,17 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
}
handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
- runInAction(() => this.uploading = true);
+ runInAction(() => {
+ this.uploading = true;
+ this.phase = "Initializing download...";
+ });
- let promises: Promise<void>[] = [];
let docs: Doc[] = [];
let files = e.target.files;
if (!files || files.length === 0) return;
- let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
+ let directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0];
let validated: File[] = [];
for (let i = 0; i < files.length; i++) {
@@ -82,37 +96,41 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
file && !unsupported.includes(file.type) && validated.push(file);
}
- runInAction(() => this.quota = validated.length);
-
- let sizes = [];
- let modifiedDates = [];
+ runInAction(() => {
+ this.quota = validated.length;
+ this.completed = 0;
+ });
- for (let uploaded_file of validated) {
- let formData = new FormData();
- formData.append('file', uploaded_file);
- let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
- let type = uploaded_file.type;
+ let sizes: number[] = [];
+ let modifiedDates: number[] = [];
- sizes.push(uploaded_file.size);
- modifiedDates.push(uploaded_file.lastModified);
+ runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`);
- runInAction(() => this.remaining++);
+ const uploads = await BatchedArray.from(validated, { batchSize: 15 }).batchedMapAsync(async batch => {
+ const formData = new FormData();
- let prom = fetch(Utils.prepend(RouteStore.upload), {
- method: 'POST',
- body: formData
- }).then(async (res: Response) => {
- (await res.json()).map(action((file: any) => {
- let docPromise = Docs.Get.DocumentFromType(type, Utils.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
- docPromise.then(doc => {
- doc && docs.push(doc) && runInAction(() => this.remaining--);
- });
- }));
+ batch.forEach(file => {
+ sizes.push(file.size);
+ modifiedDates.push(file.lastModified);
+ formData.append(Utils.GenerateGuid(), file);
});
- promises.push(prom);
- }
- await Promise.all(promises);
+ const responses = await Identified.PostFormDataToServer(RouteStore.upload, formData);
+ runInAction(() => this.completed += batch.length);
+ return responses as FileResponse[];
+ });
+
+ await Promise.all(uploads.map(async upload => {
+ const type = upload.type;
+ const path = Utils.prepend(upload.path);
+ const options = {
+ nativeWidth: 300,
+ width: 300,
+ title: upload.name
+ };
+ const document = await Docs.Get.DocumentFromType(type, path, options);
+ document && docs.push(document);
+ }));
for (let i = 0; i < docs.length; i++) {
let doc = docs[i];
@@ -136,24 +154,39 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
};
let parent = this.props.ContainingCollectionView;
if (parent) {
- let importContainer = Docs.Create.StackingDocument(docs, options);
+ let importContainer: Doc;
+ if (docs.length < 50) {
+ importContainer = Docs.Create.MasonryDocument(docs, options);
+ } else {
+ const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")];
+ importContainer = Docs.Create.SchemaDocument(headers, docs, options);
+ }
+ runInAction(() => this.phase = 'External: uploading files to Google Photos...');
importContainer.singleColumn = false;
+ await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer });
Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
!this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
DocumentManager.Instance.jumpToDocument(importContainer, true);
-
}
runInAction(() => {
this.uploading = false;
this.quota = 1;
- this.remaining = 1;
+ this.completed = 0;
});
}
componentDidMount() {
this.selector.current!.setAttribute("directory", "");
this.selector.current!.setAttribute("webkitdirectory", "");
+ this.disposer = reaction(
+ () => this.completed,
+ completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)
+ );
+ }
+
+ componentWillUnmount() {
+ this.disposer && this.disposer();
}
@action
@@ -188,7 +221,6 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
metadata.splice(index, 1);
}
}
-
}
}
@@ -196,19 +228,47 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
let dimensions = 50;
let entries = DocListCast(this.props.Document.data);
let isEditing = this.editingMetadata;
- let remaining = this.remaining;
+ let completed = this.completed;
let quota = this.quota;
let uploading = this.uploading;
let showRemoveLabel = this.removeHover;
let persistent = this.persistent;
- let percent = `${100 - (remaining / quota * 100)}`;
+ let percent = `${completed / quota * 100}`;
percent = percent.split(".")[0];
percent = percent.startsWith("100") ? "99" : percent;
let marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
+ const message = <span className={"phase"}>{this.phase}</span>;
+ const centerPiece = this.phase.includes("Google Photos") ?
+ <img src={"/assets/google_photos.png"} style={{
+ transition: "0.4s opacity ease",
+ width: 30,
+ height: 30,
+ opacity: uploading ? 1 : 0,
+ pointerEvents: "none",
+ position: "absolute",
+ left: 12,
+ top: this.top + 10,
+ fontSize: 18,
+ color: "white",
+ marginLeft: this.left + marginOffset
+ }} />
+ : <div
+ style={{
+ transition: "0.4s opacity ease",
+ opacity: uploading ? 1 : 0,
+ pointerEvents: "none",
+ position: "absolute",
+ left: 10,
+ top: this.top + 12.3,
+ fontSize: 18,
+ color: "white",
+ marginLeft: this.left + marginOffset
+ }}>{percent}%</div>;
return (
<Measure offset onResize={this.preserveCentering}>
{({ measureRef }) =>
<div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
+ {message}
<input
id={"selector"}
ref={this.selector}
@@ -281,18 +341,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
opacity: showRemoveLabel ? 1 : 0,
transition: "0.4s opacity ease"
}}>Template will be <span style={{ textDecoration: "underline", textDecorationColor: persistent ? "green" : "red", color: persistent ? "green" : "red" }}>{persistent ? "kept" : "removed"}</span> after upload</p>
- <div
- style={{
- transition: "0.4s opacity ease",
- opacity: uploading ? 1 : 0,
- pointerEvents: "none",
- position: "absolute",
- left: 10,
- top: this.top + 12.3,
- fontSize: 18,
- color: "white",
- marginLeft: this.left + marginOffset
- }}>{percent}%</div>
+ {centerPiece}
<div
style={{
position: "absolute",
@@ -313,7 +362,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
style={{
pointerEvents: "none",
position: "absolute",
- right: isEditing ? 16.3 : 14.5,
+ right: isEditing ? 14 : 15,
top: isEditing ? 15.4 : 16,
opacity: uploading ? 0 : 1,
transition: "0.4s opacity ease"
diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 1d2d33800..dd0f72af0 100644
--- a/src/client/util/ProsemirrorExampleTransfer.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -11,6 +11,20 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) :
export type KeyMap = { [key: string]: any };
+export let updateBullets = (tx2: Transaction, schema: Schema) => {
+ let fontSize: number | undefined = undefined;
+ tx2.doc.descendants((node: any, offset: any, index: any) => {
+ if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
+ let path = (tx2.doc.resolve(offset) as any).path;
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
+ if (node.type === schema.nodes.ordered_list) depth++;
+ fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize;
+ let fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined;
+ tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks);
+ }
+ });
+ return tx2;
+};
export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
let keys: { [key: string]: any } = {}, type;
@@ -93,35 +107,23 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
bind("Mod-s", TooltipTextMenu.insertStar);
- let updateBullets = (tx2: Transaction) => {
- tx2.doc.descendants((node: any, offset: any, index: any) => {
- if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
- let path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0);
- if (node.type === schema.nodes.ordered_list) depth++;
- tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth }, node.marks);
- }
- });
- };
-
-
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);
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
- dispatch(tx2);
+ let tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
})) { // couldn't sink into an existing list, so wrap in a new one
let newstate = state.applyTransaction(state.tr.setSelection(TextSelection.create(state.doc, range!.start, range!.end)));
if (!wrapInList(schema.nodes.ordered_list)(newstate.state, (tx2: Transaction) => {
- updateBullets(tx2);
+ let tx3 = updateBullets(tx2, schema);
// 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);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
})) {
console.log("bullet promote fail");
}
@@ -132,10 +134,10 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
var marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks());
if (!liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => {
- updateBullets(tx2);
- marks && tx2.ensureMarks([...marks]);
- marks && tx2.setStoredMarks([...marks]);
- dispatch(tx2);
+ let tx3 = updateBullets(tx2, schema);
+ marks && tx3.ensureMarks([...marks]);
+ marks && tx3.setStoredMarks([...marks]);
+ dispatch(tx3);
})) {
console.log("bullet demote fail");
}
@@ -145,7 +147,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal));
return tx;
- }
+ };
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) => dispatch(tx3))) {
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 00e671db9..cd37ea0bb 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -20,7 +20,7 @@ export const inpRules = {
/^1\.\s$/,
schema.nodes.ordered_list,
() => {
- return ({ mapStyle: "decimal", bulletStyle: 1 })
+ return ({ mapStyle: "decimal", bulletStyle: 1 });
},
(match: any, node: any) => {
return node.childCount + node.attrs.order === +match[1];
@@ -33,7 +33,7 @@ export const inpRules = {
schema.nodes.ordered_list,
// match => {
() => {
- return ({ mapStyle: "alpha", bulletStyle: 1 })
+ return ({ mapStyle: "alpha", bulletStyle: 1 });
// return ({ order: +match[1] })
},
(match: any, node: any) => {
@@ -61,22 +61,24 @@ export const inpRules = {
new RegExp(/^#([0-9]+)\s$/),
(state, match, start, end) => {
let size = Number(match[1]);
- let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+ let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
if (ruleProvider && heading) {
- ruleProvider["ruleSize_" + heading] = size;
+ (Cast(FormattedTextBox.InputBoxOverlay!.props.Document, Doc) as Doc).heading = Number(match[1]);
+ return state.tr.deleteRange(start, end);
}
- return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }))
+ return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) }));
}),
new InputRule(
new RegExp(/^\^\^\s$/),
(state, match, start, end) => {
let node = (state.doc.resolve(start) as any).nodeAfter;
let sm = state.storedMarks || undefined;
- let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+ let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "center";
+ return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}
return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
state.tr;
@@ -86,26 +88,24 @@ export const inpRules = {
(state, match, start, end) => {
let node = (state.doc.resolve(start) as any).nodeAfter;
let sm = state.storedMarks || undefined;
- let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+ let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "left";
}
- return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
+ return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}),
new InputRule(
new RegExp(/^\]\]\s$/),
(state, match, start, end) => {
let node = (state.doc.resolve(start) as any).nodeAfter;
let sm = state.storedMarks || undefined;
- let ruleProvider = Cast(FormattedTextBox.InputBoxOverlay!.props.Document.ruleProvider, Doc) as Doc;
+ let ruleProvider = FormattedTextBox.InputBoxOverlay!.props.ruleProvider;
let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleAlign_" + heading] = "right";
}
- return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) :
- state.tr;
+ return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr;
}),
new InputRule(
new RegExp(/\^f\s$/),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index f027a4bf7..64821d8db 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -135,6 +135,7 @@ export const nodes: { [index: string]: NodeSpec } = {
alt: { default: null },
title: { default: null },
float: { default: "left" },
+ location: { default: "onRight" },
docid: { default: "" }
},
group: "inline",
@@ -198,6 +199,8 @@ export const nodes: { [index: string]: NodeSpec } = {
attrs: {
bulletStyle: { default: 0 },
mapStyle: { default: "decimal" },
+ setFontSize: { default: undefined },
+ inheritedFontSize: { default: undefined },
visibility: { default: true }
},
toDOM(node: Node<any>) {
@@ -205,8 +208,9 @@ export const nodes: { [index: string]: NodeSpec } = {
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;
- return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;` }, 0] :
- ['ol', { class: `${map}-ol`, style: `list-style: none;` }];
+ let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize;
+ return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;font-size: ${fsize}` }, 0] :
+ ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}` }];
}
},
@@ -253,7 +257,7 @@ export const marks: { [index: string]: MarkSpec } = {
href: {},
location: { default: null },
title: { default: null },
- docref: { default: false }
+ docref: { default: false } // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text
},
inclusive: false,
parseDOM: [{
@@ -349,8 +353,9 @@ export const marks: { [index: string]: MarkSpec } = {
let style = getComputedStyle(p);
if (style.textDecoration === "underline") return null;
if (p.parentElement.outerHTML.indexOf("text-decoration: underline") !== -1 &&
- p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1)
+ p.parentElement.outerHTML.indexOf("text-decoration-style: dotted") !== -1) {
return null;
+ }
}
return false;
}
@@ -371,10 +376,9 @@ export const marks: { [index: string]: MarkSpec } = {
getAttrs: (p: any) => {
if (typeof (p) !== "string") {
let style = getComputedStyle(p);
- if (style.textDecoration === "underline")
- return null;
- if (p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1)
+ if (style.textDecoration === "underline" || p.parentElement.outerHTML.indexOf("text-decoration-style:line") !== -1) {
return null;
+ }
}
return false;
}
@@ -616,6 +620,7 @@ export class ImageResizeView {
e.preventDefault();
e.stopPropagation();
DocServer.GetRefField(node.attrs.docid).then(async linkDoc => {
+ const location = node.attrs.location;
if (linkDoc instanceof Doc) {
let proto = Doc.GetProto(linkDoc);
let targetContext = await Cast(proto.targetContext, Doc);
@@ -633,11 +638,11 @@ export class ImageResizeView {
DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
} else {
DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, false, document => addDocTab(document, undefined, location ? location : "inTab"));
- } e.ctrlKey
+ }
}
});
}
- }
+ };
this._handle.onpointerdown = function (e: any) {
e.preventDefault();
e.stopPropagation();
@@ -776,11 +781,11 @@ export class FootnoteView {
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));
+ let outerTr = this.outerView.state.tr, offsetMap = StepMap.offset(this.getPos() + 1);
+ for (let transaction of transactions) {
+ let steps = transaction.steps;
+ for (let step of steps) {
+ outerTr.step(step.map(offsetMap));
}
}
if (outerTr.docChanged) this.outerView.dispatch(outerTr);
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 1d0916ac0..ff4451824 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -19,6 +19,7 @@ export interface ScriptSucccess {
export interface ScriptError {
success: false;
error: any;
+ result: any;
}
export type ScriptResult = ScriptSucccess | ScriptError;
@@ -27,7 +28,7 @@ export interface CompiledScript {
readonly compiled: true;
readonly originalScript: string;
readonly options: Readonly<ScriptOptions>;
- run(args?: { [name: string]: any }): ScriptResult;
+ run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult;
}
export interface CompileError {
@@ -100,7 +101,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
// let params: any[] = [Docs, ...fieldTypes];
let compiledFunction = new Function(...paramNames, `return ${script}`);
let { capturedVariables = {} } = options;
- let run = (args: { [name: string]: any } = {}): ScriptResult => {
+ let run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
let argsArray: any[] = [];
for (let name of customParams) {
if (name === "this") {
@@ -127,7 +128,8 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
if (batch) {
batch.end();
}
- return { success: false, error };
+ onError && onError(error);
+ return { success: false, error, result: errorVal };
}
};
return { compiled: true, run, originalScript, options };
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ee5a83710..d8b9dbec6 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -3,18 +3,22 @@ import { DocServer } from '../DocServer';
import { Doc } from '../../new_fields/Doc';
import { Id } from '../../new_fields/FieldSymbols';
import { Utils } from '../../Utils';
+import { ResultParameters } from '../northstar/model/idea/idea';
+import { DocumentType } from '../documents/DocumentTypes';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
export interface IdSearchResult {
ids: string[];
+ lines: string[][];
numFound: number;
highlighting: HighlightingResult | undefined;
}
export interface DocSearchResult {
docs: Doc[];
+ lines: string[][];
numFound: number;
highlighting: HighlightingResult | undefined;
}
@@ -30,16 +34,31 @@ export namespace SearchUtil {
export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
query = query || "*"; //If we just have a filter query, search for * as the query
- const result: IdSearchResult = JSON.parse(await rp.get(Utils.prepend("/search"), {
+ let result: IdSearchResult = JSON.parse(await rp.get(Utils.prepend("/search"), {
qs: { ...options, q: query },
}));
if (!returnDocs) {
return result;
}
- const { ids, numFound, highlighting } = result;
+
+ let { ids, numFound, highlighting } = result;
+ let lines: string[][] = ids.map(i => []);
+
+ let txtresult = query !== "*" && JSON.parse(await rp.get(Utils.prepend("/textsearch"), {
+ qs: { ...options, q: query },
+ }));
+ let fileids = txtresult ? txtresult.ids : [];
+ await Promise.all(fileids.map(async (tr: string, i: number) => {
+ let docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query
+ let docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } }));
+ ids.push(...docResult.ids);
+ lines.push(...docResult.ids.map((dr: any) => txtresult.lines[i]));
+ numFound += docResult.numFound;
+ }));
+
const docMap = await DocServer.GetRefFields(ids);
- const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
- return { docs, numFound, highlighting };
+ const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc && doc.type !== DocumentType.KVP);
+ return { docs, numFound, highlighting, lines };
}
export async function GetAliasesOfDocument(doc: Doc): Promise<Doc[]>;
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 9efef888d..a02a270ee 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -24,6 +24,10 @@ export namespace SelectionManager {
manager.SelectedDocuments.push(docView);
// console.log(manager.SelectedDocuments);
docView.props.whenActiveChanged(true);
+ } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) {
+ manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false));
+ manager.SelectedDocuments = [docView];
+ FormattedTextBox.InputBoxOverlay = undefined;
}
}
@action
@@ -84,20 +88,4 @@ export namespace SelectionManager {
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments.slice();
}
- export function ViewsSortedHorizontally(): DocumentView[] {
- let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
- if (NumCast(doc1.props.Document.x) > NumCast(doc2.props.Document.x)) return 1;
- if (NumCast(doc1.props.Document.x) < NumCast(doc2.props.Document.x)) return -1;
- return 0;
- });
- return sorted;
- }
- export function ViewsSortedVertically(): DocumentView[] {
- let sorted = SelectionManager.SelectedDocuments().slice().sort((doc1, doc2) => {
- if (NumCast(doc1.props.Document.y) > NumCast(doc2.props.Document.y)) return 1;
- if (NumCast(doc1.props.Document.y) < NumCast(doc2.props.Document.y)) return -1;
- return 0;
- });
- return sorted;
- }
}
diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss
new file mode 100644
index 000000000..9a4c5db30
--- /dev/null
+++ b/src/client/util/SharingManager.scss
@@ -0,0 +1,136 @@
+.sharing-interface {
+ display: flex;
+ flex-direction: column;
+
+ p {
+ font-size: 20px;
+ text-align: left;
+ font-style: italic;
+ padding: 0;
+ margin: 0 0 20px 0;
+ }
+
+ .hr-substitute {
+ border: solid black 0.5px;
+ margin-top: 20px;
+ }
+
+ .people-with-container {
+ display: flex;
+ height: 25px;
+
+ .people-with {
+ font-size: 14px;
+ margin: 0;
+ padding-top: 3px;
+ font-style: normal;
+ }
+
+ .people-with-select {
+ width: 126px;
+ outline: none;
+ }
+ }
+
+ .share-individual {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }
+
+ .users-list {
+ font-style: italic;
+ background: white;
+ border: 1px solid black;
+ padding-left: 10px;
+ padding-right: 10px;
+ max-height: 200px;
+ overflow: scroll;
+ height: -webkit-fill-available;
+ text-align: left;
+ display: flex;
+ align-content: center;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+ color: red;
+ }
+
+ .container {
+ display: block;
+ position: relative;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-size: 22px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ width: 700px;
+ min-width: 700px;
+ max-width: 700px;
+ text-align: left;
+ font-style: normal;
+ font-size: 15;
+ font-weight: normal;
+ padding: 0;
+
+ .padding {
+ padding: 0 0 0 20px;
+ color: black;
+ }
+
+ .permissions-dropdown {
+ outline: none;
+ }
+ }
+
+ .no-users {
+ margin-top: 20px;
+ }
+
+ .link-container {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 10px;
+ margin-left: auto;
+ margin-right: auto;
+
+ .link-box,
+ .copy {
+ padding: 10px;
+ border-radius: 10px;
+ padding: 10px;
+ border: solid black 1px;
+ }
+
+ .link-box {
+ background: white;
+ color: blue;
+ text-decoration: underline;
+ }
+
+ .copy {
+ margin-left: 20px;
+ cursor: alias;
+ border-radius: 50%;
+ width: 42px;
+ height: 42px;
+ transition: 1.5s all ease;
+ padding-top: 12px;
+ }
+ }
+
+ .close-button {
+ border-radius: 5px;
+ margin-top: 20px;
+ padding: 10px 0;
+ background: aliceblue;
+ transition: 0.5s ease all;
+ border: 1px solid;
+ border-color: aliceblue;
+ }
+
+ .close-button:hover {
+ border-color: black;
+ }
+} \ No newline at end of file
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
new file mode 100644
index 000000000..f427e40b1
--- /dev/null
+++ b/src/client/util/SharingManager.tsx
@@ -0,0 +1,293 @@
+import { observable, runInAction, action, autorun } from "mobx";
+import * as React from "react";
+import MainViewModal from "../views/MainViewModal";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { Doc, Opt } from "../../new_fields/Doc";
+import { DocServer } from "../DocServer";
+import { Cast, StrCast } from "../../new_fields/Types";
+import { listSpec } from "../../new_fields/Schema";
+import { List } from "../../new_fields/List";
+import { RouteStore } from "../../server/RouteStore";
+import * as RequestPromise from "request-promise";
+import { Utils } from "../../Utils";
+import "./SharingManager.scss";
+import { Id } from "../../new_fields/FieldSymbols";
+import { observer } from "mobx-react";
+import { MainView } from "../views/MainView";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import * as fa from '@fortawesome/free-solid-svg-icons';
+import { DocumentView } from "../views/nodes/DocumentView";
+import { SelectionManager } from "./SelectionManager";
+import { DocumentManager } from "./DocumentManager";
+import { CollectionVideoView } from "../views/collections/CollectionVideoView";
+import { CollectionPDFView } from "../views/collections/CollectionPDFView";
+import { CollectionView } from "../views/collections/CollectionView";
+
+library.add(fa.faCopy);
+
+export interface User {
+ email: string;
+ userDocumentId: string;
+}
+
+export enum SharingPermissions {
+ None = "Not Shared",
+ View = "Can View",
+ Comment = "Can Comment",
+ Edit = "Can Edit"
+}
+
+const ColorMapping = new Map<string, string>([
+ [SharingPermissions.None, "red"],
+ [SharingPermissions.View, "maroon"],
+ [SharingPermissions.Comment, "blue"],
+ [SharingPermissions.Edit, "green"]
+]);
+
+const SharingKey = "sharingPermissions";
+const PublicKey = "publicLinkPermissions";
+const DefaultColor = "black";
+
+@observer
+export default class SharingManager extends React.Component<{}> {
+ public static Instance: SharingManager;
+ @observable private isOpen = false;
+ @observable private users: User[] = [];
+ @observable private targetDoc: Doc | undefined;
+ @observable private targetDocView: DocumentView | undefined;
+ @observable private copied = false;
+ @observable private dialogueBoxOpacity = 1;
+ @observable private overlayOpacity = 0.4;
+
+ private get linkVisible() {
+ return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false;
+ }
+
+ public open = (target: DocumentView) => {
+ SelectionManager.DeselectAll();
+ this.populateUsers().then(action(() => {
+ this.targetDocView = target;
+ this.targetDoc = target.props.Document;
+ MainView.Instance.hasActiveModal = true;
+ this.isOpen = true;
+ if (!this.sharingDoc) {
+ this.sharingDoc = new Doc;
+ }
+ }));
+ }
+
+ public close = action(() => {
+ this.isOpen = false;
+ setTimeout(action(() => {
+ this.copied = false;
+ MainView.Instance.hasActiveModal = false;
+ this.targetDoc = undefined;
+ }), 500);
+ });
+
+ private get sharingDoc() {
+ return this.targetDoc ? Cast(this.targetDoc[SharingKey], Doc) as Doc : undefined;
+ }
+
+ private set sharingDoc(value: Doc | undefined) {
+ this.targetDoc && (this.targetDoc[SharingKey] = value);
+ }
+
+ constructor(props: {}) {
+ super(props);
+ SharingManager.Instance = this;
+ }
+
+ populateUsers = async () => {
+ let userList = await RequestPromise.get(Utils.prepend(RouteStore.getUsers));
+ runInAction(() => {
+ this.users = (JSON.parse(userList) as User[]).filter(({ email }) => email !== Doc.CurrentUserEmail);
+ });
+ }
+
+ setInternalSharing = async (user: User, state: string) => {
+ if (!this.sharingDoc) {
+ console.log("SHARING ABORTED!");
+ return;
+ }
+ let sharingDoc = await this.sharingDoc;
+ sharingDoc[user.userDocumentId] = state;
+ const userDocument = await DocServer.GetRefField(user.userDocumentId);
+ if (!(userDocument instanceof Doc)) {
+ console.log(`Couldn't get user document of user ${user.email}`);
+ return;
+ }
+ let target = this.targetDoc;
+ if (!target) {
+ console.log("SharingManager trying to share an undefined document!!");
+ return;
+ }
+ const notifDoc = await Cast(userDocument.optionalRightCollection, Doc);
+ if (notifDoc instanceof Doc) {
+ const data = await Cast(notifDoc.data, listSpec(Doc));
+ if (!data) {
+ console.log("UNABLE TO ACCESS NOTIFICATION DATA");
+ return;
+ }
+ console.log(`Attempting to set permissions to ${state} for the document ${target[Id]}`);
+ if (state !== SharingPermissions.None) {
+ const sharedDoc = Doc.MakeAlias(target);
+ if (data) {
+ data.push(sharedDoc);
+ } else {
+ notifDoc.data = new List([sharedDoc]);
+ }
+ } else {
+ let dataDocs = (await Promise.all(data.map(doc => doc))).map(doc => Doc.GetProto(doc));
+ if (dataDocs.includes(target)) {
+ console.log("Searching in ", dataDocs, "for", target);
+ dataDocs.splice(dataDocs.indexOf(target), 1);
+ console.log("SUCCESSFULLY UNSHARED DOC");
+ } else {
+ console.log("DIDN'T THINK WE HAD IT, SO NOT SUCCESSFULLY UNSHARED");
+ }
+ }
+ }
+ }
+
+ private setExternalSharing = (state: string) => {
+ let sharingDoc = this.sharingDoc;
+ if (!sharingDoc) {
+ return;
+ }
+ sharingDoc[PublicKey] = state;
+ }
+
+ private get sharingUrl() {
+ if (!this.targetDoc) {
+ return undefined;
+ }
+ let baseUrl = Utils.prepend("/doc/" + this.targetDoc[Id]);
+ return `${baseUrl}?sharing=true`;
+ }
+
+ copy = action(() => {
+ if (this.sharingUrl) {
+ Utils.CopyText(this.sharingUrl);
+ this.copied = true;
+ }
+ });
+
+ private get sharingOptions() {
+ return Object.values(SharingPermissions).map(permission => {
+ return (
+ <option key={permission} value={permission}>
+ {permission}
+ </option>
+ );
+ });
+ }
+
+ private focusOn = (contents: string) => {
+ let title = this.targetDoc ? StrCast(this.targetDoc.title) : "";
+ return (
+ <span
+ title={title}
+ onClick={() => {
+ let context: Opt<CollectionVideoView | CollectionPDFView | CollectionView>;
+ if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) {
+ DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, undefined, undefined, context.props.Document);
+ }
+ }}
+ onPointerEnter={action(() => {
+ if (this.targetDoc) {
+ Doc.BrushDoc(this.targetDoc);
+ this.dialogueBoxOpacity = 0.1;
+ this.overlayOpacity = 0.1;
+ }
+ })}
+ onPointerLeave={action(() => {
+ this.targetDoc && Doc.UnBrushDoc(this.targetDoc);
+ this.dialogueBoxOpacity = 1;
+ this.overlayOpacity = 0.4;
+ })}
+ >
+ {contents}
+ </span>
+ );
+ }
+
+ private get sharingInterface() {
+ return (
+ <div className={"sharing-interface"}>
+ <p className={"share-link"}>Manage the public link to {this.focusOn("this document...")}</p>
+ {!this.linkVisible ? (null) :
+ <div className={"link-container"}>
+ <div className={"link-box"} onClick={this.copy}>{this.sharingUrl}</div>
+ <div
+ title={"Copy link to clipboard"}
+ className={"copy"}
+ style={{ backgroundColor: this.copied ? "lawngreen" : "gainsboro" }}
+ onClick={this.copy}
+ >
+ <FontAwesomeIcon icon={fa.faCopy} />
+ </div>
+ </div>
+ }
+ <div className={"people-with-container"}>
+ {!this.linkVisible ? (null) : <p className={"people-with"}>People with this link</p>}
+ <select
+ className={"people-with-select"}
+ value={this.sharingDoc ? StrCast(this.sharingDoc[PublicKey], SharingPermissions.None) : SharingPermissions.None}
+ style={{
+ marginLeft: this.linkVisible ? 10 : 0,
+ color: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[PublicKey], SharingPermissions.None)) : DefaultColor,
+ borderColor: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[PublicKey], SharingPermissions.None)) : DefaultColor
+ }}
+ onChange={e => this.setExternalSharing(e.currentTarget.value)}
+ >
+ {this.sharingOptions}
+ </select>
+ </div>
+ <div className={"hr-substitute"} />
+ <p className={"share-individual"}>Privately share {this.focusOn("this document")} with an individual...</p>
+ <div className={"users-list"} style={{ display: this.users.length ? "block" : "flex" }}>
+ {!this.users.length ? "There are no other users in your database." :
+ this.users.map(user => {
+ return (
+ <div
+ key={user.email}
+ className={"container"}
+ >
+ <select
+ className={"permissions-dropdown"}
+ value={this.sharingDoc ? StrCast(this.sharingDoc[user.userDocumentId], SharingPermissions.None) : SharingPermissions.None}
+ style={{
+ color: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[user.userDocumentId], SharingPermissions.None)) : DefaultColor,
+ borderColor: this.sharingDoc ? ColorMapping.get(StrCast(this.sharingDoc[user.userDocumentId], SharingPermissions.None)) : DefaultColor
+ }}
+ onChange={e => this.setInternalSharing(user, e.currentTarget.value)}
+ >
+ {this.sharingOptions}
+
+ </select>
+ <span className={"padding"}>{user.email}</span>
+ </div>
+ );
+ })
+ }
+ </div>
+ <div className={"close-button"} onClick={this.close}>Done</div>
+ </div>
+ );
+ }
+
+ render() {
+ return (
+ <MainViewModal
+ contents={this.sharingInterface}
+ isDisplayed={this.isOpen}
+ interactive={true}
+ dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity}
+ overlayDisplayedOpacity={this.overlayOpacity}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index c376b6f86..a83a3949d 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -19,6 +19,7 @@ import { LinkManager } from "./LinkManager";
import { schema } from "./RichTextSchema";
import "./TooltipTextMenu.scss";
import { Cast, NumCast } from '../../new_fields/Types';
+import { updateBullets } from './ProsemirrorExampleTransfer';
const { toggleMark, setBlockType } = require("prosemirror-commands");
const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
@@ -58,10 +59,6 @@ export class TooltipTextMenu {
private _collapsed: boolean = false;
- @observable
- private _storedMarks: Mark<any>[] | null | undefined;
-
-
constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
this.view = view;
this.editorProps = editorProps;
@@ -84,8 +81,6 @@ export class TooltipTextMenu {
this.dragElement(dragger);
- this._storedMarks = this.view.state.storedMarks;
-
// this.createCollapse();
// if (this._collapseBtn) {
// this.tooltip.appendChild(this._collapseBtn.render(this.view).dom);
@@ -280,7 +275,7 @@ export class TooltipTextMenu {
if (DocumentManager.Instance.getDocumentView(f)) {
DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false);
}
- else if (CollectionDockingView.Instance) CollectionDockingView.Instance.AddRightSplit(f, undefined);
+ else this.editorProps.addDocTab(f, undefined, "onRight");
}
}));
}
@@ -308,12 +303,17 @@ export class TooltipTextMenu {
{
handlers: {
dragComplete: action(() => {
- let linkDoc = dragData.linkDocument;
- let proto = Doc.GetProto(linkDoc);
- if (proto && docView && docView.props.ContainingCollectionView) {
- proto.sourceContext = docView.props.ContainingCollectionView.props.Document;
+ if (dragData.linkDocument) {
+ let linkDoc = dragData.linkDocument;
+ let proto = Doc.GetProto(linkDoc);
+ if (proto && docView) {
+ proto.sourceContext = docView.props.ContainingCollectionDoc;
+ }
+ let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab");
+ if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) {
+ proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link
+ }
}
- linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
}),
},
hideSource: false
@@ -395,17 +395,24 @@ export class TooltipTextMenu {
}
}
- makeLinkWithState = (state: EditorState, target: string, location: string) => {
- let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
- }
+ // makeLinkWithState = (state: EditorState, target: string, location: string) => {
+ // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location });
+ // }
- makeLink = (target: string, location: string) => {
+ makeLink = (targetDoc: Doc, location: string): string => {
+ let target = Utils.prepend("/doc/" + targetDoc[Id]);
let node = this.view.state.selection.$from.nodeAfter;
- let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location });
+ let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] });
this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link));
this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link));
node = this.view.state.selection.$from.nodeAfter;
link = node && node.marks.find(m => m.type.name === "link");
+ if (node) {
+ if (node.text) {
+ return node.text;
+ }
+ }
+ return "";
}
deleteLink = () => {
@@ -496,7 +503,7 @@ export class TooltipTextMenu {
if (markType.name[0] === 'p') {
let size = this.fontSizeToNum.get(markType);
if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
- let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+ let ruleProvider = this.editorProps.ruleProvider;
let heading = NumCast(this.editorProps.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleSize_" + heading] = size;
@@ -505,17 +512,18 @@ export class TooltipTextMenu {
else {
let fontName = this.fontStylesToName.get(markType);
if (fontName) { this.updateFontStyleDropdown(fontName); }
- let ruleProvider = Cast(this.editorProps.Document.ruleProvider, Doc) as Doc;
+ let ruleProvider = this.editorProps.ruleProvider;
let heading = NumCast(this.editorProps.Document.heading);
if (ruleProvider && heading) {
ruleProvider["ruleFont_" + heading] = fontName;
}
}
//actually apply font
- return toggleMark(markType)(view.state, view.dispatch, view);
- }
- else {
- return;
+ if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) {
+ view.dispatch(updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type,
+ { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema));
+ }
+ else toggleMark(markType)(view.state, view.dispatch, view);
}
}
@@ -523,12 +531,12 @@ export class TooltipTextMenu {
tx2.doc.descendants((node: any, offset: any, index: any) => {
if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) {
let path = (tx2.doc.resolve(offset) as any).path;
- let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && (c as any).type === schema.nodes.ordered_list ? 1 : 0), 0);
+ let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0);
if (node.type === schema.nodes.ordered_list) depth++;
tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks);
}
});
- };
+ }
//remove all node typeand apply the passed-in one to the selected text
changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => {
//remove oldif (nodeType) { //add new
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 156390fd3..7abb9d1ee 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -127,7 +127,7 @@ export namespace UndoManager {
export function StartBatch(batchName: string): Batch {
batchCounter++;
- if (batchCounter > 0) {
+ if (batchCounter > 0 && currentBatch === undefined) {
currentBatch = [];
}
return new Batch(batchName);
diff --git a/src/client/util/prosemirrorPatches.js b/src/client/util/prosemirrorPatches.js
index 188e3e1c5..269423482 100644
--- a/src/client/util/prosemirrorPatches.js
+++ b/src/client/util/prosemirrorPatches.js
@@ -82,7 +82,7 @@ function sinkListItem(itemType) {
if (dispatch) {
var nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type;
var inner = prosemirrorModel.Fragment.from(nestedBefore ? itemType.create() : null);
- let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create(parent.attrs, inner)))),
+ let slice = new prosemirrorModel.Slice(prosemirrorModel.Fragment.from(itemType.create(null, prosemirrorModel.Fragment.from(parent.type.create({ ...parent.attrs, fontSize: parent.attrs.fontSize ? parent.attrs.fontSize - 4 : undefined }, inner)))),
nestedBefore ? 3 : 1, 0);
var before = range.start, after = range.end;
dispatch(state.tr.step(new prosemirrorTransform.ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,