diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
11 files changed, 341 insertions, 278 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 55b3f6f1e..5c3f3dcc9 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -5,7 +5,7 @@ import { Id } from "../../../../fields/FieldSymbols"; import { ObjectField } from "../../../../fields/ObjectField"; import { ComputedField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, returnZero, returnEmptyFilter } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; import { DocumentView } from "../DocumentView"; @@ -254,6 +254,7 @@ export class DashDocView extends React.Component<IDashDocView> { whenActiveChanged={returnFalse} bringToFront={emptyFunction} dontRegisterView={false} + docFilters={this.props.tbox?.props.docFilters||returnEmptyFilter} ContainingCollectionView={this._textBox.props.ContainingCollectionView} ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} ContentScaling={this.contentScaling} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 7b0ceb6cf..5c084ae92 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -73,7 +73,7 @@ .collectionfreeformview-container { position: relative; } - + >.formattedTextBox-sidebar-handle { right: unset; left: -5; @@ -93,10 +93,13 @@ left: 10%; } -.formattedTextBox-inner-rounded, -.formattedTextBox-inner { +.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected, +.formattedTextBox-inner, .formattedTextBox-inner-selected { height: 100%; - white-space: pre-wrap; + white-space: pre-wrap; + .ProseMirror:hover { + background: rgba(200,200,200,0.8); + } hr { display: block; unicode-bidi: isolate; @@ -249,34 +252,37 @@ footnote::after { .prosemirror-links { display: none; position: absolute; - background-color: gray; - padding-bottom: 10px; - margin-top: 1em; + background-color: dimgray; + margin-top: 1.5em; z-index: 1; + padding: 5; + border-radius: 2px; } .prosemirror-hrefoptions{ width:0px; border:unset; padding:0px; - } - + .prosemirror-links a { float: left; color: white; text-decoration: none; + border-radius: 3px; } - + .prosemirror-links a:hover { background-color: #eee; color: black; } - + .prosemirror-anchor:hover .prosemirror-links { display: grid; } .ProseMirror { + padding: 0px; + height: max-content; touch-action: none; span { font-family: inherit; @@ -291,6 +297,9 @@ footnote::after { margin-left: 1em; font-family: inherit; } + .bullet { p {display: inline; font-family: inherit} margin-left: 0; } + .bullet1 { p {display: inline; font-family: inherit} } + .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p {display: inline; font-family: inherit} font-size: smaller; } .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} @@ -305,6 +314,8 @@ footnote::after { .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} + .bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; margin-left: -1em; width: 1em; content:" " } + .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } @@ -312,11 +323,21 @@ footnote::after { .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } - + .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } - .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } + .multi4:before { transition: 0.5s;counter-increment: multi4; display: inline-block; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } +} + +.formattedTextBox-inner-rounded-selected, +.formattedTextBox-inner-selected { + .ProseMirror { + padding:10px; + } + .ProseMirror:hover { + background: unset; + } } @media only screen and (max-width: 1000px) { @@ -327,11 +348,11 @@ footnote::after { height: 100%; min-height: 100%; } - + .ProseMirror:focus { outline: none !important; } - + .formattedTextBox-cont { touch-action: none; cursor: text; @@ -350,7 +371,7 @@ footnote::after { display: flex; flex-direction: row; transition: opacity 1s; - + .formattedTextBox-dictation { height: 12px; width: 10px; @@ -359,7 +380,7 @@ footnote::after { position: absolute; } } - + .formattedTextBox-outer { position: relative; overflow: auto; @@ -367,7 +388,7 @@ footnote::after { width: 100%; height: 100%; } - + .formattedTextBox-sidebar-handle { position: absolute; top: calc(50% - 17.5px); @@ -377,12 +398,12 @@ footnote::after { border-radius: 20px; cursor:grabbing; } - + .formattedTextBox-cont>.formattedTextBox-sidebar-handle { right: 0; left: unset; } - + .formattedTextBox-sidebar, .formattedTextBox-sidebar-inking { border-left: dashed 1px black; @@ -390,21 +411,21 @@ footnote::after { display: inline-block; position: absolute; right: 0; - + .collectionfreeformview-container { position: relative; } - + >.formattedTextBox-sidebar-handle { right: unset; left: -5; } } - + .formattedTextBox-sidebar-inking { pointer-events: all; } - + .formattedTextBox-inner-rounded { height: 70%; width: 85%; @@ -413,11 +434,11 @@ footnote::after { top: 15%; left: 10%; } - + .formattedTextBox-inner-rounded, .formattedTextBox-inner { height: 100%; - white-space: pre-wrap; + white-space: pre-wrap; hr { display: block; unicode-bidi: isolate; @@ -430,7 +451,7 @@ footnote::after { border-width: 1px; } } - + // .menuicon { // display: inline-block; // border-right: 1px solid rgba(0, 0, 0, 0.2); @@ -442,21 +463,21 @@ footnote::after { // text-align: center; // min-width: 1.4em; // } - + .strong, .heading { font-weight: bold; } - + .em { font-style: italic; } - + .userMarkOpen { background: rgba(255, 255, 0, 0.267); display: inline; } - + .userMark { background: rgba(255, 255, 0, 0.267); font-size: 2px; @@ -469,28 +490,28 @@ footnote::after { text-align: center; align-content: center; } - + footnote { display: inline-block; position: relative; cursor: pointer; - + div { padding: 0 !important; } } - + footnote::after { content: counter(prosemirror-footnote); vertical-align: super; font-size: 75%; counter-increment: prosemirror-footnote; } - + .ProseMirror { counter-reset: prosemirror-footnote; } - + .footnote-tooltip { cursor: auto; font-size: 75%; @@ -504,11 +525,11 @@ footnote::after { min-width: 50px; width: max-content; } - + .prosemirror-attribution { font-size: 8px; } - + .footnote-tooltip::before { border: 5px solid silver; border-top-width: 0px; @@ -521,8 +542,8 @@ footnote::after { height: 0; width: 0; } - - + + .formattedTextBox-inlineComment { position: relative; width: 40px; @@ -534,7 +555,7 @@ footnote::after { background: orange; } } - + .formattedTextBox-summarizer { opacity: 0.5; position: relative; @@ -544,7 +565,7 @@ footnote::after { content: "←"; } } - + .formattedTextBox-summarizer-collapsed { opacity: 0.5; position: relative; @@ -554,13 +575,13 @@ footnote::after { content: "..."; } } - + .ProseMirror { touch-action: none; span { font-family: inherit; } - + ol, ul { counter-reset: deci1 0 multi1 0; padding-left: 1em; @@ -570,7 +591,7 @@ footnote::after { margin-left: 1em; font-family: inherit; } - + .decimal1-ol { counter-reset: deci1; p {display: inline; font-family: inherit} margin-left: 0; } .decimal2-ol { counter-reset: deci2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1em;} .decimal3-ol { counter-reset: deci3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} @@ -578,12 +599,12 @@ footnote::after { .decimal5-ol { counter-reset: deci5; p {display: inline; font-family: inherit} font-size: smaller; } .decimal6-ol { counter-reset: deci6; p {display: inline; font-family: inherit} font-size: smaller; } .decimal7-ol { counter-reset: deci7; p {display: inline; font-family: inherit} font-size: smaller; } - + .multi1-ol { counter-reset: multi1; p {display: inline; font-family: inherit} margin-left: 0; padding-left: 1.2em } .multi2-ol { counter-reset: multi2; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 1.4em;} .multi3-ol { counter-reset: multi3; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 2em;} .multi4-ol { counter-reset: multi4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3.4em;} - + .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } @@ -591,7 +612,7 @@ footnote::after { .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; margin-left: -2em; width: 5em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) ". "; } .decimal6:before { transition: 0.5s;counter-increment: deci6; display: inline-block; margin-left: -2em; width: 6em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) ". "; } .decimal7:before { transition: 0.5s;counter-increment: deci7; display: inline-block; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } - + .multi1:before { transition: 0.5s;counter-increment: multi1; display: inline-block; margin-left: -1em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; margin-left: -2.85em; width:2.85em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) ". "; } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ae508cd67..a5a40a31f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,8 +13,9 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../../fields/DateField'; -import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym, AclSym } from "../../../../fields/Doc"; import { documentSchema } from '../../../../fields/documentSchemas'; +import applyDevTools = require("prosemirror-dev-tools"); import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; @@ -22,7 +23,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast } from "../../../../fields/Types"; -import { TraceMobx } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_ACL } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -57,7 +58,6 @@ import { FieldView, FieldViewProps } from "../FieldView"; import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); -import { InkingStroke } from '../../InkingStroke'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -199,22 +199,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template const json = JSON.stringify(state.toJSON()); - if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { - this._applyingChange = true; - this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (json !== curLayout?.Data) { - !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize)); - !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily)); - this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + if (!this.dataDoc[AclSym]) { + if (!this._applyingChange && json.replace(/"selection":.*/, "") !== curProto?.Data.replace(/"selection":.*/, "")) { + this._applyingChange = true; + this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + if ((!curTemp && !curProto) || curText || curLayout?.Data.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) + if (json !== curLayout?.Data) { + !curText && tx.storedMarks?.map(m => m.type.name === "pFontSize" && (Doc.UserDoc().fontSize = this.layoutDoc._fontSize = m.attrs.fontSize)); + !curText && tx.storedMarks?.map(m => m.type.name === "pFontFamily" && (Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily)); + this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = (curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited + } + } else { // if we've deleted all the text in a note driven by a template, then restore the template data + this.dataDoc[this.props.fieldKey] = undefined; + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); + this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have } - } else { // if we've deleted all the text in a note driven by a template, then restore the template data - this.dataDoc[this.props.fieldKey] = undefined; - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); - this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + this._applyingChange = false; } - this._applyingChange = false; + } else { + const json = JSON.parse(Cast(this.dataDoc[this.fieldKey], RichTextField)?.Data!); + json.selection = state.toJSON().selection; + this._editorView.updateState(EditorState.fromJSON(this.config, json)); } this.updateTitle(); this.tryUpdateHeight(); @@ -417,42 +423,66 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; - const funcs: ContextMenuProps[] = []; - this.rootDoc.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - !this.layoutDoc.isTemplateDoc && funcs.push({ - description: "Convert to use as a style", event: () => { - this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc); - Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); - }, icon: "eye" + const appearance = ContextMenu.Instance.findByDescription("Appearance..."); + const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; + + const changeItems: ContextMenuProps[] = []; + const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); + DocListCast(noteTypesDoc?.data).forEach(note => { + changeItems.push({ + description: StrCast(note.title), event: undoBatch(() => { + Doc.setNativeView(this.rootDoc); + DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); + }), icon: "eye" + }); }); - this.layoutDoc.isTemplateDoc && funcs.push({ - description: "Make New Template", event: () => { - const title = this.rootDoc.title as string; - this.rootDoc.layout = (this.layoutDoc as Doc).layout as string; - this.rootDoc.title = this.layoutDoc.isTemplateForField as string; - this.rootDoc.isTemplateDoc = false; - this.rootDoc.isTemplateForField = ""; - this.rootDoc.layoutKey = "layout"; - this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); - setTimeout(() => { - this.rootDoc._width = this.layoutDoc._width || 300; // the width and height are stored on the template, since we're getting rid of the old template - this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields - this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); - }, 10); + changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); + appearanceItems.push({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); + const uicontrols: ContextMenuProps[] = []; + uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); + uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); + uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); + !Doc.UserDoc().noviceMode && uicontrols.push({ + description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => + proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" + }); + + appearanceItems.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); + this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); + Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + appearanceItems.push({ + description: "Convert to be a template style", event: () => { + if (!this.layoutDoc.isTemplateDoc) { + const title = StrCast(this.rootDoc.title); + this.rootDoc.title = "text"; + this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + } else { + const title = StrCast(this.rootDoc.title); + this.rootDoc.title = "text"; + this.rootDoc.layout = (this.layoutDoc as Doc).layout as string; + this.rootDoc.title = this.layoutDoc.isTemplateForField as string; + this.rootDoc.isTemplateDoc = false; + this.rootDoc.isTemplateForField = ""; + this.rootDoc.layoutKey = "layout"; + this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + setTimeout(() => { + this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height + this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template + this.rootDoc._height = this.layoutDoc._height || 200; // we need to copy them over to the root. This should probably apply to all '_' fields + this.rootDoc._backgroundColor = Cast(this.layoutDoc._backgroundColor, "string", null); + }, 10); + } Doc.AddDocToList(Cast(Doc.UserDoc()["template-notes"], Doc, null), "data", this.rootDoc); }, icon: "eye" }); + !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); + + const funcs: ContextMenuProps[] = []; + //funcs.push({ description: `${this.Document._autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc._autoHeight = !this.layoutDoc._autoHeight, icon: "plus" }); funcs.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); funcs.push({ description: "Toggle Single Line", event: () => this.layoutDoc._singleLine = !this.layoutDoc._singleLine, icon: "expand-arrows-alt" }); - const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ description: "Toggle Sidebar", event: () => this.layoutDoc._showSidebar = !this.layoutDoc._showSidebar, icon: "expand-arrows-alt" }); - uicontrols.push({ description: "Toggle Dictation Icon", event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); - uicontrols.push({ description: "Toggle Menubar", event: () => this.toggleMenubar(), icon: "expand-arrows-alt" }); - - funcs.push({ description: "UI Controls...", subitems: uicontrols, icon: "asterisk" }); - const highlighting: ContextMenuProps[] = []; ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => highlighting.push({ @@ -469,21 +499,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp funcs.push({ description: "highlighting...", subitems: highlighting, icon: "hand-point-right" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - - const change = cm.findByDescription("Change Perspective..."); - const changeItems: ContextMenuProps[] = change && "subitems" in change ? change.subitems : []; - - const noteTypesDoc = Cast(Doc.UserDoc()["template-notes"], Doc, null); - DocListCast(noteTypesDoc?.data).forEach(note => { - changeItems.push({ - description: StrCast(note.title), event: undoBatch(() => { - Doc.setNativeView(this.rootDoc); - DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.TreeDocument, StrCast(note.title), note); - }), icon: "eye" - }); - }); - changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); - !change && cm.addItem({ description: "Change Perspective...", subitems: changeItems, icon: "external-link-alt" }); + this._downX = this._downY = Number.NaN; } recordDictation = () => { @@ -584,10 +600,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; } - makeLinkToSelection(linkId: string, title: string, location: string, targetId: string) { + makeLinkToSelection(linkId: string, title: string, location: string, targetId: string, targetHref?: string) { const state = this._editorView?.state; if (state) { - const href = Utils.prepend("/doc/" + linkId); + const href = targetHref ?? Utils.prepend("/doc/" + linkId); const sel = state.selection; const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); @@ -595,11 +611,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { const allHrefs = [{ href, title, targetId, linkId }]; allHrefs.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.link.name)?.attrs.allHrefs ?? [])); - const link = state.schema.marks.link.create({ href, allHrefs, title, location, linkId, targetId }); + const link = state.schema.marks.link.create({ allHrefs, title, location, linkId }); tr = tr.addMark(pos, pos + node.nodeSize, link); } }); + OVERRIDE_ACL(true); this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); + OVERRIDE_ACL(false); } } componentDidMount() { @@ -914,11 +932,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); + // applyDevTools.applyDevTools(this._editorView); const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { const { state: { tr }, dispatch } = this._editorView; dispatch(tr.insertText(startupText)); } + (this._editorView as any).TextView = this; } const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad; @@ -989,15 +1009,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!FormattedTextBox._downEvent) return; FormattedTextBox._downEvent = false; if (!(e.nativeEvent as any).formattedHandled) { + const editor = this._editorView!; FormattedTextBoxComment.textBox = this; - FormattedTextBoxComment.update(this._editorView!, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : ""); + const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); + const node = pcords && editor.state.doc.nodeAt(pcords.pos); // get what prosemirror thinks the clicked node is (if it's null, then we didn't click on any text) + !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(node && pcords ? + new NodeSelection(editor.state.doc.resolve(pcords.pos)) : new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); + FormattedTextBoxComment.update(editor, undefined, (e.target as any)?.className === "prosemirror-dropdownlink" ? (e.target as any).href : ""); } (e.nativeEvent as any).formattedHandled = true; if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { e.stopPropagation(); } - this._downX = this._downY = Number.NaN; } @action @@ -1018,6 +1042,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (bounds && this.layoutDoc._chromeStatus !== "disabled") { const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width); let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height); + console.log("y = " + y + " hgt = " + RichTextMenu.Instance.height + " cords = " + coords.top); if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) { y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height); } @@ -1052,48 +1077,42 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } (e.nativeEvent as any).formattedHandled = true; - if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientX - this._downX) < 4) { - this.props.select(e.ctrlKey); - this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); - } if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events e.stopPropagation(); + this.hitBulletTargets(e.clientX, e.clientY, e.shiftKey, false); } } // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. - hitBulletTargets(x: number, y: number, select: boolean, highlightOnly: boolean) { + hitBulletTargets(x: number, y: number, collapse: boolean, highlightOnly: boolean) { clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); - const pos = this._editorView!.posAtCoords({ left: x, top: y }); - if (pos && this.props.isSelected(true)) { - // let beforeEle = document.querySelector("." + hit.className) as Element; // const before = hit ? window.getComputedStyle(hit, ':before') : undefined; - //const node = this._editorView!.state.doc.nodeAt(pos.pos); - const $pos = this._editorView!.state.doc.resolve(pos.pos); - let list_node = $pos.node().type === schema.nodes.list_item ? $pos.node() : undefined; - if ($pos.node().type === schema.nodes.ordered_list) { - for (let off = 1; off < 100; off++) { - const pos = this._editorView!.posAtCoords({ left: x + off, top: y }); - const node = pos && this._editorView!.state.doc.nodeAt(pos.pos); - if (node?.type === schema.nodes.list_item) { - list_node = node; - break; - } + const clickPos = this._editorView!.posAtCoords({ left: x, top: y }); + let olistPos = clickPos?.pos; + if (clickPos && olistPos && this.props.isSelected(true)) { + const clickNode = this._editorView?.state.doc.nodeAt(olistPos); + const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1)); + olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos; + let $olistPos = this._editorView?.state.doc.resolve(olistPos); + let olistNode = (nodeBef !== null || clickNode?.type === this._editorView?.state.schema.nodes.list_item) && olistPos === clickPos?.pos ? clickNode : nodeBef; + if (olistNode?.type === this._editorView?.state.schema.nodes.list_item) { + if ($olistPos && ($olistPos as any).path.length > 3) { + olistNode = $olistPos.parent; + $olistPos = this._editorView?.state.doc.resolve(($olistPos as any).path[($olistPos as any).path.length - 4]); } } - if (list_node && pos.inside >= 0 && this._editorView!.state.doc.nodeAt(pos.inside)!.attrs.bulletStyle === list_node.attrs.bulletStyle) { - if (select) { - const $olist_pos = this._editorView!.state.doc.resolve($pos.pos - $pos.parentOffset - 1); + const listNode = this._editorView?.state.doc.nodeAt(clickPos.pos); + if (olistNode && olistNode.type === this._editorView?.state.schema.nodes.ordered_list) { + if (!collapse) { if (!highlightOnly) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection($olist_pos))); + this._editorView.dispatch(this._editorView.state.tr.setSelection(new NodeSelection($olistPos!))); } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); - } else if (Math.abs(pos.pos - pos.inside) < 2) { + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + } else if (listNode && listNode.type === this._editorView.state.schema.nodes.list_item) { if (!highlightOnly) { - const offset = this._editorView!.state.doc.nodeAt(pos.inside)?.type === schema.nodes.ordered_list ? 1 : 0; - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.inside + offset, list_node.type, { ...list_node.attrs, visibility: !list_node.attrs.visibility })); - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(TextSelection.create(this._editorView!.state.doc, pos.inside + offset))); + this._editorView.dispatch(this._editorView.state.tr.setNodeMarkup(clickPos.pos, listNode.type, { ...listNode.attrs, visibility: !listNode.attrs.visibility })); + this._editorView.dispatch(this._editorView.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, clickPos.pos))); } - addStyleSheetRule(FormattedTextBox._bulletStyleSheet, list_node.attrs.mapStyle + list_node.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, olistNode.attrs.mapStyle + olistNode.attrs.bulletStyle + ":hover:before", { background: "lightgray" }); } } } @@ -1190,18 +1209,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const nh = this.layoutDoc.isTemplateForField ? 0 : NumCast(this.dataDoc._nativeHeight, 0); const dh = NumCast(this.rootDoc._height, 0); const newHeight = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); - if (Math.abs(newHeight - dh) > 1) { // bcz: Argh! without this, we get into a React crash if the same document is opened in a freeform view and in the treeview. no idea why, but after dragging the freeform document, selecting it, and selecting text, it will compute to 1 pixel higher than the treeview which causes a cycle - if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) { - // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... - console.log("Delayed height adjustment..."); - setTimeout(() => { - this.rootDoc._height = newHeight; - this.dataDoc._nativeHeight = nh ? scrollHeight : undefined; - }, 10); - } else { + if (this.rootDoc !== this.layoutDoc.doc && !this.layoutDoc.resolvedDataDoc) { + // if we have a template that hasn't been resolved yet, we can't set the height or we'd be setting it on the unresolved template. So set a timeout and hope its arrived... + console.log("Delayed height adjustment..."); + setTimeout(() => { this.rootDoc._height = newHeight; this.dataDoc._nativeHeight = nh ? scrollHeight : undefined; - } + }, 10); + } else { + this.rootDoc._height = newHeight; + this.dataDoc._nativeHeight = nh ? scrollHeight : undefined; } } } @@ -1220,16 +1237,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else if (FormattedTextBoxComment.textBox === this) { setTimeout(() => FormattedTextBoxComment.Hide(), 0); } + const selPad = this.props.isSelected() ? -10 : 0; + const selclass = this.props.isSelected() ? "-selected" : ""; return ( - <div className={"formattedTextBox-cont"} style={{ - transform: `scale(${scale})`, - transformOrigin: "top left", - width: `${100 / scale}%`, - height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`, - ...this.styleFromLayoutString(scale) - }}> + <div className={"formattedTextBox-cont"} + style={{ + transform: `scale(${scale})`, + transformOrigin: "top left", + width: `${100 / scale}%`, + height: `calc(${100 / scale}% - ${this.props.ChromeHeight?.() || 0}px)`, + ...this.styleFromLayoutString(scale) + }}> <div className={`formattedTextBox-cont`} ref={this._ref} style={{ + overflow: this.layoutDoc._autoHeight ? "hidden" : undefined, width: "100%", height: this.props.height ? this.props.height : this.layoutDoc._autoHeight && this.props.renderDepth ? "max-content" : undefined, background: Doc.UserDoc().renderStyle === "comic" ? "transparent" : this.props.background ? this.props.background : StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], this.props.hideOnLeave ? "rgba(0,0,0 ,0.4)" : ""), @@ -1260,12 +1281,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } })} > - <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, }} onScroll={this.onscrolled} ref={this._scrollRef}> - <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} + <div className={`formattedTextBox-outer`} style={{ width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !this.props.isSelected() ? "none" : undefined }} onScroll={this.onscrolled} ref={this._scrollRef}> + <div className={`formattedTextBox-inner${rounded}${selclass}`} ref={this.createDropTarget} style={{ - padding: `${NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0)}px`, - pointerEvents: ((this.layoutDoc.isLinkButton || this.props.onClick) && !this.props.isSelected()) ? "none" : undefined - }} /> + padding: `${Math.max(0, NumCast(this.layoutDoc._yMargin, this.props.yMargin || 0) + selPad)}px ${NumCast(this.layoutDoc._xMargin, this.props.xMargin || 0) + selPad}px`, + pointerEvents: !this.props.isSelected() ? ((this.layoutDoc.isLinkButton || this.props.onClick) ? "none" : "all") : undefined + }} + /> </div> {!this.layoutDoc._showSidebar ? (null) : this.sidebarWidthPercent === "0%" ? <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} /> : diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 000b3c2e5..0d8e22251 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -4,7 +4,7 @@ import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; import { Doc, DocCastAsync } from "../../../../fields/Doc"; import { Cast, FieldValue, NumCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne } from "../../../../Utils"; +import { emptyFunction, returnEmptyString, returnFalse, Utils, emptyPath, returnZero, returnOne, returnEmptyFilter } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import { schema } from "./schema_rts"; @@ -222,9 +222,10 @@ export class FormattedTextBoxComment { addDocTab={returnFalse} pinToPres={returnFalse} dontRegisterView={true} + docFilters={returnEmptyFilter} ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} - renderDepth={1} + renderDepth={0} PanelWidth={() => Math.min(350, NumCast(target._width, 350))} PanelHeight={() => Math.min(250, NumCast(target._height, 250))} focus={emptyFunction} diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 0a4c52ef9..75cfe6bd1 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -16,16 +16,13 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : export type KeyMap = { [key: string]: any }; -export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => { - let fontSize: number | undefined = undefined; +export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string, from?: number, to?: number) => { tx2.doc.descendants((node: any, offset: any, index: any) => { - if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { + if ((from === undefined || to === undefined || (from <= offset + node.nodeSize && to >= offset)) && (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item)) { const path = (tx2.doc.resolve(offset) as any).path; let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); if (node.type === schema.nodes.ordered_list) depth++; - fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize; - const fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined; - tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle || node.attrs.mapStyle, bulletStyle: depth, }, node.marks); } }); return tx2; @@ -62,7 +59,6 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, props: any bind("Mod-U", toggleMark(schema.marks.underline)); //Commands for lists - bind("Ctrl-.", wrapInList(schema.nodes.bullet_list)); bind("Ctrl-i", wrapInList(schema.nodes.ordered_list)); bind("Tab", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => { diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 03d393cde..1a961ae21 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -12,7 +12,7 @@ import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscr import { updateBullets } from "./ProsemirrorExampleTransfer"; import { FieldViewProps } from "../FieldView"; import { Cast, StrCast } from "../../../../fields/Types"; -import { FormattedTextBoxProps } from "./FormattedTextBox"; +import { FormattedTextBoxProps, FormattedTextBox } from "./FormattedTextBox"; import { unimplementedFunction, Utils } from "../../../../Utils"; import { wrapInList } from "prosemirror-schema-list"; import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../../../fields/SchemaHeaderField'; @@ -104,7 +104,7 @@ export default class RichTextMenu extends AntimodeMenu { { node: schema.nodes.ordered_list.create({ mapStyle: "bullet" }), title: "Set list type", label: ":", command: this.changeListType }, { node: schema.nodes.ordered_list.create({ mapStyle: "decimal" }), title: "Set list type", label: "1.1", command: this.changeListType }, { node: schema.nodes.ordered_list.create({ mapStyle: "multi" }), title: "Set list type", label: "1.A", command: this.changeListType }, - { node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, + //{ node: undefined, title: "Set list type", label: "Remove", command: this.changeListType }, ]; this.fontColors = [ @@ -189,9 +189,9 @@ export default class RichTextMenu extends AntimodeMenu { const node = (state.selection as NodeSelection).node; if (node?.type === schema.nodes.ordered_list) { let attrs = node.attrs; - if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, setFontFamily: mark.attrs.family }; - if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, setFontSize: mark.attrs.fontSize }; - if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, setFontColor: mark.attrs.color }; + if (mark.type === schema.marks.pFontFamily) attrs = { ...attrs, fontFamily: mark.attrs.family }; + if (mark.type === schema.marks.pFontSize) attrs = { ...attrs, fontSize: `${mark.attrs.fontSize}px` }; + if (mark.type === schema.marks.pFontColor) attrs = { ...attrs, fontColor: mark.attrs.color }; const tr = updateBullets(state.tr.setNodeMarkup(state.selection.from, node.type, attrs), state.schema); dispatch(tr.setSelection(new NodeSelection(tr.doc.resolve(state.selection.from)))); } else { @@ -307,7 +307,6 @@ export default class RichTextMenu extends AntimodeMenu { function onClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.view.focus(); self.view && command && command(self.view.state, self.view.dispatch, self.view); self.view && onclick && onclick(self.view.state, self.view.dispatch, self.view); self.setActiveMarkButtons(self.getActiveMarksOnSelection()); @@ -378,26 +377,24 @@ export default class RichTextMenu extends AntimodeMenu { // TODO: remove doesn't work //remove all node type and apply the passed-in one to the selected text - changeListType = (nodeType: NodeType | undefined) => { + changeListType = (nodeType: Node | undefined) => { if (!this.view) return; - if (nodeType === schema.nodes.bullet_list) { - wrapInList(nodeType)(this.view.state, this.view.dispatch); - } else { - const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - this.view!.dispatch(tx2); - })) { - const tx2 = this.view.state.tr; - const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); + const marks = this.view.state.storedMarks || (this.view.state.selection.$to.parentOffset && this.view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(this.view.state, (tx2: any) => { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view!.state.selection.from - 1, this.view!.state.selection.to + 1); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + this.view!.dispatch(tx2); + })) { + const tx2 = this.view.state.tr; + if (nodeType && this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list) { + const tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle, this.view.state.selection.from, this.view.state.selection.to); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); - this.view.dispatch(tx3); + this.view.dispatch(tx3.setSelection(new NodeSelection(tx3.doc.resolve(this.view.state.selection.$from.pos)))); } } } @@ -429,7 +426,6 @@ export default class RichTextMenu extends AntimodeMenu { function onBrushClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.view.focus(); self.view && self.fillBrush(self.view.state, self.view.dispatch); } @@ -503,13 +499,11 @@ export default class RichTextMenu extends AntimodeMenu { function onColorClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.view.focus(); self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); } function changeColor(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); - self.view && self.view.focus(); self.setActiveColor(color); self.view && self.insertColor(self.activeFontColor, self.view.state, self.view.dispatch); } @@ -556,13 +550,11 @@ export default class RichTextMenu extends AntimodeMenu { function onHighlightClick(e: React.PointerEvent) { e.preventDefault(); e.stopPropagation(); - self.view && self.view.focus(); self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); } function changeHighlight(e: React.PointerEvent, color: string) { e.preventDefault(); e.stopPropagation(); - self.view && self.view.focus(); self.setActiveHighlight(color); self.view && self.insertHighlight(self.activeHighlightColor, self.view.state, self.view.dispatch); } @@ -661,15 +653,8 @@ export default class RichTextMenu extends AntimodeMenu { } // TODO: should check for valid URL - makeLinkToURL = (target: String, lcoation: string) => { - if (!this.view) return; - - let node = this.view.state.selection.$from.nodeAfter; - let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); - this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - node = this.view.state.selection.$from.nodeAfter; - link = node && node.marks.find(m => m.type.name === "link"); + makeLinkToURL = (target: string, lcoation: string) => { + ((this.view as any)?.TextView as FormattedTextBox).makeLinkToSelection("", target, "onRight", "", target); } deleteLink = () => { @@ -762,13 +747,14 @@ export default class RichTextMenu extends AntimodeMenu { this.collapsed = !this.collapsed; setTimeout(() => { const x = Math.min(this._left, window.innerWidth - RichTextMenu.Instance.width); - RichTextMenu.Instance.jumpTo(x, this._top); + RichTextMenu.Instance.jumpTo(x, this._top, true); }, 0); } render() { const row1 = <div className="antimodeMenu-row" key="row1" style={{ display: this.collapsed ? "none" : undefined }}>{[ + !this.collapsed ? this.getDragger() : (null), this.createButton("bold", "Bold", this.boldActive, toggleMark(schema.marks.strong)), this.createButton("italic", "Italic", this.italicsActive, toggleMark(schema.marks.em)), this.createButton("underline", "Underline", this.underlineActive, toggleMark(schema.marks.underline)), @@ -783,6 +769,7 @@ export default class RichTextMenu extends AntimodeMenu { ]}</div>; const row2 = <div className="antimodeMenu-row row-2" key="antimodemenu row2"> + {this.collapsed ? this.getDragger() : (null)} <div key="row" style={{ display: this.collapsed ? "none" : undefined }}> {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size"), this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family"), @@ -797,7 +784,6 @@ export default class RichTextMenu extends AntimodeMenu { <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}> <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> </button> - {this.getDragger()} </div> </div>; @@ -842,7 +828,6 @@ class ButtonDropdown extends React.Component<ButtonDropdownProps> { onDropdownClick = (e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); - this.props.view && this.props.view.focus(); this.toggleDropdown(); } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 91187edf9..ba3230801 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -59,7 +59,16 @@ export class RichTextRules { ), // * + - create bullet list - wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list), + wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.ordered_list, + // match => { + () => { + return ({ mapStyle: "bullet" }); + // return ({ order: +match[1] }) + }, + (match: any, node: any) => { + return node.childCount + node.attrs.order === +match[1]; + }, + (type: any) => ({ type: type, attrs: { mapStyle: "bullet" } })), // ``` create code block textblockTypeInputRule(/^```$/, schema.nodes.code_block), diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index 05557e22a..a989abd6a 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -152,6 +152,7 @@ export class DashDocView { whenActiveChanged={returnFalse} bringToFront={emptyFunction} dontRegisterView={false} + docFilters={this._textBox.props.docFilters} ContainingCollectionView={this._textBox.props.ContainingCollectionView} ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc} ContentScaling={this.contentScaling} diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 922285bd0..c017db034 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -14,7 +14,7 @@ export class SummaryView { const self = this; this._fieldWrapper = document.createElement("span"); this._fieldWrapper.className = this.className(node.attrs.visibility); - this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); } + this._fieldWrapper.onpointerdown = function (e: any) { self.onPointerDown(e, node, view, getPos); }; this._fieldWrapper.onkeypress = function (e: any) { e.stopPropagation(); }; this._fieldWrapper.onkeydown = function (e: any) { e.stopPropagation(); }; this._fieldWrapper.onkeyup = function (e: any) { e.stopPropagation(); }; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 49d5c96a4..1de211f28 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -53,21 +53,45 @@ export const marks: { [index: string]: MarkSpec } = { } }, + /** FONT SIZES */ + pFontSize: { + attrs: { fontSize: { default: 10 } }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { fontSize: dom.style.fontSize ? Number(dom.style.fontSize.replace("px", "")) : "" }; + } + }], + toDOM: (node) => node.attrs.fontSize ? ['span', { style: `font-size: ${node.attrs.fontSize}px;` }] : ['span', 0] + }, + /* FONTS */ + pFontFamily: { + attrs: { family: { default: "" } }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + const cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; + if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; + if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; + if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; + if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; + if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; + } + } + }], + toDOM: (node) => node.attrs.family ? ['span', { style: `font-family: "${node.attrs.family}";` }] : ['span', 0] + }, // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. pFontColor: { - attrs: { - color: { default: "#000" } - }, + attrs: { color: { default: "" } }, inclusive: true, parseDOM: [{ tag: "span", getAttrs(dom: any) { return { color: dom.getAttribute("color") }; } }], - toDOM(node: any) { - return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0]; - } + toDOM: (node) => node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', 0] }, marker: { @@ -277,38 +301,4 @@ export const marks: { [index: string]: MarkSpec } = { parseDOM: [{ tag: "code" }], toDOM() { return codeDOM; } }, - - /* FONTS */ - pFontFamily: { - attrs: { - family: { default: "Crimson Text" }, - }, - parseDOM: [{ - tag: "span", getAttrs(dom: any) { - const cstyle = getComputedStyle(dom); - if (cstyle.font) { - if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; - if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; - if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; - if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; - if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; - if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; - } - } - }], - toDOM: (node) => ['span', { - style: `font-family: "${node.attrs.family}";` - }] - }, - - /** FONT SIZES */ - pFontSize: { - attrs: { - fontSize: { default: 10 } - }, - parseDOM: [{ style: 'font-size: 10px;' }], - toDOM: (node) => ['span', { - style: `font-size: ${node.attrs.fontSize}px;` - }] - }, }; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index af39ef9c7..33ef67ff5 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -218,48 +218,85 @@ export const nodes: { [index: string]: NodeSpec } = { group: 'block', attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, - setFontSize: { default: undefined }, - setFontFamily: { default: "inherit" }, - setFontColor: { default: "inherit" }, - inheritedFontSize: { default: undefined }, + mapStyle: { default: "decimal" },// "decimal", "multi", "bullet" + fontColor: { default: "inherit" }, + fontSize: { default: undefined }, + fontFamily: { default: undefined }, visibility: { default: true }, indent: { default: undefined } }, + parseDOM: [ + { + tag: "ul", getAttrs(dom: any) { + return { + bulletStyle: dom.getAttribute("data-bulletStyle"), + mapStyle: dom.getAttribute("data-mapStyle"), + fontColor: dom.style.color, + fontSize: dom.style["font-size"], + fontFamily: dom.style["font-family"], + indent: dom.style["margin-left"] + }; + } + }, + { + style: 'list-style-type=disc', getAttrs(dom: any) { + return { mapStyle: "bullet" }; + } + }, + { + tag: "ol", getAttrs(dom: any) { + return { + bulletStyle: dom.getAttribute("data-bulletStyle"), + mapStyle: dom.getAttribute("data-mapStyle"), + fontColor: dom.style.color, + fontSize: dom.style["font-size"], + fontFamily: dom.style["font-family"], + indent: dom.style["margin-left"] + }; + } + }], toDOM(node: Node<any>) { - if (node.attrs.mapStyle === "bullet") return ['ul', 0]; const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - const fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; - const ffam = node.attrs.setFontFamily; - const color = node.attrs.setFontColor; + const fsize = node.attrs.fontSize ? `font-size: ${node.attrs.fontSize};` : ""; + const ffam = node.attrs.fontFamily ? `font-family:${node.attrs.fontFamily};` : ""; + const fcol = node.attrs.fontColor ? `color: ${node.attrs.fontColor};` : ""; + const marg = node.attrs.indent ? `margin-left: ${node.attrs.indent};` : ""; + if (node.attrs.mapStyle === "bullet") { + return ['ul', { + "data-mapStyle": node.attrs.mapStyle, + "data-bulletStyle": node.attrs.bulletStyle, + style: `${fsize} ${ffam} ${fcol} ${marg}` + }, 0]; + } return node.attrs.visibility ? - ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}; color:${color}; margin-left: ${node.attrs.indent}` }, 0] : + ['ol', { + class: `${map}-ol`, + "data-mapStyle": node.attrs.mapStyle, + "data-bulletStyle": node.attrs.bulletStyle, + style: `list-style: none; ${fsize} ${ffam} ${fcol} ${marg}` + }, 0] : ['ol', { class: `${map}-ol`, style: `list-style: none;` }]; } }, - bullet_list: { - ...bulletList, - content: 'list_item+', - group: 'block', - // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], - toDOM(node: Node<any>) { - return ['ul', 0]; - } - }, - list_item: { + ...listItem, attrs: { bulletStyle: { default: 0 }, - mapStyle: { default: "decimal" }, + mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet" visibility: { default: true } }, - ...listItem, content: 'paragraph block*', + parseDOM: [{ + tag: "li", getAttrs(dom: any) { + return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") }; + } + }], toDOM(node: any) { const map = node.attrs.bulletStyle ? node.attrs.mapStyle + node.attrs.bulletStyle : ""; - return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; - //return ["li", { class: `${map}` }, 0]; + return node.attrs.visibility ? + ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, 0] : + ["li", { class: `${map}`, "data-mapStyle": node.attrs.mapStyle, "data-bulletStyle": node.attrs.bulletStyle }, "..."]; } }, };
\ No newline at end of file |
