aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorloudonclear <loudon_cohen@brown.edu>2019-06-14 14:36:41 -0400
committerloudonclear <loudon_cohen@brown.edu>2019-06-14 14:36:41 -0400
commit84ee857c73715c44258313b1481358db03a33b32 (patch)
tree29db2c072212092d37f350024b93c42b1ec5e7d8 /src
parent35786bfec967b2a9f1abc570bffe73530c053207 (diff)
parent618d3717e118f978de976cb34e8bc2051c726ffc (diff)
pull from master
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/util/DragManager.ts21
-rw-r--r--src/client/util/ProsemirrorCopy/prompt.js179
-rw-r--r--src/client/util/ProsemirrorExampleTransfer.ts (renamed from src/client/util/ProsemirrorKeymap.ts)6
-rw-r--r--src/client/util/RichTextSchema.tsx129
-rw-r--r--src/client/util/SelectionManager.ts38
-rw-r--r--src/client/util/TooltipTextMenu.scss76
-rw-r--r--src/client/util/TooltipTextMenu.tsx166
-rw-r--r--src/client/views/DocumentDecorations.tsx57
-rw-r--r--src/client/views/InkingControl.scss10
-rw-r--r--src/client/views/InkingControl.tsx35
-rw-r--r--src/client/views/Main.scss30
-rw-r--r--src/client/views/MainOverlayTextBox.tsx51
-rw-r--r--src/client/views/MainView.tsx56
-rw-r--r--src/client/views/PresentationView.tsx1
-rw-r--r--src/client/views/Templates.tsx16
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx5
-rw-r--r--src/client/views/collections/CollectionSubView.tsx19
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx24
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx19
-rw-r--r--src/client/views/nodes/DocumentView.tsx24
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx147
-rw-r--r--src/debug/Test.tsx1
-rw-r--r--src/new_fields/Doc.ts3
-rw-r--r--src/new_fields/util.ts4
-rw-r--r--src/server/index.ts1
30 files changed, 827 insertions, 303 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 611c61135..e8a80bdc3 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -15,7 +15,7 @@ export class Utils {
return v5(seed, v5.URL);
}
- public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } {
+ public static GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } {
if (!ele) {
return { scale: 1, translateX: 1, translateY: 1 };
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2ae127e21..b1df89dbd 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -166,7 +166,7 @@ export namespace Docs {
}
function CreateTextPrototype(): Doc {
let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" });
+ { x: 0, y: 0, width: 300, backgroundColor: "#f1efeb" });
return textProto;
}
function CreatePdfPrototype(): Doc {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4c9f798a8..ab00fabe1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,9 +1,11 @@
-import { action, runInAction } from "mobx";
+import { action, runInAction, observable } from "mobx";
import { Doc, DocListCastAsync } 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 { URLField } from "../../new_fields/URLField";
+import { SelectionManager } from "./SelectionManager";
export type dropActionType = "alias" | "copy" | undefined;
export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) {
@@ -180,10 +182,24 @@ export namespace DragManager {
[id: string]: any;
}
+ export class EmbedDragData {
+ constructor(embeddableSourceDoc: Doc) {
+ this.embeddableSourceDoc = embeddableSourceDoc;
+ this.urlField = embeddableSourceDoc.data instanceof URLField ? embeddableSourceDoc.data : undefined;
+ }
+ embeddableSourceDoc: Doc;
+ urlField?: URLField;
+ [id: string]: any;
+ }
+
export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) {
StartDrag([ele], dragData, downX, downY, options);
}
+ export function StartEmbedDrag(ele: HTMLElement, dragData: EmbedDragData, downX: number, downY: number, options?: DragOptions) {
+ StartDrag([ele], dragData, downX, downY, options);
+ }
+
export let AbortDrag: () => void = emptyFunction;
function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
@@ -194,7 +210,7 @@ export namespace DragManager {
dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
-
+ SelectionManager.SetIsDragging(true);
let scaleXs: number[] = [];
let scaleYs: number[] = [];
let xs: number[] = [];
@@ -293,6 +309,7 @@ export namespace DragManager {
};
let hideDragElements = () => {
+ SelectionManager.SetIsDragging(false);
dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
eles.map(ele => (ele.hidden = false));
};
diff --git a/src/client/util/ProsemirrorCopy/prompt.js b/src/client/util/ProsemirrorCopy/prompt.js
new file mode 100644
index 000000000..b9068195f
--- /dev/null
+++ b/src/client/util/ProsemirrorCopy/prompt.js
@@ -0,0 +1,179 @@
+const prefix = "ProseMirror-prompt"
+
+export function openPrompt(options) {
+ let wrapper = document.body.appendChild(document.createElement("div"))
+ wrapper.className = prefix
+ wrapper.style.zIndex = 1000;
+ wrapper.style.width = 250;
+ wrapper.style.textAlign = "center";
+
+ let mouseOutside = e => { if (!wrapper.contains(e.target)) close() }
+ setTimeout(() => window.addEventListener("mousedown", mouseOutside), 50)
+ let close = () => {
+ window.removeEventListener("mousedown", mouseOutside)
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper)
+ }
+
+ let domFields = []
+ for (let name in options.fields) domFields.push(options.fields[name].render())
+
+ let submitButton = document.createElement("button")
+ submitButton.type = "submit"
+ submitButton.className = prefix + "-submit"
+ submitButton.textContent = "OK"
+ let cancelButton = document.createElement("button")
+ cancelButton.type = "button"
+ cancelButton.className = prefix + "-cancel"
+ cancelButton.textContent = "Cancel"
+ cancelButton.addEventListener("click", close)
+
+ let form = wrapper.appendChild(document.createElement("form"))
+ let title = document.createElement("h5")
+ title.style.marginBottom = 15
+ title.style.marginTop = 10
+ if (options.title) form.appendChild(title).textContent = options.title
+ domFields.forEach(field => {
+ form.appendChild(document.createElement("div")).appendChild(field)
+ })
+ let b = document.createElement("div");
+ b.style.marginTop = 15;
+ let buttons = form.appendChild(b)
+ // buttons.className = prefix + "-buttons"
+ buttons.appendChild(submitButton)
+ buttons.appendChild(document.createTextNode(" "))
+ buttons.appendChild(cancelButton)
+
+ let box = wrapper.getBoundingClientRect()
+ wrapper.style.top = options.flyout_top + "px"
+ wrapper.style.left = options.flyout_left + "px"
+
+ let submit = () => {
+ let params = getValues(options.fields, domFields)
+ if (params) {
+ close()
+ options.callback(params)
+ }
+ }
+
+ form.addEventListener("submit", e => {
+ e.preventDefault()
+ submit()
+ })
+
+ form.addEventListener("keydown", e => {
+ if (e.keyCode == 27) {
+ e.preventDefault()
+ close()
+ } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) {
+ e.preventDefault()
+ submit()
+ } else if (e.keyCode == 9) {
+ window.setTimeout(() => {
+ if (!wrapper.contains(document.activeElement)) close()
+ }, 500)
+ }
+ })
+
+ let input = form.elements[0]
+ if (input) input.focus()
+}
+
+function getValues(fields, domFields) {
+ let result = Object.create(null), i = 0
+ for (let name in fields) {
+ let field = fields[name], dom = domFields[i++]
+ let value = field.read(dom), bad = field.validate(value)
+ if (bad) {
+ reportInvalid(dom, bad)
+ return null
+ }
+ result[name] = field.clean(value)
+ }
+ return result
+}
+
+function reportInvalid(dom, message) {
+ // FIXME this is awful and needs a lot more work
+ let parent = dom.parentNode
+ let msg = parent.appendChild(document.createElement("div"))
+ msg.style.left = (dom.offsetLeft + dom.offsetWidth + 2) + "px"
+ msg.style.top = (dom.offsetTop - 5) + "px"
+ msg.className = "ProseMirror-invalid"
+ msg.textContent = message
+ setTimeout(() => parent.removeChild(msg), 1500)
+}
+
+// ::- The type of field that `FieldPrompt` expects to be passed to it.
+export class Field {
+ // :: (Object)
+ // Create a field with the given options. Options support by all
+ // field types are:
+ //
+ // **`value`**`: ?any`
+ // : The starting value for the field.
+ //
+ // **`label`**`: string`
+ // : The label for the field.
+ //
+ // **`required`**`: ?bool`
+ // : Whether the field is required.
+ //
+ // **`validate`**`: ?(any) → ?string`
+ // : A function to validate the given value. Should return an
+ // error message if it is not valid.
+ constructor(options) { this.options = options }
+
+ // render:: (state: EditorState, props: Object) → dom.Node
+ // Render the field to the DOM. Should be implemented by all subclasses.
+
+ // :: (dom.Node) → any
+ // Read the field's value from its DOM node.
+ read(dom) { return dom.value }
+
+ // :: (any) → ?string
+ // A field-type-specific validation function.
+ validateType(_value) { }
+
+ validate(value) {
+ if (!value && this.options.required)
+ return "Required field"
+ return this.validateType(value) || (this.options.validate && this.options.validate(value))
+ }
+
+ clean(value) {
+ return this.options.clean ? this.options.clean(value) : value
+ }
+}
+
+// ::- A field class for single-line text fields.
+export class TextField extends Field {
+ render() {
+ let input = document.createElement("input")
+ input.type = "text"
+ input.placeholder = this.options.label
+ input.value = this.options.value || ""
+ input.autocomplete = "off"
+ input.style.marginBottom = 4
+ input.style.border = "1px solid black"
+ input.style.padding = "4px 4px"
+ return input
+ }
+}
+
+
+// ::- A field class for dropdown fields based on a plain `<select>`
+// tag. Expects an option `options`, which should be an array of
+// `{value: string, label: string}` objects, or a function taking a
+// `ProseMirror` instance and returning such an array.
+export class SelectField extends Field {
+ render() {
+ let select = document.createElement("select")
+ this.options.options.forEach(o => {
+ let opt = select.appendChild(document.createElement("option"))
+ opt.value = o.value
+ opt.selected = o.value == this.options.value
+ opt.label = o.label
+ })
+ return select
+ }
+}
diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorExampleTransfer.ts
index 00d086b97..091926d0a 100644
--- a/src/client/util/ProsemirrorKeymap.ts
+++ b/src/client/util/ProsemirrorExampleTransfer.ts
@@ -1,4 +1,4 @@
-import { Schema } from "prosemirror-model";
+import { Schema, NodeType } from "prosemirror-model";
import {
wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
joinUp, joinDown, lift, selectParentNode
@@ -50,7 +50,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
}
if (type = schema.nodes.bullet_list) {
- bind("Ctrl-b", wrapInList(type));
+ bind("Ctrl-.", wrapInList(type));
}
if (type = schema.nodes.ordered_list) {
bind("Ctrl-n", wrapInList(type));
@@ -97,4 +97,4 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?:
}
return keys;
-} \ No newline at end of file
+}
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 3e3e98206..e1e595925 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,9 +1,9 @@
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 { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType, Slice } from "prosemirror-model";
+import { joinUp, lift, setBlockType, toggleMark, wrapIn, selectNodeForward, deleteSelection } 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 { EditorState, Transaction, NodeSelection, TextSelection, Selection, } from "prosemirror-state";
import { EditorView, } from "prosemirror-view";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
@@ -26,6 +26,14 @@ export const nodes: { [index: string]: NodeSpec } = {
toDOM() { return pDOM; }
},
+ // starmine: {
+ // inline: true,
+ // attrs: { oldtext: { default: "" } },
+ // group: "inline",
+ // toDOM() { return ["star", "㊉"]; },
+ // parseDOM: [{ tag: "star" }]
+ // },
+
// :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
blockquote: {
content: "block+",
@@ -77,6 +85,30 @@ export const nodes: { [index: string]: NodeSpec } = {
group: "inline"
},
+ star: {
+ inline: true,
+ attrs: {
+ visibility: { default: false },
+ oldtext: { default: undefined },
+ oldtextslice: { default: undefined },
+ oldtextlen: { default: 0 }
+
+ },
+ group: "inline",
+ toDOM(node) {
+ const attrs = { style: `width: 40px` };
+ return ["span", { ...node.attrs, ...attrs }];
+ },
+ // parseDOM: [{
+ // tag: "star", getAttrs(dom: any) {
+ // return {
+ // visibility: dom.getAttribute("visibility"),
+ // oldtext: dom.getAttribute("oldtext"),
+ // oldtextlen: dom.getAttribute("oldtextlen"),
+ // }
+ // }
+ // }]
+ },
// :: NodeSpec An inline image (`<img>`) node. Supports `src`,
// `alt`, and `href` attributes. The latter two default to the empty
// string.
@@ -107,6 +139,32 @@ export const nodes: { [index: string]: NodeSpec } = {
}
},
+ video: {
+ inline: true,
+ attrs: {
+ src: {},
+ width: { default: "100px" },
+ alt: { default: null },
+ title: { default: null }
+ },
+ group: "inline",
+ draggable: true,
+ parseDOM: [{
+ tag: "video[src]", getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute("src"),
+ title: dom.getAttribute("title"),
+ alt: dom.getAttribute("alt"),
+ width: Math.min(100, Number(dom.getAttribute("width"))),
+ }
+ }
+ }],
+ toDOM(node) {
+ const attrs = { style: `width: ${node.attrs.width}` }
+ return ["video", { ...node.attrs, ...attrs }]
+ }
+ },
+
// :: NodeSpec A hard line break, represented in the DOM as `<br>`.
hard_break: {
inline: true,
@@ -222,6 +280,15 @@ export const marks: { [index: string]: MarkSpec } = {
toDOM: () => ['sup']
},
+ collapse: {
+ parseDOM: [{ style: 'color: blue' }],
+ toDOM() {
+ return ['span', {
+ style: 'color: blue'
+ }]
+ }
+ },
+
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
@@ -280,6 +347,7 @@ export const marks: { [index: string]: MarkSpec } = {
}]
},
+
/** FONT SIZES */
p10: {
@@ -407,6 +475,49 @@ export class ImageResizeView {
this._handle.style.display = "none";
}
}
+
+export class SummarizedView {
+ // TODO: highlight text that is summarized. to find end of region, walk along mark
+ _collapsed: HTMLElement;
+ constructor(node: any, view: any, getPos: any) {
+ this._collapsed = document.createElement("span");
+ this._collapsed.textContent = "㊉";
+ this._collapsed.style.opacity = "0.5";
+ // this._collapsed.style.background = "yellow";
+ this._collapsed.style.position = "relative";
+ this._collapsed.style.width = "40px";
+ this._collapsed.style.height = "20px";
+ this._collapsed.onpointerdown = function (e: any) {
+ console.log("star pressed!");
+ if (node.attrs.visibility) {
+ node.attrs.visibility = !node.attrs.visibility;
+ console.log("content is visible");
+ let y = getPos();
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen)));
+ view.dispatch(view.state.tr.deleteSelection(view.state, () => { }));
+ //this._collapsed.textContent = "㊀";
+ } else {
+ node.attrs.visibility = !node.attrs.visibility;
+ console.log("content is invisible");
+ let y = getPos();
+ let mark = view.state.schema.mark(view.state.schema.marks.underline);
+ console.log("PASTING " + node.attrs.oldtext.toString());
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1)));
+ view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(view.state.selection.from, view.state.selection.from + node.attrs.oldtextlen, mark));
+ //this._collapsed.textContent = "㊉";
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ };
+ (this as any).dom = this._collapsed;
+
+ }
+ selectNode() {
+ }
+
+ deselectNode() {
+ }
+}
// :: Schema
// This schema rougly corresponds to the document schema used by
// [CommonMark](http://commonmark.org/), minus the list elements,
@@ -415,4 +526,14 @@ export class ImageResizeView {
//
// 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 });
+
+const fromJson = schema.nodeFromJSON;
+
+schema.nodeFromJSON = (json: any) => {
+ let node = fromJson(json);
+ if (json.type === "star") {
+ node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice);
+ }
+ return node;
+} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 0e22d576c..09bccb1a0 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,4 +1,4 @@
-import { observable, action } from "mobx";
+import { observable, action, runInAction } from "mobx";
import { Doc } from "../../new_fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
@@ -6,19 +6,27 @@ import { NumCast } from "../../new_fields/Types";
export namespace SelectionManager {
class Manager {
- @observable
- SelectedDocuments: Array<DocumentView> = [];
+ @observable IsDragging: boolean = false;
+ @observable SelectedDocuments: Array<DocumentView> = [];
@action
- SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+ SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
// if doc is not in SelectedDocuments, add it
- if (!ctrlPressed) {
- this.DeselectAll();
- }
+ if (manager.SelectedDocuments.indexOf(docView) === -1) {
+ if (!ctrlPressed) {
+ this.DeselectAll();
+ }
- if (manager.SelectedDocuments.indexOf(doc) === -1) {
- manager.SelectedDocuments.push(doc);
- doc.props.whenActiveChanged(true);
+ manager.SelectedDocuments.push(docView);
+ docView.props.whenActiveChanged(true);
+ }
+ }
+ @action
+ DeselectDoc(docView: DocumentView): void {
+ let ind = manager.SelectedDocuments.indexOf(docView);
+ if (ind !== -1) {
+ manager.SelectedDocuments.splice(ind, 1);
+ docView.props.whenActiveChanged(false);
}
}
@action
@@ -31,8 +39,11 @@ export namespace SelectionManager {
const manager = new Manager();
- export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
- manager.SelectDoc(doc, ctrlPressed);
+ export function DeselectDoc(docView: DocumentView): void {
+ manager.DeselectDoc(docView);
+ }
+ export function SelectDoc(docView: DocumentView, ctrlPressed: boolean): void {
+ manager.SelectDoc(docView, ctrlPressed);
}
export function IsSelected(doc: DocumentView): boolean {
@@ -51,6 +62,9 @@ export namespace SelectionManager {
if (found) manager.SelectDoc(found, false);
}
+ export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
+ export function GetIsDragging() { return manager.IsDragging; }
+
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 437da0d63..4d4eb386d 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -36,7 +36,7 @@
position: relative;
padding-right: 15px;
margin: 3px;
- background: #333333;
+ background: white;
border-radius: 3px;
text-align: center;
}
@@ -69,6 +69,7 @@
.ProseMirror-menu-dropdown-menu {
z-index: 15;
min-width: 6em;
+ background: white;
}
.linking {
@@ -82,7 +83,7 @@
}
.ProseMirror-menu-dropdown-item:hover {
- background: #2e2b2b;
+ background: white;
}
.ProseMirror-menu-submenu-wrap {
@@ -132,7 +133,7 @@
position: relative;
min-height: 1em;
color: white;
- padding: 1px 6px;
+ padding: 10px 10px;
top: 0; left: 0; right: 0;
border-bottom: 1px solid silver;
background:$dark-color;
@@ -155,7 +156,7 @@
}
.ProseMirror-icon svg {
- fill: currentColor;
+ fill:white;
height: 1em;
}
@@ -184,7 +185,7 @@
position: fixed;
border-radius: 3px;
z-index: 11;
- box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
+ box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2);
}
.ProseMirror-prompt h5 {
@@ -196,7 +197,7 @@
.ProseMirror-prompt input[type="text"],
.ProseMirror-prompt textarea {
- background: #eee;
+ background: white;
border: none;
outline: none;
}
@@ -233,15 +234,18 @@
.tooltipMenu {
position: absolute;
- z-index: 200;
- background: $dark-color;
+ z-index: 50;
+ background: whitesmoke;
border: 1px solid silver;
- border-radius: 4px;
+ border-radius: 15px;
padding: 2px 10px;
- margin-bottom: 7px;
- -webkit-transform: translateX(-50%);
- transform: translateX(-50%);
+ //margin-bottom: 100px;
+ //-webkit-transform: translateX(-50%);
+ //transform: translateX(-50%);
+ transform: translateY(-50%);
pointer-events: all;
+ height: auto;
+ width:inherit;
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
border: none;
@@ -257,34 +261,34 @@
}
}
-.tooltipMenu:before {
- content: "";
- height: 0; width: 0;
- position: absolute;
- left: 50%;
- margin-left: -5px;
- bottom: -6px;
- border: 5px solid transparent;
- border-bottom-width: 0;
- border-top-color: silver;
- }
- .tooltipMenu:after {
- content: "";
- height: 0; width: 0;
- position: absolute;
- left: 50%;
- margin-left: -5px;
- bottom: -4.5px;
- border: 5px solid transparent;
- border-bottom-width: 0;
- border-top-color: $dark-color;
- }
+// .tooltipMenu:before {
+// content: "";
+// height: 0; width: 0;
+// position: absolute;
+// left: 50%;
+// margin-left: -5px;
+// bottom: -6px;
+// border: 5px solid transparent;
+// border-bottom-width: 0;
+// border-top-color: silver;
+// }
+// .tooltipMenu:after {
+// content: "";
+// height: 0; width: 0;
+// position: absolute;
+// left: 50%;
+// margin-left: -5px;
+// bottom: -4.5px;
+// border: 5px solid transparent;
+// border-bottom-width: 0;
+// border-top-color: $dark-color;
+// }
.menuicon {
display: inline-block;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
+ border-right: 1px solid white(0, 0, 0, 0.2);
//color: rgb(19, 18, 18);
- color: white;
+ color: rgb(226, 21, 21);
line-height: 1;
padding: 0px 2px;
margin: 1px;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index f517f757a..0a61b7e7d 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,6 +1,6 @@
import { action, IReactionDisposer, reaction } from "mobx";
import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
-import { baseKeymap, lift } from "prosemirror-commands";
+import { baseKeymap, lift, deleteSelection } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
@@ -14,20 +14,22 @@ import { library } from '@fortawesome/fontawesome-svg-core';
import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform';
import {
- faListUl,
+ faListUl, faGrinTongueSquint,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FieldViewProps } from "../views/nodes/FieldView";
import { throwStatement } from "babel-types";
+const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js");
import { View } from "@react-pdf/renderer";
import { DragManager } from "./DragManager";
import { Doc, Opt, Field } from "../../new_fields/Doc";
-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";
import { Id } from "../../new_fields/FieldSymbols";
+import { Utils } from "../../Utils";
+// import { wrap } from "module";
const SVG = "http://www.w3.org/2000/svg";
@@ -46,6 +48,8 @@ export class TooltipTextMenu {
private fontStylesToName: Map<MarkType, string>;
private listTypeToIcon: Map<NodeType, string>;
private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+ private link: HTMLAnchorElement;
+
private linkEditor?: HTMLDivElement;
private linkText?: HTMLDivElement;
private linkDrag?: HTMLImageElement;
@@ -68,12 +72,13 @@ export class TooltipTextMenu {
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: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") },
+ { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") },
+ { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") },
+ { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") },
+ { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") },
+ { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") },
+ { command: deleteSelection, dom: this.icon("C", 'collapse', 'Collapse') }
// { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
// { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
// { command: lift, dom: this.icon("<", "lift") },
@@ -86,7 +91,10 @@ export class TooltipTextMenu {
dom.addEventListener("pointerdown", e => {
e.preventDefault();
view.focus();
- command(view.state, view.dispatch, view);
+ if (dom.contains(e.target as Node)) {
+ e.stopPropagation();
+ command(view.state, view.dispatch, view);
+ }
});
});
@@ -120,6 +128,13 @@ export class TooltipTextMenu {
this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
this.listTypes = Array.from(this.listTypeToIcon.keys());
+ this.link = document.createElement("a");
+ this.link.target = "_blank";
+ this.link.style.color = "white";
+ this.tooltip.appendChild(this.link);
+
+ this.tooltip.appendChild(this.createLink().render(this.view).dom);
+
this.update(view, undefined);
}
@@ -131,13 +146,13 @@ export class TooltipTextMenu {
//font SIZES
let fontSizeBtns: MenuItem[] = [];
this.fontSizeToNum.forEach((number, mark) => {
- fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+ fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
});
if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), {
label: label,
- css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;"
+ css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;"
}) as MenuItem).render(this.view).dom;
this.tooltip.appendChild(this.fontSizeDom);
}
@@ -150,13 +165,13 @@ export class TooltipTextMenu {
//font STYLES
let fontBtns: MenuItem[] = [];
this.fontStylesToName.forEach((name, mark) => {
- fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+ fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
});
if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
this.fontStyleDom = (new Dropdown(cut(fontBtns), {
label: label,
- css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;"
+ css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;"
}) as MenuItem).render(this.view).dom;
this.tooltip.appendChild(this.fontStyleDom);
@@ -165,7 +180,7 @@ export class TooltipTextMenu {
updateLinkMenu() {
if (!this.linkEditor || !this.linkText) {
this.linkEditor = document.createElement("div");
- this.linkEditor.style.color = "white";
+ this.linkEditor.style.color = "black";
this.linkText = document.createElement("div");
this.linkText.style.cssFloat = "left";
this.linkText.style.marginRight = "5px";
@@ -174,13 +189,13 @@ export class TooltipTextMenu {
this.linkText.style.whiteSpace = "nowrap";
this.linkText.style.width = "150px";
this.linkText.style.overflow = "hidden";
- this.linkText.style.color = "white";
+ this.linkText.style.color = "black";
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.width = "10px";
+ linkBtn.style.height = "10px";
+ linkBtn.style.color = "black";
linkBtn.style.cssFloat = "left";
linkBtn.onpointerdown = (e: PointerEvent) => {
let node = this.view.state.selection.$from.nodeAfter;
@@ -207,7 +222,7 @@ export class TooltipTextMenu {
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.color = "black";
this.linkDrag.style.background = "black";
this.linkDrag.style.cssFloat = "left";
this.linkDrag.onpointerdown = (e: PointerEvent) => {
@@ -228,6 +243,22 @@ export class TooltipTextMenu {
this.linkEditor.appendChild(this.linkText);
this.linkEditor.appendChild(linkBtn);
this.tooltip.appendChild(this.linkEditor);
+
+ let starButton = document.createElement("span");
+ // starButton.style.width = '10px';
+ // starButton.style.height = '10px';
+ starButton.style.marginLeft = '10px';
+ starButton.textContent = "Summarize";
+ starButton.style.color = 'black';
+ starButton.style.height = '20px';
+ starButton.style.backgroundColor = 'white';
+ starButton.style.textAlign = 'center';
+ starButton.onclick = () => {
+ let state = this.view.state;
+ this.insertStar(state, this.view.dispatch);
+ }
+
+ this.tooltip.appendChild(starButton);
}
let node = this.view.state.selection.$from.nodeAfter;
@@ -253,6 +284,16 @@ export class TooltipTextMenu {
link = node && node.marks.find(m => m.type.name === "link");
}
+ insertStar(state: EditorState<any>, dispatch: any) {
+ console.log("creating star...");
+ let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from });
+ if (dispatch) {
+ console.log(newNode.attrs.oldtext.toString());
+ dispatch(state.tr.replaceSelectionWith(newNode));
+ }
+ return true;
+ }
+
//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
@@ -261,14 +302,14 @@ export class TooltipTextMenu {
//Make a dropdown of all list types
let toAdd: MenuItem[] = [];
this.listTypeToIcon.forEach((icon, type) => {
- toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+ toAdd.push(this.dropdownNodeBtn(icon, "color: black; width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
});
//option to remove the list formatting
- toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+ toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
listTypeBtn = (new Dropdown(toAdd, {
label: label,
- css: "color:white; width: 40px;"
+ css: "color:black; width: 40px;"
}) as MenuItem).render(this.view).dom;
//add this new button and return it
@@ -332,6 +373,43 @@ export class TooltipTextMenu {
});
}
+ createLink() {
+ let markType = schema.marks.link;
+ return new MenuItem({
+ title: "Add or remove link",
+ label: "Add or remove link",
+ execEvent: "",
+ icon: icons.link,
+ css: "color:white;",
+ class: "menuicon",
+ enable(state) { return !state.selection.empty; },
+ run: (state, dispatch, view) => {
+ // to remove link
+ if (this.markActive(state, markType)) {
+ toggleMark(markType)(state, dispatch);
+ return true;
+ }
+ // to create link
+ openPrompt({
+ title: "Create a link",
+ fields: {
+ href: new TextField({
+ label: "Link target",
+ required: true
+ }),
+ title: new TextField({ label: "Title" })
+ },
+ callback(attrs: any) {
+ toggleMark(markType, attrs)(view.state, view.dispatch);
+ view.focus();
+ },
+ flyout_top: 0,
+ flyout_left: 0
+ });
+ }
+ });
+ }
+
//makes a button for the drop down FOR NODE TYPES
//css is the style you want applied to the button
dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
@@ -348,13 +426,19 @@ export class TooltipTextMenu {
});
}
+ markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) {
+ let { from, $from, to, empty } = state.selection;
+ if (empty) return type.isInSet(state.storedMarks || $from.marks());
+ else return state.doc.rangeHasMark(from, to, type);
+ };
+
// Helper function to create menu icons
- icon(text: string, name: string) {
+ icon(text: string, name: string, title: string = name) {
let span = document.createElement("span");
span.className = name + " menuicon";
- span.title = name;
+ span.title = title;
span.textContent = text;
- span.style.color = "white";
+ span.style.color = "black";
return span;
}
@@ -395,6 +479,20 @@ export class TooltipTextMenu {
};
}
+ getMarksInSelection(state: EditorState<any>, targets: MarkType<any>[]) {
+ let found: Mark<any>[] = [];
+ let { from, to } = state.selection as TextSelection;
+ state.doc.nodesBetween(from, to, (node) => {
+ let marks = node.marks;
+ if (marks) {
+ marks.forEach(m => {
+ if (targets.includes(m.type)) found.push(m);
+ });
+ }
+ });
+ return found;
+ }
+
//updates the tooltip menu when the selection changes
update(view: EditorView, lastState: EditorState | undefined) {
let state = view.state;
@@ -408,6 +506,14 @@ export class TooltipTextMenu {
return;
}
+ let linksInSelection = this.activeMarksOnSelection([schema.marks.link]);
+ if (linksInSelection.length > 0) {
+ let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs;
+ this.link.href = attributes.href;
+ this.link.textContent = attributes.title;
+ this.link.style.visibility = "visible";
+ } else this.link.style.visibility = "hidden";
+
// Otherwise, reposition it and update its content
this.tooltip.style.display = "";
let { from, to } = state.selection;
@@ -421,8 +527,14 @@ export class TooltipTextMenu {
let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
let mid = Math.min(start.left, end.left) + width;
- this.tooltip.style.width = 225 + "px";
+ //this.tooltip.style.width = 225 + "px";
this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ this.tooltip.style.top = "-100px";
+ //this.tooltip.style.height = "100px";
+
+ // let transform = this.editorProps.ScreenToLocalTransform();
+ // this.tooltip.style.width = `${225 / transform.Scale}px`;
+ // Utils
//UPDATE LIST ITEM DROPDOWN
this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7260b00cf..ceca940b6 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { LinkMenu } from "./nodes/LinkMenu";
import { TemplateMenu } from "./TemplateMenu";
import { Template, Templates } from "./Templates";
import React = require("react");
+import { URLField } from '../../new_fields/URLField';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -41,6 +42,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
private _linkerButton = React.createRef<HTMLDivElement>();
+ private _embedButton = React.createRef<HTMLDivElement>();
private _downX = 0;
private _downY = 0;
private _iconDoc?: Doc = undefined;
@@ -329,12 +331,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointerup", this.onLinkerButtonUp);
document.addEventListener("pointerup", this.onLinkerButtonUp);
}
+
+ onEmbedButtonDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ document.removeEventListener("pointermove", this.onEmbedButtonMoved);
+ document.addEventListener("pointermove", this.onEmbedButtonMoved);
+ document.removeEventListener("pointerup", this.onEmbedButtonUp);
+ document.addEventListener("pointerup", this.onEmbedButtonUp);
+ }
+
onLinkerButtonUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onLinkerButtonMoved);
document.removeEventListener("pointerup", this.onLinkerButtonUp);
e.stopPropagation();
}
+ onEmbedButtonUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onEmbedButtonMoved);
+ document.removeEventListener("pointerup", this.onEmbedButtonUp);
+ e.stopPropagation();
+ }
+
@action
onLinkerButtonMoved = (e: PointerEvent): void => {
if (this._linkerButton.current !== null) {
@@ -354,6 +371,25 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
e.stopPropagation();
}
+ @action
+ onEmbedButtonMoved = (e: PointerEvent): void => {
+ if (this._embedButton.current !== null) {
+ document.removeEventListener("pointermove", this.onEmbedButtonMoved);
+ document.removeEventListener("pointerup", this.onEmbedButtonUp);
+
+ let dragDocView = SelectionManager.SelectedDocuments()[0];
+ let dragData = new DragManager.EmbedDragData(dragDocView.props.Document);
+
+ DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, {
+ handlers: {
+ dragComplete: action(emptyFunction),
+ },
+ hideSource: false
+ });
+ }
+ e.stopPropagation();
+ }
+
onLinkButtonDown = (e: React.PointerEvent): void => {
e.stopPropagation();
document.removeEventListener("pointermove", this.onLinkButtonMoved);
@@ -442,11 +478,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let actualdH = Math.max(height + (dH * scale), 20);
doc.x = (doc.x || 0) + dX * (actualdW - width);
doc.y = (doc.y || 0) + dY * (actualdH - height);
- let proto = Doc.GetProto(doc);
+ let proto = Doc.GetProto(element.props.Document);
let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect, false) && nwidth && nheight);
if (fixedAspect && (!nwidth || !nheight)) {
- proto.nativeWidth = doc.width;
- proto.nativeHeight = doc.height;
+ proto.nativeWidth = nwidth = doc.width || 0;
+ proto.nativeHeight = nheight = doc.height || 0;
proto.ignoreAspect = true;
}
if (nwidth > 0 && nheight > 0) {
@@ -469,6 +505,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
} else {
doc.width = zoomBasis * actualdW;
doc.height = zoomBasis * actualdH;
+ proto.autoHeight = undefined;
}
}
});
@@ -510,6 +547,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
// e.stopPropagation();
// }
+ considerEmbed = () => {
+ let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+ let canEmbed = thisDoc.data && thisDoc.data instanceof URLField;
+ if (!canEmbed) return (null);
+ return (
+ <div className="linkButtonWrapper">
+ <div style={{ paddingTop: 3, marginLeft: 30 }} title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}>
+ <FontAwesomeIcon className="fa-image" icon="image" size="sm" />
+ </div>
+ </div>
+ );
+ }
+
render() {
var bounds = this.Bounds;
let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -604,6 +654,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
</div>
</div>
<TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+ {this.considerEmbed()}
</div>
</div >
</div>
diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss
index ba4ec41af..465e14d07 100644
--- a/src/client/views/InkingControl.scss
+++ b/src/client/views/InkingControl.scss
@@ -1,8 +1,6 @@
@import "globalCssVariables";
.inking-control {
- position: absolute;
- left: 70px;
- bottom: 70px;
+ bottom: 20px;
margin: 0;
padding: 0;
display: flex;
@@ -63,10 +61,9 @@
margin-top: 4px;
}
.ink-panel {
- margin: 6px 12px 6px 0;
- height: 30px;
+ height: 24px;
vertical-align: middle;
- line-height: 36px;
+ line-height: 28px;
padding: 0 10px;
color: $intermediate-color;
&:first {
@@ -114,7 +111,6 @@
border-radius: 11px;
width: 22px;
height: 22px;
- margin-top: 6px;
cursor: pointer;
text-align: center; // span {
// color: $light-color;
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index d1a6eb7fd..b98132c23 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -4,7 +4,6 @@ import React = require("react");
import { observer } from "mobx-react";
import "./InkingControl.scss";
import { library } from '@fortawesome/fontawesome-svg-core';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
import { SelectionManager } from "../util/SelectionManager";
import { InkTool } from "../../new_fields/InkField";
@@ -19,7 +18,6 @@ export class InkingControl extends React.Component {
@observable private _selectedColor: string = "rgb(244, 67, 54)";
@observable private _selectedWidth: string = "25";
@observable private _open: boolean = false;
- @observable private _colorPickerDisplay = false;
constructor(props: Readonly<{}>) {
super(props);
@@ -57,45 +55,14 @@ export class InkingControl extends React.Component {
return this._selectedWidth;
}
- selected = (tool: InkTool) => {
- if (this._selectedTool === tool) {
- return { color: "#61aaa3" };
- }
- return {};
- }
-
@action
toggleDisplay = () => {
this._open = !this._open;
+ this.switchTool(this._open ? InkTool.Pen : InkTool.None);
}
-
-
- @action
- toggleColorPicker = () => {
- this._colorPickerDisplay = !this._colorPickerDisplay;
- }
-
render() {
return (
<ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}>
- <li className="ink-tools ink-panel">
- <div className="ink-tool-buttons">
- <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button>
- <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button>
- <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button>
- <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button>
- </div>
- </li>
- <li className="ink-color ink-panel">
- <label>COLOR: </label>
- <div className="ink-color-display" style={{ backgroundColor: this._selectedColor }}
- onClick={() => this.toggleColorPicker()}>
- {/* {this._colorPickerDisplay ? <span>&#9660;</span> : <span>&#9650;</span>} */}
- </div>
- <div className="ink-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}>
- <CirclePicker onChange={this.switchColor} circleSize={22} width={"220"} />
- </div>
- </li>
<li className="ink-size ink-panel">
<label htmlFor="stroke-width">SIZE: </label>
<input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width"
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 57a53c999..690139341 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -126,6 +126,26 @@ button:hover {
margin-bottom: 10px;
}
}
+.toolbar-color-picker {
+ background-color: $light-color;
+ border-radius: 5px;
+ padding: 12px;
+ position: absolute;
+ bottom: 36px;
+ left: -3px;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+}
+.toolbar-color-button {
+ border-radius: 11px;
+ width: 22px;
+ height: 22px;
+ cursor: pointer;
+ text-align: center; // span {
+ // color: $light-color;
+ // font-size: 8px;
+ // user-select: none;
+ // }
+}
// add nodes menu. Note that the + button is actually an input label, not an actual button.
#add-nodes-menu {
@@ -133,7 +153,7 @@ button:hover {
bottom: 22px;
left: 24px;
- label {
+ > label {
background: $dark-color;
color: $light-color;
display: inline-block;
@@ -155,15 +175,15 @@ button:hover {
transform: scale(1.15);
}
- input {
+ > input {
display: none;
}
- input:not(:checked)~#add-options-content {
+ > input:not(:checked)~#add-options-content {
display: none;
}
- input:checked~label {
+ > input:checked~label {
transform: rotate(45deg);
transition: transform 0.5s;
cursor: pointer;
@@ -207,7 +227,7 @@ ul#add-options-list {
list-style: none;
padding: 5 0 0 0;
- li {
+ > li {
display: inline-block;
padding: 0;
}
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 6bbd70a25..23e90ece5 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -1,14 +1,15 @@
import { action, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
+import "normalize.css";
import * as React from 'react';
-import { emptyFunction, returnTrue, returnZero } from '../../Utils';
+import { Doc } from '../../new_fields/Doc';
+import { BoolCast } from '../../new_fields/Types';
+import { emptyFunction, returnTrue, returnZero, Utils } from '../../Utils';
import { DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
-import "normalize.css";
+import { CollectionDockingView } from './collections/CollectionDockingView';
import "./MainOverlayTextBox.scss";
import { FormattedTextBox } from './nodes/FormattedTextBox';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { Doc } from '../../new_fields/Doc';
interface MainOverlayTextBoxProps {
}
@@ -17,11 +18,14 @@ interface MainOverlayTextBoxProps {
export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> {
public static Instance: MainOverlayTextBox;
@observable _textXf: () => Transform = () => Transform.Identity();
- private _textFieldKey: string = "data";
+ public TextFieldKey: string = "data";
private _textColor: string | null = null;
private _textHideOnLeave?: boolean;
private _textTargetDiv: HTMLDivElement | undefined;
private _textProxyDiv: React.RefObject<HTMLDivElement>;
+ private _textBottom: boolean | undefined;
+ private _textAutoHeight: boolean | undefined;
+ @observable public TextDoc?: Doc;
constructor(props: MainOverlayTextBoxProps) {
super(props);
@@ -29,21 +33,32 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
MainOverlayTextBox.Instance = this;
reaction(() => FormattedTextBox.InputBoxOverlay,
(box?: FormattedTextBox) => {
- if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, box.props.ScreenToLocalTransform);
- else this.setTextDoc();
+ if (box) {
+ this.TextDoc = box.props.Document;
+ let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined);
+ let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); };
+ this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content")
+ }
+ else {
+ this.TextDoc = undefined;
+ this.setTextDoc();
+ }
});
}
@action
- private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) {
+ private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform, autoHeight?: boolean) {
if (this._textTargetDiv) {
this._textTargetDiv.style.color = this._textColor;
}
- this._textFieldKey = textFieldKey!;
- this._textXf = tx ? tx : () => Transform.Identity();
+ this._textAutoHeight = autoHeight;
+ this.TextFieldKey = textFieldKey!;
+ let txf = tx ? tx : () => Transform.Identity();
+ this._textXf = txf;
this._textTargetDiv = div;
this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave;
if (div) {
+ this._textBottom = div.parentElement && div.parentElement.style.bottom ? true : false;
this._textColor = (getComputedStyle(div) as any).color;
div.style.color = "transparent";
}
@@ -90,15 +105,21 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
}
render() {
+ this.TextDoc;
if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) {
let textRect = this._textTargetDiv.getBoundingClientRect();
let s = this._textXf().Scale;
- return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
+ let location = this._textBottom ? textRect.bottom : textRect.top;
+ let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight;
+ return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${location}px) scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} >
<div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
- style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
- <FormattedTextBox color={`${this._textColor}`} fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
- selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
- ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
+ style={{ width: `${textRect.width * s}px`, height: "0px" }}>
+ <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}>
+ <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document}
+ isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true}
+ ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} />
+ </div>
</div>
</ div>;
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 42d5929bf..879c2aca0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -3,6 +3,7 @@ import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
+import { CirclePicker } from 'react-color';
import "normalize.css";
import * as React from 'react';
import Measure from 'react-measure';
@@ -32,6 +33,7 @@ import { listSpec } from '../../new_fields/Schema';
import { Id } from '../../new_fields/FieldSymbols';
import { HistoryUtil } from '../util/History';
import { CollectionBaseView } from './collections/CollectionBaseView';
+import { InkTool } from '../../new_fields/InkField';
@observer
@@ -217,37 +219,28 @@ export class MainView extends React.Component {
</Measure>;
}
+ selected = (tool: InkTool) => {
+ if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" };
+ if (InkingControl.Instance.selectedTool === tool) {
+ return { color: "#61aaa3", fontSize: "50%" };
+ }
+ return { fontSize: "50%" };
+ }
+
+ @observable private _colorPickerDisplay = false;
/* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
nodesMenu() {
let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
- let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf";
- let weburl = "https://cs.brown.edu/courses/cs166/";
- let audiourl = "http://techslides.com/demos/samples/sample.mp3";
- let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
- let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
- let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
let addTreeNode = action(() => CurrentUserUtils.UserDocument);
- //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
- // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" }));
- let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
- let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
- let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
- let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
- [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
[React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
- [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
- [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode],
- [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
- [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
[React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
- [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
];
return < div id="add-nodes-menu" >
@@ -256,17 +249,38 @@ export class MainView extends React.Component {
<div id="add-options-content">
<ul id="add-options-list">
+ <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
+ <li key="undo"><button className="add-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
+ <li key="redo"><button className="add-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
+ <li key="color"><button className="add-button round-button" title="Redo" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} >
+
+ <div className="toolbar-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}>
+ <CirclePicker onChange={InkingControl.Instance.switchColor} circleSize={22} width={"220"} />
+ </div>
+ </div></button></li>
{btns.map(btn =>
<li key={btn[1]} ><div ref={btn[0]}>
<button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
<FontAwesomeIcon icon={btn[1]} size="sm" />
</button>
</div></li>)}
+ <li key="ink" style={{ paddingRight: "6px" }}><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button></li>
+ <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button></li>
+ <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Pen" /></button></li>
+ <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Pen" /></button></li>
+ <li key="inkControls"><InkingControl /></li>
</ul>
</div>
</div >;
}
+
+
+ @action
+ toggleColorPicker = () => {
+ this._colorPickerDisplay = !this._colorPickerDisplay;
+ }
+
/* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
@computed
get miscButtons() {
@@ -276,7 +290,6 @@ export class MainView extends React.Component {
let logoutRef = React.createRef<HTMLDivElement>();
return [
- <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
<div id="toolbar" key="toolbar">
<div ref={notifsRef}>
<button className="toolbar-button round-button" title="Notifs"
@@ -287,10 +300,6 @@ export class MainView extends React.Component {
{length}
</div>
</div>
- <button className="toolbar-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button>
- <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
- <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
- <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
</div >,
this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div> : null,
<div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
@@ -314,7 +323,6 @@ export class MainView extends React.Component {
<ContextMenu />
{this.nodesMenu()}
{this.miscButtons}
- <InkingControl />
<MainOverlayTextBox />
</div>
);
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index b0c93ee26..d2d41a4ba 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -18,7 +18,6 @@ interface PresListProps extends PresViewProps {
gotoDocument(index: number): void;
}
-
@observer
/**
* Component that takes in a document prop and a boolean whether it's collapsed or not.
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 8943bbcaf..3cd525afa 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -41,21 +41,13 @@ export namespace Templates {
export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
`<div>
- <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+ <div style="height:100%; width:100%;">{layout}</div>
<div style="bottom: 0; font-size:14px; width:100%; position:absolute">
- <FormattedTextBox {...props} fieldKey={"caption"} hideOnLeave={"true"} />
+ <FormattedTextBox {...props} height="min-content" fieldKey={"caption"} hideOnLeave={"true"} />
</div>
</div>` );
- export const ImageTitle = new Template("Image Title", TemplatePosition.InnerTop,
- `<div style="height:100%">
- <div style="height:100%; width:100%;position:absolute;">{layout}</div>
- <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; ">
- <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
- </div>
- </div>` );
-
- export const TextTitle = new Template("Text Title", TemplatePosition.InnerTop,
+ export const Title = new Template("Title", TemplatePosition.InnerTop,
`<div style="height:100%">
<div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; ">
<span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span>
@@ -92,7 +84,7 @@ export namespace Templates {
</div > `);
}
- export const TemplateList: Template[] = [TextTitle, Header, ImageTitle, Caption, Bullet];
+ export const TemplateList: Template[] = [Title, Header, Caption, Bullet];
export function sortTemplates(a: Template, b: Template) {
if (a.Position < b.Position) { return -1; }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index d120c3a0c..5238ad114 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -10,6 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager';
import { ContextMenu } from '../ContextMenu';
import { FieldViewProps } from '../nodes/FieldView';
import './CollectionBaseView.scss';
+import { DocumentManager } from '../../util/DocumentManager';
export enum CollectionViewType {
Invalid,
@@ -129,7 +130,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
removeDocument(doc: Doc): boolean {
- SelectionManager.DeselectAll();
+ let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView)
+ docView && SelectionManager.DeselectDoc(docView);
const props = this.props;
//TODO This won't create the field if it doesn't already exist
const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
@@ -163,7 +165,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
return true;
}
if (this.removeDocument(doc)) {
- SelectionManager.DeselectAll();
return addDocument(doc);
}
return false;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index fe9e12640..d6b1aa29d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -166,6 +166,25 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
e.stopPropagation();
e.preventDefault();
+ if (html && FormattedTextBox.IsFragment(html)) {
+ let href = FormattedTextBox.GetHref(html);
+ if (href) {
+ let docid = FormattedTextBox.GetDocFromUrl(href);
+ if (docid) { // prosemirror text containing link to dash document
+ DocServer.GetRefField(docid).then(f => {
+ if (f instanceof Doc) {
+ if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+ (f instanceof Doc) && this.props.addDocument(f, false);
+ }
+ });
+ } else {
+ this.props.addDocument && this.props.addDocument(Docs.WebDocument(href, options));
+ }
+ } else if (text) {
+ this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }), false);
+ }
+ return;
+ }
if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
this.props.addDocument(htmlDoc, false);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8dc10bcd1..8a6764c58 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -20,6 +20,7 @@ import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
import { Transform } from '../../util/Transform';
+import { SelectionManager } from '../../util/SelectionManager';
export interface TreeViewProps {
@@ -32,6 +33,7 @@ export interface TreeViewProps {
ScreenToLocalTransform: () => Transform;
treeViewId: string;
parentKey: string;
+ active: () => boolean;
}
export enum BulletType {
@@ -69,7 +71,7 @@ class TreeView extends React.Component<TreeViewProps> {
@action onMouseLeave = () => { this._isOver = false; }
onPointerEnter = (e: React.PointerEvent): void => {
- this.props.document.libraryBrush = true;
+ this.props.active() && (this.props.document.libraryBrush = true);
if (e.buttons === 1) {
this._header!.current!.className = "treeViewItem-header";
document.addEventListener("pointermove", this.onDragMove, true);
@@ -149,7 +151,10 @@ class TreeView extends React.Component<TreeViewProps> {
</div>);
return (
<div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
- style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
+ style={{
+ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
+ pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
+ }}
>
{editableView(StrCast(this.props.document.title))}
{openRight}
@@ -225,18 +230,19 @@ class TreeView extends React.Component<TreeViewProps> {
while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
keys.map(key => {
- let docList = DocListCast(this.props.document[key]);
+ let docList = Cast(this.props.document[key], listSpec(Doc));
let remDoc = (doc: Doc) => this.remove(doc, key);
let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before);
let doc = Cast(this.props.document[key], Doc);
- if (doc instanceof Doc || docList.length) {
+ if (doc instanceof Doc || docList) {
if (!this._collapsed) {
bulletType = BulletType.Collapsible;
contentElement.push(<ul key={key + "more"}>
{(key === "data") ? (null) :
<span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>}
<div style={{ display: "block" }}>
- {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, this.props.treeViewId, key, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform)}
+ {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, key, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active)}
</div>
</ul >);
} else {
@@ -266,11 +272,12 @@ class TreeView extends React.Component<TreeViewProps> {
move: DragManager.MoveFunction,
dropAction: dropActionType,
addDocTab: (doc: Doc, where: string) => void,
- screenToLocalXf: () => Transform
+ screenToLocalXf: () => Transform,
+ active: () => boolean
) {
return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child =>
<TreeView document={child} treeViewId={treeViewId} key={child[Id]} deleteDoc={remove} addDocument={add} moveDocument={move}
- dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} />);
+ dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} active={active} />);
}
}
@@ -309,7 +316,8 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
- let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform);
+ let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active);
return (
<div id="body" className="collectionTreeView-dropTarget"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index ca7c99f28..ba7e6cf9e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -44,8 +44,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2);
let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2);
let text = "";
- this.props.LinkDocs.map(l => text += StrCast(l.title) + "(" + StrCast(l.linkDescription) + "), ");
- text = text.substr(0, text.length - 2);
+ let first = this.props.LinkDocs[0];
+ if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : "");
+ else text = "-multiple-";
return (
<>
<line key="linkLine" className="collectionfreeformlinkview-linkLine"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index e10ba9d7e..5ac2e1f9c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -57,6 +57,7 @@
}
>.jsx-parser {
+ position:absolute;
z-index:0;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f2960541e..0fa6ebc1e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -6,7 +6,6 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { BoolCast, Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
import { emptyFunction, returnOne } from "../../../../Utils";
-import { DocServer } from "../../../DocServer";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
@@ -220,6 +219,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
+
this.props.Document.panTransformType = "None";
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index c699b3437..5f2a732b9 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -45,12 +45,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
_commandExecuted = false;
@action
- cleanupInteractions = (all: boolean = false) => {
+ cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => {
if (all) {
document.removeEventListener("pointerup", this.onPointerUp, true);
document.removeEventListener("pointermove", this.onPointerMove, true);
}
- document.removeEventListener("keydown", this.marqueeCommand, true);
+ if (rem_keydown) {
+ document.removeEventListener("keydown", this.marqueeCommand, true);
+ }
this._visible = false;
}
@@ -93,7 +95,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
} else if (!e.ctrlKey) {
- let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
+ let newBox = Docs.TextDocument({ width: 200, height: 30, x: x, y: y, title: "-typed text-" });
+ newBox.proto!.autoHeight = true;
this.props.addLiveTextDocument(newBox);
}
e.stopPropagation();
@@ -145,6 +148,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this._downY = this._lastY = e.pageY;
this._commandExecuted = false;
PreviewCursor.Visible = false;
+ this.cleanupInteractions(true);
if (e.button === 2 || (e.button === 0 && e.altKey)) {
if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
document.addEventListener("pointermove", this.onPointerMove, true);
@@ -180,14 +184,21 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@action
onPointerUp = (e: PointerEvent): void => {
+ console.log("pointer up!");
if (this._visible) {
+ console.log("visible");
let mselect = this.marqueeSelect();
if (!e.shiftKey) {
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
}
this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
+ mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true);
}
- this.cleanupInteractions(true);
+ else {
+ console.log("invisible");
+ this.cleanupInteractions(true);
+ }
+
if (e.altKey) {
e.preventDefault();
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2ac6d12bf..6fe01963a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -125,10 +125,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
constructor(props: DocumentViewProps) {
super(props);
- this.selectOnLoad = props.selectOnLoad;
}
-
_reactionDisposer?: IReactionDisposer;
@action
componentDidMount() {
@@ -271,7 +269,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (!linkedFwdDocs.some(l => l instanceof Promise)) {
let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab");
- DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], linkedFwdContextDocs[altKey ? 1 : 0]);
+ let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
+ DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext);
}
}
}
@@ -317,13 +316,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
makeBtnClicked = (): void => {
let doc = Doc.GetProto(this.props.Document);
doc.isButton = !BoolCast(doc.isButton, false);
- if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally
- if (doc.isButton && !doc.nativeWidth) {
+ if (doc.isButton) {
+ if (!doc.nativeWidth) {
doc.nativeWidth = this.props.Document[WidthSym]();
doc.nativeHeight = this.props.Document[HeightSym]();
- } else {
- doc.nativeWidth = doc.nativeHeight = undefined;
}
+ } else {
+ doc.nativeWidth = doc.nativeHeight = undefined;
}
}
fullScreenClicked = (): void => {
@@ -349,9 +348,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
dst.nativeHeight = src.nativeHeight;
}
else {
- const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
- const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
- DocUtils.MakeLink(sourceDoc, destDoc, views.length ? views[0].props.Document : undefined);
+ // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true);
+ // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView);
+ DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined);
de.data.droppedDocuments.push(destDoc);
}
}
@@ -442,14 +441,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
isSelected = () => SelectionManager.IsSelected(this);
- @action select = (ctrlPressed: boolean) => { this.selectOnLoad = false; SelectionManager.SelectDoc(this, ctrlPressed); }
+ @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); }
- @observable selectOnLoad: boolean = false;
@computed get nativeWidth() { return this.Document.nativeWidth || 0; }
@computed get nativeHeight() { return this.Document.nativeHeight || 0; }
@computed get contents() {
return (
- <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.selectOnLoad} layoutKey={"layout"} />);
+ <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.props.selectOnLoad} layoutKey={"layout"} />);
}
render() {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 559a9fbfc..e5a43c60a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -5,49 +5,37 @@ import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
+import { NodeType } from 'prosemirror-model';
import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc, Opt } from "../../../new_fields/Doc";
+import { Id } from '../../../new_fields/FieldSymbols';
import { RichTextField } from "../../../new_fields/RichTextField";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { DocServer } from "../../DocServer";
-import { DocUtils, Docs } from '../../documents/Documents';
-import { DocumentManager } from "../../util/DocumentManager";
+import { Docs } from '../../documents/Documents';
import { DragManager } from "../../util/DragManager";
-import buildKeymap from "../../util/ProsemirrorKeymap";
+import buildKeymap from "../../util/ProsemirrorExampleTransfer";
import { inpRules } from "../../util/RichTextRules";
-import { ImageResizeView, schema } from "../../util/RichTextSchema";
+import { ImageResizeView, schema, SummarizedView } from "../../util/RichTextSchema";
import { SelectionManager } from "../../util/SelectionManager";
import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { ContextMenu } from "../../views/ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
import { DocComponent } from "../DocComponent";
import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
-import { Id } from '../../../new_fields/FieldSymbols';
library.add(faEdit);
library.add(faSmile);
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
-// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name}
-//
-// In Code, the node's HTML is specified in the document's parameterized structure as:
-// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
-// and the node's binding to the specified document KEYNAME as:
-// document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.<KEYNAME>]));
-// The Jsx parser at run time will bind:
-// 'fieldKey' property to the Key stored in LayoutKeys
-// and 'doc' property to the document that is being rendered
-//
-// When rendered() by React, this extracts the TextController from the Document stored at the
-// specified Key and assigns it to an HTML input node. When changes are made to this node,
-// this will edit the document and assign the new value to that field.
-//]
export interface FormattedTextBoxProps {
isOverlay?: boolean;
@@ -69,16 +57,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
private _ref: React.RefObject<HTMLDivElement>;
- private _proseRef: React.RefObject<HTMLDivElement>;
+ private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
private _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
- private _lastState: any = undefined;
private _applyingChange: boolean = false;
- private _dropDisposer?: DragManager.DragDropDisposer;
private _linkClicked = "";
private _reactionDisposer: Opt<IReactionDisposer>;
- private _inputReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
+ private dropDisposer?: DragManager.DragDropDisposer;
public get CurrentDiv(): HTMLDivElement { return this._ref.current!; }
@observable _entered = false;
@@ -110,16 +96,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
super(props);
this._ref = React.createRef();
- this._proseRef = React.createRef();
if (this.props.isOverlay) {
DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined);
}
}
-
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
- const state = this._lastState = this._editorView.state.apply(tx);
+ const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this._applyingChange = true;
Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
@@ -135,25 +119,30 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ this._proseRef = ele;
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
- if (de.data instanceof DragManager.LinkDragData) {
- let sourceDoc = de.data.linkSourceDocument;
- let destDoc = this.props.Document;
-
- DocUtils.MakeLink(sourceDoc, destDoc);
- de.data.droppedDocuments.push(destDoc);
+ // We're dealing with a link to a document
+ if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) {
+ // We're dealing with an internal document drop
+ let url = de.data.urlField.url.href;
+ let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image;
+ this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url })));
e.stopPropagation();
}
}
componentDidMount() {
- if (this._ref.current) {
- this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, {
- handlers: { drop: this.drop.bind(this) }
- });
- }
const config = {
schema,
inpRules, //these currently don't do anything, but could eventually be helpful
@@ -175,16 +164,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
]
};
- if (this.props.isOverlay) {
- this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay,
- () => {
- if (this._editorView) {
- this._editorView.destroy();
- }
- this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
- }
- );
- } else {
+ if (!this.props.isOverlay) {
this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
() => {
if (this.props.isSelected()) {
@@ -194,42 +174,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
});
}
-
this._reactionDisposer = reaction(
() => {
const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
- return field ? field.Data : undefined;
+ return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`;
},
- field => field && this._editorView && !this._applyingChange &&
+ field => this._editorView && !this._applyingChange &&
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
);
- this.setupEditor(config, this.props.Document);
+ this.setupEditor(config, this.props.Document, this.props.fieldKey);
}
- private setupEditor(config: any, doc?: Doc) {
- let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined;
- let startup = StrCast(this.props.Document.documentText);
+ private setupEditor(config: any, doc: Doc, fieldKey: string) {
+ let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined;
+ let startup = StrCast(doc.documentText);
startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : "";
if (!startup && !field && doc) {
- startup = StrCast(doc[this.props.fieldKey]);
+ startup = StrCast(doc[fieldKey]);
}
- if (this._proseRef.current) {
- this._editorView = new EditorView(this._proseRef.current, {
+ if (this._proseRef) {
+ this._editorView = new EditorView(this._proseRef, {
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }
+ image(node, view, getPos) { return new ImageResizeView(node, view, getPos); },
+ star(node, view, getPos) { return new SummarizedView(node, view, getPos); }
}
});
if (startup) {
- this.props.Document.proto!.documentText = undefined;
+ Doc.GetProto(doc).documentText = undefined;
this._editorView.dispatch(this._editorView.state.tr.insertText(startup));
}
}
if (this.props.selectOnLoad) {
- this.props.select(false);
- this._editorView!.focus();
+ if (!this.props.isOverlay) this.props.select(false);
+ else this._editorView!.focus();
}
}
@@ -240,15 +220,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this._reactionDisposer) {
this._reactionDisposer();
}
- if (this._inputReactionDisposer) {
- this._inputReactionDisposer();
- }
if (this._proxyReactionDisposer) {
this._proxyReactionDisposer();
}
- if (this._dropDisposer) {
- this._dropDisposer();
- }
}
onPointerDown = (e: React.PointerEvent): void => {
@@ -258,7 +232,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
this._toolTipTextMenu.tooltip.style.opacity = "0";
}
}
- this._linkClicked = "";
+ let ctrlKey = e.ctrlKey;
if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) {
let href = (e.target as any).href;
for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) {
@@ -275,6 +249,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
e.stopPropagation();
e.preventDefault();
}
+
}
if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
e.preventDefault();
@@ -284,14 +259,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
this._toolTipTextMenu.tooltip.style.opacity = "1";
}
- let ctrlKey = e.ctrlKey;
- if (this._linkClicked) {
- DocServer.GetRefField(this._linkClicked).then(f => {
- (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab"));
- });
- e.stopPropagation();
- e.preventDefault();
- }
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
@@ -314,7 +281,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
onClick = (e: React.MouseEvent): void => {
- this._proseRef.current!.focus();
+ this._proseRef!.focus();
if (this._linkClicked) {
e.preventDefault();
e.stopPropagation();
@@ -369,6 +336,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (!this._undoTyping) {
this._undoTyping = UndoManager.StartBatch("undoTyping");
}
+ if (this.props.isOverlay && this.props.Document.autoHeight) {
+ let xf = this._ref.current!.getBoundingClientRect();
+ let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height);
+ let nh = NumCast(this.props.Document.nativeHeight, 0);
+ let dh = NumCast(this.props.Document.height, 0);
+ let sh = scrBounds.height;
+ this.props.Document.height = nh ? dh / nh * sh : sh;
+ this.props.Document.proto!.nativeHeight = nh ? sh : undefined;
+ }
}
@action
@@ -379,6 +355,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onPointerLeave = (e: React.PointerEvent) => {
this._entered = false;
}
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ let subitems: ContextMenuProps[] = [];
+ subitems.push({
+ description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height",
+ event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt"
+ });
+ ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems });
+ }
render() {
let style = this.props.isOverlay ? "scroll" : "hidden";
let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
@@ -392,10 +377,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial",
pointerEvents: interactive ? "all" : "none",
}}
- // onKeyDown={this.onKeyPress}
- onKeyPress={this.onKeyPress}
+ onKeyDown={this.onKeyPress}
onFocus={this.onFocused}
onClick={this.onClick}
+ onContextMenu={this.specificContextMenu}
onBlur={this.onBlur}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
@@ -405,7 +390,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onPointerEnter={this.onPointerEnter}
onPointerLeave={this.onPointerLeave}
>
- <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} />
+ <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} />
</div>
);
}
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 57221aa39..0dca4b4b1 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -6,7 +6,6 @@ import { ImageField } from '../new_fields/URLField';
import { Doc } from '../new_fields/Doc';
import { List } from '../new_fields/List';
-
const schema1 = createSchema({
hello: "number",
test: "string",
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 7e02a5bc5..c2cfda079 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -186,7 +186,8 @@ export namespace Doc {
}
// compare whether documents or their protos match
- export function AreProtosEqual(doc: Doc, other: Doc) {
+ export function AreProtosEqual(doc?: Doc, other?: Doc) {
+ if (!doc || !other) return false;
let r = (doc === other);
let r2 = (doc.proto === other);
let r3 = (other.proto === doc);
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index fe0da2e7a..8cb1db953 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -6,7 +6,7 @@ import { FieldValue } from "./Types";
import { RefField } from "./RefField";
import { ObjectField } from "./ObjectField";
import { action } from "mobx";
-import { Parent, OnUpdate, Update, Id } from "./FieldSymbols";
+import { Parent, OnUpdate, Update, Id, SelfProxy } from "./FieldSymbols";
import { ComputedField } from "../fields/ScriptField";
export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean {
@@ -86,7 +86,7 @@ export function deleteProperty(target: any, prop: string | number | symbol) {
delete target[prop];
return true;
}
- target[prop] = undefined;
+ target[SelfProxy][prop] = undefined;
return true;
}
diff --git a/src/server/index.ts b/src/server/index.ts
index fd66c90b4..eda1ab422 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -9,7 +9,6 @@ import * as fs from 'fs';
import * as sharp from 'sharp';
const imageDataUri = require('image-data-uri');
import * as mobileDetect from 'mobile-detect';
-import { ObservableMap } from 'mobx';
import * as passport from 'passport';
import * as path from 'path';
import * as request from 'request';