aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/DocumentManager.ts19
-rw-r--r--src/client/util/DragManager.ts28
-rw-r--r--src/client/util/RichTextSchema.tsx87
-rw-r--r--src/client/util/SelectionManager.ts21
-rw-r--r--src/client/util/TooltipTextMenu.tsx109
-rw-r--r--src/client/util/UndoManager.ts5
6 files changed, 246 insertions, 23 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3151bcfb5..a8b643d4d 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,7 +1,7 @@
import { computed, observable } from 'mobx';
import { DocumentView } from '../views/nodes/DocumentView';
import { Doc } from '../../new_fields/Doc';
-import { FieldValue, Cast, NumCast } from '../../new_fields/Types';
+import { FieldValue, Cast, NumCast, BoolCast } from '../../new_fields/Types';
import { listSpec } from '../../new_fields/Schema';
import { undoBatch } from './UndoManager';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
@@ -72,8 +72,8 @@ export class DocumentManager {
@computed
public get LinkedDocumentViews() {
- return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => {
- let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc));
+ return DocumentManager.Instance.DocumentViews.filter(dv => dv.isSelected() || BoolCast(dv.props.Document.libraryBrush, false)).reduce((pairs, dv) => {
+ let linksList = Cast(dv.props.Document.linkedToDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
if (linksList && linksList.length) {
pairs.push(...linksList.reduce((pairs, link) => {
if (link) {
@@ -86,6 +86,19 @@ export class DocumentManager {
return pairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc }[]));
}
+ linksList = Cast(dv.props.Document.linkedFromDocs, listSpec(Doc), []).filter(d => d).map(d => d as Doc);
+ if (linksList && linksList.length) {
+ pairs.push(...linksList.reduce((pairs, link) => {
+ if (link) {
+ let linkFromDoc = FieldValue(Cast(link.linkedFrom, Doc));
+ if (linkFromDoc) {
+ DocumentManager.Instance.getDocumentViews(linkFromDoc).map(docView1 =>
+ pairs.push({ a: dv, b: docView1, l: link }));
+ }
+ }
+ return pairs;
+ }, pairs));
+ }
return pairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc }[]);
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index a3dbe6e43..eaf851a75 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,11 +1,9 @@
-import { action } from "mobx";
+import { action, runInAction } from "mobx";
+import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Cast } from "../../new_fields/Types";
import { emptyFunction } from "../../Utils";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import * as globalCssVariables from "../views/globalCssVariables.scss";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
-import { Doc } from "../../new_fields/Doc";
-import { Cast } from "../../new_fields/Types";
-import { listSpec } from "../../new_fields/Schema";
export type dropActionType = "alias" | "copy" | undefined;
export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Doc, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType) {
@@ -42,12 +40,14 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Doc) {
let srcTarg = sourceDoc.proto;
- let draggedDocs = srcTarg ?
- Cast(srcTarg.linkedToDocs, listSpec(Doc), []).map(linkDoc =>
- Cast(linkDoc.linkedTo, Doc) as Doc) : [];
- let draggedFromDocs = srcTarg ?
- Cast(srcTarg.linkedFromDocs, listSpec(Doc), []).map(linkDoc =>
- Cast(linkDoc.linkedFrom, Doc) as Doc) : [];
+ let draggedDocs: Doc[] = [];
+ let draggedFromDocs: Doc[] = []
+ if (srcTarg) {
+ let linkToDocs = await DocListCast(srcTarg.linkedToDocs);
+ let linkFromDocs = await DocListCast(srcTarg.linkedFromDocs);
+ if (linkToDocs) draggedDocs = linkToDocs.map(linkDoc => Cast(linkDoc.linkedTo, Doc) as Doc);
+ if (linkFromDocs) draggedFromDocs = linkFromDocs.map(linkDoc => Cast(linkDoc.linkedFrom, Doc) as Doc);
+ }
draggedDocs.push(...draggedFromDocs);
if (draggedDocs.length) {
let moddrag: Doc[] = [];
@@ -152,7 +152,10 @@ export namespace DragManager {
[id: string]: any;
}
+ export let StartDragFunctions: (() => void)[] = [];
+
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) {
+ runInAction(() => StartDragFunctions.map(func => func()));
StartDrag(eles, dragData, downX, downY, options,
(dropData: { [id: string]: any }) =>
(dropData.droppedDocuments = dragData.userDropAction == "alias" || (!dragData.userDropAction && dragData.dropAction == "alias") ?
@@ -170,6 +173,7 @@ export namespace DragManager {
droppedDocuments: Doc[] = [];
linkSourceDocument: Doc;
blacklist: Doc[];
+ dontClearTextBox?: boolean;
[id: string]: any;
}
@@ -186,7 +190,6 @@ export namespace DragManager {
dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
- MainOverlayTextBox.Instance.SetTextDoc();
let scaleXs: number[] = [];
let scaleYs: number[] = [];
@@ -214,6 +217,7 @@ export namespace DragManager {
dragElement.style.top = "0";
dragElement.style.bottom = "";
dragElement.style.left = "0";
+ dragElement.style.color = "black";
dragElement.style.transformOrigin = "0 0";
dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "1000";
dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 9ef71e305..c0e6f7899 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -84,6 +84,7 @@ export const nodes: { [index: string]: NodeSpec } = {
inline: true,
attrs: {
src: {},
+ width: { default: "100px" },
alt: { default: null },
title: { default: null }
},
@@ -94,11 +95,16 @@ export const nodes: { [index: string]: NodeSpec } = {
return {
src: dom.getAttribute("src"),
title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt")
- };
+ alt: dom.getAttribute("alt"),
+ width: Math.min(100, Number(dom.getAttribute("width"))),
+ }
}
}],
- toDOM(node: any) { return ["img", node.attrs]; }
+ // TODO if we don't define toDom, something weird happens: dragging the image will not move it but clone it. Why?
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}` }
+ return ["img", { ...node.attrs, ...attrs }]
+ }
},
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
@@ -290,6 +296,13 @@ export const marks: { [index: string]: MarkSpec } = {
}]
},
+ p14: {
+ parseDOM: [{ style: 'font-size: 14px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 14px;'
+ }]
+ },
+
p16: {
parseDOM: [{ style: 'font-size: 16px;' }],
toDOM: () => ['span', {
@@ -325,7 +338,75 @@ export const marks: { [index: string]: MarkSpec } = {
}]
},
};
+function getFontSize(element: any) {
+ return parseFloat((getComputedStyle(element) as any).fontSize);
+}
+
+export class ImageResizeView {
+ _handle: HTMLElement;
+ _img: HTMLElement;
+ _outer: HTMLElement;
+ constructor(node: any, view: any, getPos: any) {
+ this._handle = document.createElement("span");
+ this._img = document.createElement("img");
+ this._outer = document.createElement("span");
+ this._outer.style.position = "relative";
+ this._outer.style.width = node.attrs.width;
+ this._outer.style.display = "inline-block";
+ this._outer.style.overflow = "hidden";
+
+ this._img.setAttribute("src", node.attrs.src);
+ this._img.style.width = "100%";
+ this._handle.style.position = "absolute";
+ this._handle.style.width = "20px";
+ this._handle.style.height = "20px";
+ this._handle.style.backgroundColor = "blue";
+ this._handle.style.borderRadius = "15px";
+ this._handle.style.display = "none";
+ this._handle.style.bottom = "-10px";
+ this._handle.style.right = "-10px";
+ let self = this;
+ this._handle.onpointerdown = function (e: any) {
+ e.preventDefault();
+ e.stopPropagation();
+ const startX = e.pageX;
+ const startWidth = parseFloat(node.attrs.width);
+ const onpointermove = (e: any) => {
+ const currentX = e.pageX;
+ const diffInPx = currentX - startX;
+ self._outer.style.width = `${startWidth + diffInPx}`;
+ }
+
+ const onpointerup = () => {
+ document.removeEventListener("pointermove", onpointermove);
+ document.removeEventListener("pointerup", onpointerup);
+ view.dispatch(
+ view.state.tr.setNodeMarkup(getPos(), null,
+ { src: node.attrs.src, width: self._outer.style.width })
+ .setSelection(view.state.selection));
+ }
+
+ document.addEventListener("pointermove", onpointermove)
+ document.addEventListener("pointerup", onpointerup)
+ }
+ this._outer.appendChild(this._handle);
+ this._outer.appendChild(this._img);
+ (this as any).dom = this._outer;
+ }
+
+ selectNode() {
+ this._img.classList.add("ProseMirror-selectednode");
+
+ this._handle.style.display = "";
+ }
+
+ deselectNode() {
+ this._img.classList.remove("ProseMirror-selectednode");
+
+ this._handle.style.display = "none";
+ }
+}
// :: Schema
// This schema rougly corresponds to the document schema used by
// [CommonMark](http://commonmark.org/), minus the list elements,
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index fe5acf4b4..8c92c2023 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,7 +1,8 @@
import { observable, action } from "mobx";
import { Doc } from "../../new_fields/Doc";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
import { DocumentView } from "../views/nodes/DocumentView";
+import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { NumCast } from "../../new_fields/Types";
export namespace SelectionManager {
class Manager {
@@ -25,7 +26,7 @@ export namespace SelectionManager {
DeselectAll(): void {
manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments = [];
- MainOverlayTextBox.Instance.SetTextDoc();
+ FormattedTextBox.InputBoxOverlay = undefined;
}
@action
ReselectAll() {
@@ -68,4 +69,20 @@ export namespace SelectionManager {
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
+ export function ViewsSortedVertically(): 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 ViewsSortedHorizontally(): 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/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 68a73375e..6eb654319 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -6,19 +6,28 @@ import { keymap } from "prosemirror-keymap";
import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
-import { Schema, NodeType, MarkType } from "prosemirror-model";
+import { Schema, NodeType, MarkType, Mark } 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 { liftTarget } from 'prosemirror-transform';
+import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
import {
faListUl,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FieldViewProps } from "../views/nodes/FieldView";
import { throwStatement } from "babel-types";
+import { View } from "@react-pdf/renderer";
+import { DragManager } from "./DragManager";
+import { Doc, Opt, Field } from "../../new_fields/Doc";
+import { Id } from "../../new_fields/RefField";
+import { Utils } from "../northstar/utils/Utils";
+import { DocServer } from "../DocServer";
+import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView";
+import { CollectionDockingView } from "../views/collections/CollectionDockingView";
+import { DocumentManager } from "./DocumentManager";
const SVG = "http://www.w3.org/2000/svg";
@@ -37,6 +46,9 @@ export class TooltipTextMenu {
private fontStylesToName: Map<MarkType, string>;
private listTypeToIcon: Map<NodeType, string>;
private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+ private linkEditor?: HTMLDivElement;
+ private linkText?: HTMLDivElement;
+ private linkDrag?: HTMLImageElement;
//dropdown doms
private fontSizeDom?: Node;
private fontStyleDom?: Node;
@@ -94,6 +106,7 @@ export class TooltipTextMenu {
this.fontSizeToNum = new Map();
this.fontSizeToNum.set(schema.marks.p10, 10);
this.fontSizeToNum.set(schema.marks.p12, 12);
+ this.fontSizeToNum.set(schema.marks.p14, 14);
this.fontSizeToNum.set(schema.marks.p16, 16);
this.fontSizeToNum.set(schema.marks.p24, 24);
this.fontSizeToNum.set(schema.marks.p32, 32);
@@ -149,6 +162,96 @@ export class TooltipTextMenu {
this.tooltip.appendChild(this.fontStyleDom);
}
+ updateLinkMenu() {
+ if (!this.linkEditor || !this.linkText) {
+ this.linkEditor = document.createElement("div");
+ this.linkEditor.style.color = "white";
+ this.linkText = document.createElement("div");
+ this.linkText.style.cssFloat = "left";
+ this.linkText.style.marginRight = "5px";
+ this.linkText.style.marginLeft = "5px";
+ this.linkText.setAttribute("contenteditable", "true");
+ this.linkText.style.whiteSpace = "nowrap";
+ this.linkText.style.width = "150px";
+ this.linkText.style.overflow = "hidden";
+ this.linkText.style.color = "white";
+ this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }
+ let linkBtn = document.createElement("div");
+ linkBtn.textContent = ">>";
+ linkBtn.style.width = "20px";
+ linkBtn.style.height = "20px";
+ linkBtn.style.color = "white";
+ linkBtn.style.cssFloat = "left";
+ linkBtn.onpointerdown = (e: PointerEvent) => {
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = node && node.marks.find(m => m.type.name === "link");
+ if (link) {
+ console.log("Link to : " + link.attrs.href);
+ let href: string = link.attrs.href;
+ if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
+ let docid = href.replace(DocServer.prepend("/doc/"), "");
+ DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
+ if (f instanceof Doc) {
+ if (DocumentManager.Instance.getDocumentView(f))
+ DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
+ else CollectionDockingView.Instance.AddRightSplit(f);
+ }
+ }));
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+ this.linkDrag = document.createElement("img");
+ this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png";
+ this.linkDrag.style.width = "20px";
+ this.linkDrag.style.height = "20px";
+ this.linkDrag.style.color = "white";
+ this.linkDrag.style.background = "black";
+ this.linkDrag.style.cssFloat = "left";
+ this.linkDrag.onpointerdown = (e: PointerEvent) => {
+ let dragData = new DragManager.LinkDragData(this.editorProps.Document);
+ dragData.dontClearTextBox = true;
+ DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY,
+ {
+ handlers: {
+ dragComplete: action(() => {
+ let m = dragData.droppedDocuments as Doc[];
+ this.makeLink(DocServer.prepend("/doc/" + m[0][Id]));
+ }),
+ },
+ hideSource: false
+ })
+ };
+ this.linkEditor.appendChild(this.linkDrag);
+ this.linkEditor.appendChild(this.linkText);
+ this.linkEditor.appendChild(linkBtn);
+ this.tooltip.appendChild(this.linkEditor);
+ }
+
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = node && node.marks.find(m => m.type.name === "link");
+ this.linkText.textContent = link ? link.attrs.href : "-empty-";
+
+ this.linkText.onkeydown = (e: KeyboardEvent) => {
+ if (e.key === "Enter") {
+ this.makeLink(this.linkText!.textContent!);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+ this.tooltip.appendChild(this.linkEditor);
+ }
+
+ makeLink = (target: string) => {
+ let node = this.view.state.selection.$from.nodeAfter;
+ let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target });
+ 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");
+ }
+
//will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
updateListItemDropdown(label: string, listTypeBtn: Node) {
//remove old btn
@@ -347,6 +450,8 @@ export class TooltipTextMenu {
} else { //multiple font sizes selected
this.updateFontSizeDropdown("Various");
}
+
+ this.updateLinkMenu();
}
//finds all active marks on selection in given group
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 0b5280c4a..c0ed015bd 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,6 @@
import { observable, action, runInAction } from "mobx";
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();
@@ -94,6 +93,10 @@ export namespace UndoManager {
return redoStack.length > 0;
}
+ export function PrintBatches(): void {
+ GetOpenBatches().forEach(batch => console.log(batch.batchName));
+ }
+
let openBatches: Batch[] = [];
export function GetOpenBatches(): Without<Batch, 'end'>[] {
return openBatches;