diff options
Diffstat (limited to 'src/client/views/nodes/formattedText')
6 files changed, 702 insertions, 323 deletions
diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 08f255cab..73a711b9d 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -70,6 +70,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { @observable _dashDoc: Doc | undefined; @observable _finalLayout: any; @observable _resolvedDataDoc: any; + @observable _width: number = 0; + @observable _height: number = 0; updateDoc = action((dashDoc: Doc) => { this._dashDoc = dashDoc; @@ -83,12 +85,14 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { } if (this.props.width !== (this._dashDoc?._width ?? '') + 'px' || this.props.height !== (this._dashDoc?._height ?? '') + 'px') { try { + this._width = NumCast(this._dashDoc?._width); + this._height = NumCast(this._dashDoc?._height); // bcz: an exception will be thrown if two aliases are open at the same time when a doc view comment is made this.props.view.dispatch( this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { ...this.props.node.attrs, - width: (this._dashDoc?._width ?? '') + 'px', - height: (this._dashDoc?._height ?? '') + 'px', + width: this._width + 'px', + height: this._height + 'px', }) ); } catch (e) { @@ -121,17 +125,17 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { componentDidMount() { this._disposers.upater = reaction( () => this._dashDoc && NumCast(this._dashDoc._height) + NumCast(this._dashDoc._width), - () => { + action(() => { if (this._dashDoc) { - this.props.view.dispatch( - this.props.view.state.tr.setNodeMarkup(this.props.getPos(), null, { - ...this.props.node.attrs, - width: (this._dashDoc?._width ?? '') + 'px', - height: (this._dashDoc?._height ?? '') + 'px', - }) - ); + this._width = NumCast(this._dashDoc._width); + this._height = NumCast(this._dashDoc._height); + const parent = this._spanRef.current?.parentNode as HTMLElement; + if (parent) { + parent.style.width = this._width + 'px'; + parent.style.height = this._height + 'px'; + } } - } + }) ); } @@ -172,8 +176,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { ref={this._spanRef} className="dash-span" style={{ - width: this.props.width, - height: this.props.height, + width: this._width, + height: this._height, position: 'absolute', display: 'inline-block', }} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 8c8b74560..35d919f38 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -178,7 +178,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna } else { if (Number(newText).toString() === newText) { if (this._fieldKey.startsWith('_')) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); - Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true); + Doc.SetInPlace(this._dashDoc!, this._fieldKey, Number(newText), true); } else { const splits = newText.split(DashFieldViewInternal.multiValueDelimeter); if (this._fieldKey !== 'PARAMS' || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) { @@ -216,7 +216,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna onPointerDownLabelSpan = (e: any) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; - DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16); + DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey); }); }; @@ -253,17 +253,21 @@ export class DashFieldViewMenu extends AntimodeMenu<AntimodeMenuProps> { DashFieldViewMenu.Instance.fadeOut(true); }; - public show = (x: number, y: number) => { + @observable _fieldKey = ''; + + @action + public show = (x: number, y: number, fieldKey: string) => { + this._fieldKey = fieldKey; this.jumpTo(x, y, true); const hideMenu = () => { this.fadeOut(true); - document.removeEventListener('pointerdown', hideMenu); + document.removeEventListener('pointerdown', hideMenu, true); }; - document.addEventListener('pointerdown', hideMenu); + document.addEventListener('pointerdown', hideMenu, true); }; render() { const buttons = [ - <Tooltip key="trash" title={<div className="dash-tooltip">{'Remove Link Anchor'}</div>}> + <Tooltip key="trash" title={<div className="dash-tooltip">{`Show Pivot Viewer for '${this._fieldKey}'`}</div>}> <button className="antimodeMenu-button" onPointerDown={this.showFields}> <FontAwesomeIcon icon="eye" size="lg" /> </button> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 27817f317..d3d8c47c0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -1,4 +1,4 @@ -@import "../../global/globalCssVariables"; +@import '../../global/globalCssVariables'; .ProseMirror { width: 100%; @@ -11,13 +11,13 @@ } audiotag { - left: 0; - position: absolute; - cursor: pointer; - border-radius: 10px; - width: 10px; - margin-top: -2px; - font-size: 4px; + left: 0; + position: absolute; + cursor: pointer; + border-radius: 10px; + width: 10px; + margin-top: -2px; + font-size: 4px; background: lightblue; } audiotag:hover { @@ -63,12 +63,11 @@ audiotag:hover { .formattedTextBox-outer-selected { cursor: text; } - + .formattedTextBox-sidebar-handle { position: absolute; top: 0; - left: 17px; - //top: calc(50% - 17.5px); // use this to center vertically -- make sure it looks okay for slide views + right: 0; width: 17px; height: 17px; font-size: 11px; @@ -79,15 +78,14 @@ audiotag:hover { display: flex; justify-content: center; align-items: center; - cursor:grabbing; + cursor: grabbing; box-shadow: $standard-box-shadow; // transition: 0.2s; opacity: 0.3; - &:hover{ + &:hover { opacity: 1 !important; filter: brightness(0.85); } - } .formattedTextBox-sidebar, @@ -117,14 +115,15 @@ audiotag:hover { left: 10%; } -.formattedTextBox-inner-rounded, .formattedTextBox-inner-rounded-selected, -.formattedTextBox-inner, +.formattedTextBox-inner-rounded, +.formattedTextBox-inner-rounded-selected, +.formattedTextBox-inner, .formattedTextBox-inner-minimal, .formattedTextBox-inner-selected { height: 100%; white-space: pre-wrap; .ProseMirror:hover { - background: rgba(200,200,200,0.2); + background: rgba(200, 200, 200, 0.2); } hr { display: block; @@ -141,7 +140,7 @@ audiotag:hover { .formattedTextBox-inner-rounded-selected, .formattedTextBox-inner-selected { > .ProseMirror { - padding:10px; + padding: 10px; } } .formattedTextBox-outer-selected { @@ -236,18 +235,17 @@ footnote::after { position: absolute; top: -5px; left: 27px; - content: " "; + content: ' '; height: 0; width: 0; } - .formattedTextBox-inlineComment { position: relative; width: 40px; height: 20px; &::before { - content: "→"; + content: '→'; } &:hover { background: orange; @@ -260,7 +258,7 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "←"; + content: '←'; } } @@ -270,21 +268,21 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "..."; + content: '...'; } } .prosemirror-anchor { - overflow:hidden; - display:inline-grid; + overflow: hidden; + display: inline-grid; } .prosemirror-linkBtn { - background:unset; - color:unset; - padding:0; + background: unset; + color: unset; + padding: 0; text-transform: unset; letter-spacing: unset; - font-size:unset; + font-size: unset; } .prosemirror-links { display: none; @@ -294,28 +292,28 @@ footnote::after { z-index: 1; padding: 5; border-radius: 2px; - } - .prosemirror-hrefoptions{ - width:0px; - border:unset; - padding:0px; - } - - .prosemirror-links a { +} +.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 { +.prosemirror-links a:hover { background-color: #eee; color: black; - } +} - .prosemirror-anchor:hover .prosemirror-links { +.prosemirror-anchor:hover .prosemirror-links { display: grid; - } +} .ProseMirror { padding: 0px; @@ -334,7 +332,8 @@ footnote::after { border-left: solid 2px dimgray; } - ol, ul { + ol, + ul { counter-reset: deci1 0 multi1 0; padding-left: 1em; font-family: inherit; @@ -342,42 +341,231 @@ footnote::after { ol { font-family: inherit; } - .bullet { p { font-family: inherit} margin-left: 0; } - .bullet1 { p { font-family: inherit} } - .bullet2,.bullet3,.bullet4,.bullet5,.bullet6 { p { font-family: inherit} font-size: smaller; } + .bullet { + p { + font-family: inherit; + } + margin-left: 0; + } + .bullet1 { + p { + font-family: inherit; + } + } + .bullet2, + .bullet3, + .bullet4, + .bullet5, + .bullet6 { + p { + font-family: inherit; + } + font-size: smaller; + } - .decimal1-ol { counter-reset: deci1; p {display: inline-block; font-family: inherit} margin-left: 0; } - .decimal2-ol { counter-reset: deci2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.1em;} - .decimal3-ol { counter-reset: deci3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;} - .decimal4-ol { counter-reset: deci4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;} - .decimal5-ol { counter-reset: deci5; p {display: inline-block; font-family: inherit} font-size: smaller; } - .decimal6-ol { counter-reset: deci6; p {display: inline-block; font-family: inherit} font-size: smaller; } - .decimal7-ol { counter-reset: deci7; p {display: inline-block; font-family: inherit} font-size: smaller; } + .decimal1-ol { + counter-reset: deci1; + p { + display: inline-block; + font-family: inherit; + } + margin-left: 0; + } + .decimal2-ol { + counter-reset: deci2; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2.1em; + } + .decimal3-ol { + counter-reset: deci3; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2.85em; + } + .decimal4-ol { + counter-reset: deci4; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 3.85em; + } + .decimal5-ol { + counter-reset: deci5; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + } + .decimal6-ol { + counter-reset: deci6; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + } + .decimal7-ol { + counter-reset: deci7; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + } - .multi1-ol { counter-reset: multi1; p {display: inline-block; font-family: inherit} margin-left: 0; padding-left: 1.2em } - .multi2-ol { counter-reset: multi2; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2em;} - .multi3-ol { counter-reset: multi3; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 2.85em;} - .multi4-ol { counter-reset: multi4; p {display: inline-block; font-family: inherit} font-size: smaller; padding-left: 3.85em;} + .multi1-ol { + counter-reset: multi1; + p { + display: inline-block; + font-family: inherit; + } + margin-left: 0; + padding-left: 1.2em; + } + .multi2-ol { + counter-reset: multi2; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2em; + } + .multi3-ol { + counter-reset: multi3; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 2.85em; + } + .multi4-ol { + counter-reset: multi4; + p { + display: inline-block; + font-family: inherit; + } + font-size: smaller; + padding-left: 3.85em; + } //.bullet:before, .bullet1:before, .bullet2:before, .bullet3:before, .bullet4:before, .bullet5:before { transition: 0.5s; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content:" " } - .decimal1:before { transition: 0.5s;counter-increment: deci1; display: inline-block; vertical-align: top; margin-left: -1em; width: 1em; content: counter(deci1) ". "; } - .decimal2:before { transition: 0.5s;counter-increment: deci2; display: inline-block; vertical-align: top; margin-left: -2.1em; width: 2.1em; content: counter(deci1) "."counter(deci2) ". "; } - .decimal3:before { transition: 0.5s;counter-increment: deci3; display: inline-block; vertical-align: top; margin-left: -2.85em;width: 2.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) ". "; } - .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; vertical-align: top; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } - .decimal5:before { transition: 0.5s;counter-increment: deci5; display: inline-block; vertical-align: top; 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; vertical-align: top; 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; vertical-align: top; margin-left: -2em; width: 7em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) "."counter(deci5) "."counter(deci6) "."counter(deci7) ". "; } + .decimal1:before { + transition: 0.5s; + counter-increment: deci1; + display: inline-block; + vertical-align: top; + margin-left: -1em; + width: 1em; + content: counter(deci1) '. '; + } + .decimal2:before { + transition: 0.5s; + counter-increment: deci2; + display: inline-block; + vertical-align: top; + margin-left: -2.1em; + width: 2.1em; + content: counter(deci1) '.' counter(deci2) '. '; + } + .decimal3:before { + transition: 0.5s; + counter-increment: deci3; + display: inline-block; + vertical-align: top; + margin-left: -2.85em; + width: 2.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '. '; + } + .decimal4:before { + transition: 0.5s; + counter-increment: deci4; + display: inline-block; + vertical-align: top; + margin-left: -3.85em; + width: 3.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '. '; + } + .decimal5:before { + transition: 0.5s; + counter-increment: deci5; + display: inline-block; + vertical-align: top; + 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; + vertical-align: top; + 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; + vertical-align: top; + 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; vertical-align: top; margin-left: -1.3em; width: 1.2em; content: counter(multi1, upper-alpha) ". "; } - .multi2:before { transition: 0.5s;counter-increment: multi2; display: inline-block; vertical-align: top; margin-left: -2em; width: 2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) ". "; } - .multi3:before { transition: 0.5s;counter-increment: multi3; display: inline-block; vertical-align: top; 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; vertical-align: top; margin-left: -4.2em; width: 4.2em; content: counter(multi1, upper-alpha) "."counter(multi2, decimal) "."counter(multi3, lower-alpha) "."counter(multi4, lower-roman) ". "; } + .multi1:before { + transition: 0.5s; + counter-increment: multi1; + display: inline-block; + vertical-align: top; + margin-left: -1.3em; + width: 1.2em; + content: counter(multi1, upper-alpha) '. '; + } + .multi2:before { + transition: 0.5s; + counter-increment: multi2; + display: inline-block; + vertical-align: top; + margin-left: -2em; + width: 2em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '. '; + } + .multi3:before { + transition: 0.5s; + counter-increment: multi3; + display: inline-block; + vertical-align: top; + 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; + vertical-align: top; + margin-left: -4.2em; + width: 4.2em; + content: counter(multi1, upper-alpha) '.' counter(multi2, decimal) '.' counter(multi3, lower-alpha) '.' counter(multi4, lower-roman) '. '; + } } - @media only screen and (max-width: 1000px) { - @import "../../global/globalCssVariables"; + @import '../../global/globalCssVariables'; .ProseMirror { width: 100%; @@ -425,7 +613,7 @@ footnote::after { width: 100%; height: 100%; } - + .formattedTextBox-sidebar-handle { position: absolute; background: lightgray; @@ -562,18 +750,17 @@ footnote::after { position: absolute; top: -5px; left: 27px; - content: " "; + content: ' '; height: 0; width: 0; } - .formattedTextBox-inlineComment { position: relative; width: 40px; height: 20px; &::before { - content: "→"; + content: '→'; } &:hover { background: orange; @@ -586,7 +773,7 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "←"; + content: '←'; } } @@ -596,7 +783,7 @@ footnote::after { width: 40px; height: 20px; &::after { - content: "..."; + content: '...'; } } @@ -606,7 +793,8 @@ footnote::after { font-family: inherit; } - ol, ul { + ol, + ul { counter-reset: deci1 0 multi1 0; padding-left: 1em; font-family: inherit; @@ -616,30 +804,191 @@ footnote::after { 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;} - .decimal4-ol { counter-reset: deci4; p {display: inline; font-family: inherit} font-size: smaller; padding-left: 3em;} - .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) ". "; } - .decimal4:before { transition: 0.5s;counter-increment: deci4; display: inline-block; margin-left: -3.85em;width: 3.85em; content: counter(deci1) "."counter(deci2) "."counter(deci3) "."counter(deci4) ". "; } - .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) ". "; } + .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; + } + .decimal4-ol { + counter-reset: deci4; + p { + display: inline; + font-family: inherit; + } + font-size: smaller; + padding-left: 3em; + } + .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) '. '; + } + .decimal4:before { + transition: 0.5s; + counter-increment: deci4; + display: inline-block; + margin-left: -3.85em; + width: 3.85em; + content: counter(deci1) '.' counter(deci2) '.' counter(deci3) '.' counter(deci4) '. '; + } + .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) '. '; + } } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index cfaa428f9..5cb805f80 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -20,11 +20,11 @@ import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { ComputedField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; -import { DocumentType } from '../../../documents/DocumentTypes'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; @@ -35,6 +35,7 @@ import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; +import { CollectionTreeView } from '../../collections/CollectionTreeView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; @@ -44,6 +45,7 @@ import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; +import { DocumentViewInternal } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; import { DashDocCommentView } from './DashDocCommentView'; @@ -63,20 +65,12 @@ import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); const translateGoogleApi = require('translate-google-api'); -export interface FormattedTextBoxProps { - makeLink?: () => Opt<Doc>; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text - xPadding?: number; // used to override document's settings for xMargin --- see CollectionCarouselView - yPadding?: number; - noSidebar?: boolean; - dontScale?: boolean; - dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded (and mark as not being associated with scrollTop document field) -} export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps & FormattedTextBoxProps>() { +export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -123,6 +117,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return DocListCast(this.dataDoc[this.SidebarKey]); } + @computed get noSidebar() { + return this.props.docViewPath?.()[this.props.docViewPath().length - 2]?.rootDoc.type === DocumentType.RTF || this.props.noSidebar || this.Document._noSidebar; + } @computed get sidebarWidthPercent() { return this._showSidebar ? '20%' : StrCast(this.layoutDoc._sidebarWidthPercent, '0%'); } @@ -244,6 +241,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps !this.layoutDoc.showSidebar && this.toggleSidebar(); this._sidebarRef.current?.anchorMenuClick(this.getAnchor()); }; + AnchorMenu.Instance.OnAudio = (e: PointerEvent) => { + !this.layoutDoc.showSidebar && this.toggleSidebar(); + const anchor = this.getAnchor(); + const target = this._sidebarRef.current?.anchorMenuClick(anchor); + if (target) { + anchor.followLinkAudio = true; + DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target)); + target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`); + } + }; AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => { this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch); return undefined; @@ -377,9 +384,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; autoLink = () => { + const newAutoLinks = new Set<Doc>(); + const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords); if (this._editorView?.state.doc.textContent) { - const newAutoLinks = new Set<Doc>(); - const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords); const f = this._editorView.state.selection.from; const t = this._editorView.state.selection.to; var tr = this._editorView.state.tr as any; @@ -388,12 +395,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); - oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); } + oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); }; updateTitle = () => { - const title = StrCast(this.dataDoc.title); + const title = StrCast(this.dataDoc.title, Cast(this.dataDoc.title, RichTextField, null)?.Text); if ( !this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing (title.startsWith('-') || title.startsWith('@')) && @@ -421,28 +428,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { const autoLinkTerm = StrCast(target.title).replace(/^@/, ''); - const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; - flattened1.forEach((flat, i) => { - const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm); - this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; + this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => { const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); - const sel = flattened[i]; - tr = tr.addMark(sel.from, sel.to, splitter); - tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { - if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { - alink = - alink ?? - (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || - DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!); - newAutoLinks.add(alink); - const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; - allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); - const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' }); - tr = tr.addMark(pos, pos + node.nodeSize, link); - } - }); - tr = tr.removeMark(sel.from, sel.to, splitter); + if (!sel.$anchor.pos || editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim() === autoLinkTerm) { + tr = tr.addMark(sel.from, sel.to, splitter); + tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { + if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { + alink = + alink ?? + (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) || + DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!); + newAutoLinks.add(alink); + const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; + allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); + const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' }); + tr = tr.addMark(pos, pos + node.nodeSize, link); + } + }); + tr = tr.removeMark(sel.from, sel.to, splitter); + } }); } return tr; @@ -633,18 +638,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @action toggleSidebar = (preview: boolean = false) => { - const prevWidth = this.sidebarWidth(); + const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', '')); if (preview) this._showSidebar = true; else this.layoutDoc._showSidebar = (this.layoutDoc._sidebarWidthPercent = StrCast(this.layoutDoc._sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; - this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth); + this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); }; sidebarDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), false); + const batch = UndoManager.StartBatch('sidebar'); + setupMoveUpEvents( + this, + e, + this.sidebarMove, + (e, movement, isClick) => !isClick && batch.end(), + () => { + this.toggleSidebar(); + batch.end(); + }, + true + ); }; sidebarMove = (e: PointerEvent, down: number[], delta: number[]) => { - const bounds = this._ref.current!.getBoundingClientRect(); - this.layoutDoc._sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%'; + const localDelta = this.props + .ScreenToLocalTransform() + .scale(this.props.NativeDimScaling?.() || 1) + .transformDirection(delta[0], delta[1]); + const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.sidebarWidthPercent.replace('%', ''))) / 100; + const width = this.layoutDoc[WidthSym]() + localDelta[0]; + this.layoutDoc._sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%'; + this.layoutDoc.width = width; this.layoutDoc._showSidebar = this.layoutDoc._sidebarWidthPercent !== '0%'; e.preventDefault(); return false; @@ -672,9 +694,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); - let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> + let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> while (target && !target.dataset?.targethrefs) target = target.parentElement; - if (target) { + if (target && !(e.nativeEvent as any).dash) { const hrefs = (target.dataset?.targethrefs as string) ?.trim() .split(' ') @@ -693,6 +715,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); }) ); + e.preventDefault(); e.stopPropagation(); return; } @@ -751,7 +774,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const uicontrols: ContextMenuProps[] = []; !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' }); uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' }); - uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' }); uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && uicontrols.push({ @@ -833,23 +855,28 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps setDictationContent = (value: string) => { if (this._editorView && this._recordingStart) { if (this._break) { - const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' }); - this.addDocument(textanchor); - const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement(); - link && (Doc.GetProto(link).isDictation = true); - if (!link) return; - const audioanchor = Cast(link.anchor2, Doc, null); - if (!audioanchor) return; - audioanchor.backgroundColor = 'tan'; - const audiotag = this._editorView.state.schema.nodes.audiotag.create({ - timeCode: NumCast(audioanchor._timecodeToShow), - audioId: audioanchor[Id], - textId: textanchor[Id], - }); - Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode; - const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag); - const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size)); - this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size))); + const textanchorFunc = () => { + const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' }); + return this.addDocument(tanch) ? tanch : undefined; + }; + const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement(); + if (link) { + Doc.GetProto(link).isDictation = true; + const audioanchor = Cast(link.anchor2, Doc, null); + const textanchor = Cast(link.anchor1, Doc, null); + if (audioanchor) { + audioanchor.backgroundColor = 'tan'; + const audiotag = this._editorView.state.schema.nodes.audiotag.create({ + timeCode: NumCast(audioanchor._timecodeToShow), + audioId: audioanchor[Id], + textId: textanchor[Id], + }); + Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode; + const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag); + const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size)); + this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size))); + } + } } const from = this._editorView.state.selection.from; this._break = false; @@ -957,7 +984,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; @computed get contentScaling() { - return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1; + return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.NativeDimScaling?.() || 1 : 1; } componentDidMount() { !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. @@ -978,7 +1005,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { - autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight))); + const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); + if (autoHeight && newHeight && newHeight !== this.rootDoc.height) { + this.props.setHeight?.(newHeight); + } }, { fireImmediately: true } ); @@ -1357,10 +1387,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } selectOnLoad && this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. - if (this._editorView && !this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark)) { + if (this._editorView) { this._editorView.state.storedMarks = [ ...(this._editorView.state.storedMarks ?? []), - schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }), + ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []), ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), @@ -1436,14 +1466,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps document.removeEventListener('pointermove', this.onSelectMove); }; onPointerUp = (e: React.PointerEvent): void => { - if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate) this.setupAnchorMenu(); + if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; - if (this.props.isContentActive(true)) { + if (this.props.isContentActive(true) && !(e.nativeEvent as any).dash) { const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); - let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> + let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); } @@ -1479,7 +1509,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onFocused = (e: React.FocusEvent): void => { //applyDevTools.applyDevTools(this._editorView); FormattedTextBox.Focused = this; - this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); + this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); }; @observable public static Focused: FormattedTextBox | undefined; @@ -1567,6 +1597,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @action onBlur = (e: any) => { + if (this.ProseRef?.children[0] !== e.nativeEvent.target) return; this.autoLink(); FormattedTextBox.Focused === this && (FormattedTextBox.Focused = undefined); if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { @@ -1666,12 +1697,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (children) { - const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')), margins); + const proseHeight = !this.ProseRef + ? 0 + : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace('px', '')) + Number(getComputedStyle(child).marginTop.replace('px', '')) + Number(getComputedStyle(child).marginBottom.replace('px', '')), margins); const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation const setScrollHeight = () => (this.rootDoc[this.fieldKey + '-scrollHeight'] = scrollHeight); - if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { + if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); } else { setTimeout(setScrollHeight, 10); // 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... @@ -1680,8 +1713,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } }; fitContentsToBox = () => this.props.Document._fitContentsToBox; - sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); - sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { + sidebarContentScaling = () => (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + sidebarAddDocument = (doc: Doc | Doc[], sidebarKey: string = this.SidebarKey) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); // console.log("printting allSideBarDocs"); // console.log(this.allSidebarDocs); @@ -1694,13 +1727,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps sidebarScreenToLocal = () => this.props .ScreenToLocalTransform() - .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.scaling?.() || 1), 0) - .scale(1 / NumCast(this.layoutDoc._viewScale, 1)); + .translate(-(this.props.PanelWidth() - this.sidebarWidth()) / (this.props.NativeDimScaling?.() || 1), 0) + .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1)); @computed get audioHandle() { - return ( - <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}> - <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" /> + return !this._recording ? null : ( + <div + className="formattedTextBox-dictation" + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + action(e => (this._recording = !this._recording)) + ) + }> + <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" /> </div> ); } @@ -1715,7 +1758,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} style={{ - left: `max(0px, calc(100% - ${this.sidebarWidthPercent} - 17px))`, backgroundColor: backgroundColor, color: color, opacity: annotated ? 1 : undefined, @@ -1726,7 +1768,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } @computed get sidebarCollection() { const renderComponent = (tag: string) => { - const ComponentTag = tag === 'freeform' ? CollectionFreeFormView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; + const ComponentTag = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; return ComponentTag === CollectionStackingView ? ( <SidebarAnnos ref={this._sidebarRef} @@ -1735,7 +1777,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} - // usePanelWidth={true} + ScreenToLocalTransform={this.sidebarScreenToLocal} nativeWidth={NumCast(this.layoutDoc._nativeWidth)} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} showSidebar={this.SidebarShown} @@ -1746,30 +1788,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps removeDocument={this.removeDocument} /> ) : ( - <ComponentTag - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} - NativeWidth={returnZero} - NativeHeight={returnZero} - PanelHeight={this.props.PanelHeight} - PanelWidth={this.sidebarWidth} - xPadding={0} - yPadding={0} - scaleField={this.SidebarKey + '-scale'} - isAnnotationOverlay={false} - select={emptyFunction} - scaling={this.sidebarContentScaling} - whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} - removeDocument={this.sidebarRemDocument} - moveDocument={this.sidebarMoveDocument} - addDocument={this.sidebarAddDocument} - CollectionView={undefined} - ScreenToLocalTransform={this.sidebarScreenToLocal} - renderDepth={this.props.renderDepth + 1} - setHeight={this.setSidebarHeight} - fitContentsToBox={this.fitContentsToBox} - noSidebar={true} - fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} - /> + <div onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.props.DocumentView?.()!, false), true)}> + <ComponentTag + {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} + NativeWidth={returnZero} + NativeHeight={returnZero} + PanelHeight={this.props.PanelHeight} + PanelWidth={this.sidebarWidth} + xPadding={0} + yPadding={0} + scaleField={this.SidebarKey + '-scale'} + isAnnotationOverlay={false} + select={emptyFunction} + NativeDimScaling={this.sidebarContentScaling} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + removeDocument={this.sidebarRemDocument} + moveDocument={this.sidebarMoveDocument} + addDocument={this.sidebarAddDocument} + CollectionView={undefined} + ScreenToLocalTransform={this.sidebarScreenToLocal} + renderDepth={this.props.renderDepth + 1} + setHeight={this.setSidebarHeight} + fitContentsToBox={this.fitContentsToBox} + noSidebar={true} + treeViewHideTitle={true} + fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}-translation` : `${this.fieldKey}-sidebar`} + /> + </div> ); }; return ( @@ -1782,7 +1827,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps TraceMobx(); const active = this.props.isContentActive() || this.props.isSelected(); const selected = active; - const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : ''; const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); @@ -1791,18 +1836,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const selPad = (selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0; const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? '-selected' : ''; - const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > - return styleFromString?.height === '0px' ? null : ( + const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > + return styleFromLayoutString?.height === '0px' ? null : ( <div className="formattedTextBox-cont" onWheel={e => this.props.isContentActive() && e.stopPropagation()} style={{ - transform: this.props.dontScale ? undefined : `scale(${scale})`, - transformOrigin: this.props.dontScale ? undefined : 'top left', - width: this.props.dontScale ? undefined : `${100 / scale}%`, - height: this.props.dontScale ? undefined : `${100 / scale}%`, + ...(this.props.dontScale + ? {} + : { + transform: `scale(${scale})`, + transformOrigin: 'top left', + width: `${100 / scale}%`, + height: `${100 / scale}%`, + }), + transition: 'inherit', // overflowY: this.layoutDoc._autoHeight ? "hidden" : undefined, - ...styleFromString, + color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), + fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), + fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily), + fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight), + ...styleFromLayoutString, }}> <div className={`formattedTextBox-cont`} @@ -1810,11 +1864,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps style={{ overflow: this.autoHeight ? 'hidden' : undefined, height: this.props.height || (this.autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined), - background: this.props.background ? this.props.background : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), - color: this.props.color ? this.props.color : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), - fontSize: this.props.fontSize ? this.props.fontSize : this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), - fontWeight: Cast(this.layoutDoc._fontWeight, 'string', null) as any, - fontFamily: StrCast(this.layoutDoc._fontFamily, 'inherit'), pointerEvents: interactive ? undefined : 'none', }} onContextMenu={this.specificContextMenu} @@ -1832,7 +1881,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps style={{ width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined, - overflow: this.layoutDoc._singleLine ? 'hidden' : undefined, + overflow: this.layoutDoc._singleLine ? 'hidden' : this.layoutDoc._autoHeight ? 'visible' : undefined, }} onScroll={this.onScroll} onDrop={this.ondrop}> @@ -1849,9 +1898,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }} /> </div> - {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} - {this.props.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} - {!this.layoutDoc._showAudio ? null : this.audioHandle} + {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection} + {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle} + {this.audioHandle} </div> </div> ); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 22ca76b2e..6c6d26af5 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -16,7 +16,7 @@ import { SelectionManager } from '../../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { FieldViewProps } from '../FieldView'; -import { FormattedTextBox, FormattedTextBoxProps } from './FormattedTextBox'; +import { FormattedTextBox } from './FormattedTextBox'; import { updateBullets } from './ProsemirrorExampleTransfer'; import './RichTextMenu.scss'; import { schema } from './schema_rts'; @@ -29,7 +29,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { private _linkToRef = React.createRef<HTMLInputElement>(); @observable public view?: EditorView; - public editorProps: (FieldViewProps & FormattedTextBoxProps) | undefined; + public editorProps: FieldViewProps | undefined; public _brushMap: Map<string, Set<Mark>> = new Map(); @@ -60,26 +60,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { @observable private showLinkDropdown: boolean = false; _reaction: IReactionDisposer | undefined; - _delayHide = false; constructor(props: Readonly<{}>) { super(props); runInAction(() => { RichTextMenu.Instance = this; + this.updateMenu(undefined, undefined, props); this._canFade = false; this.Pinned = true; }); } - componentDidMount() { - this._reaction = reaction( - () => SelectionManager.Views(), - () => this._delayHide && !(this._delayHide = false) && this.fadeOut(true) - ); - } - componentWillUnmount() { - this._reaction?.(); - } - @computed get noAutoLink() { return this._noLinkActive; } @@ -108,21 +98,18 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { return this._activeAlignment; } - public delayHide = () => (this._delayHide = true); - @action public updateMenu(view: EditorView | undefined, lastState: EditorState | undefined, props: any) { if (this._linkToRef.current?.getBoundingClientRect().width) { return; } this.view = view; - if (!view || !view.hasFocus()) { - return; - } props && (this.editorProps = props); // Don't do anything if the document/selection didn't change - if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; + if (view && view.hasFocus()) { + if (lastState?.doc.eq(view.state.doc) && lastState.selection.eq(view.state.selection)) return; + } // update active marks const activeMarks = this.getActiveMarksOnSelection(); @@ -137,9 +124,9 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? 'Arial' : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; - this._activeFontSize = !activeSizes.length ? StrCast(this.TextView.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; - this._activeFontColor = !activeColors.length ? 'black' : activeColors.length > 0 ? String(activeColors[0]) : '...'; + this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; + this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; this.activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; // update link in current selection @@ -157,13 +144,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { 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 if (dontToggle) { - toggleMark(mark.type, mark.attrs)(state, (tx: any) => { - const { from, $from, to, empty } = tx.selection; - if (!tx.doc.rangeHasMark(from, to, mark.type)) { - // hack -- should have just set the mark in the first place - toggleMark(mark.type, mark.attrs)({ tr: tx, doc: tx.doc, selection: tx.selection, storedMarks: tx.storedMarks }, dispatch); - } else dispatch(tx); - }); + const tr = state.tr.addMark(state.selection.from, state.selection.to, mark); + dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(state.selection.from), tr.doc.resolve(state.selection.to)))); // bcz: need to redo the selection because ctrl-a selections disappear otherwise } else { toggleMark(mark.type, mark.attrs)(state, dispatch); } @@ -172,7 +154,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveAlignment() { - if (this.view && this.TextView.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected(true)) { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { @@ -185,7 +167,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveListStyle() { - if (this.view && this.TextView.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected(true)) { const path = (this.view.state.selection.$from as any).path; for (let i = 0; i < path.length; i += 3) { if (path[i].type === this.view.state.schema.nodes.ordered_list) { @@ -201,17 +183,16 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveFontStylesOnSelection() { - if (!this.view) return { activeFamilies: [], activeSizes: [], activeColors: [], activeHighlights: [] }; - - const activeFamilies: string[] = []; - const activeSizes: string[] = []; - const activeColors: string[] = []; - const activeHighlights: string[] = []; - if (this.TextView.props.isSelected(true)) { + const activeFamilies = new Set<string>(); + const activeSizes = new Set<string>(); + const activeColors = new Set<string>(); + const activeHighlights = new Set<string>(); + if (this.view && this.TextView?.props.isSelected(true)) { const state = this.view.state; const pos = this.view.state.selection.$from; const marks: Mark[] = [...(state.storedMarks ?? [])]; - if (state.selection.empty) { + if (state.storedMarks !== null) { + } else if (state.selection.empty) { const ref_node = this.reference_node(pos); marks.push(...(ref_node !== this.view.state.doc && ref_node?.isText ? Array.from(ref_node.marks) : [])); } else { @@ -220,13 +201,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { }); } marks.forEach(m => { - m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family); - m.type === state.schema.marks.pFontColor && activeColors.push(m.attrs.color); - m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize); - m.type === state.schema.marks.marker && activeHighlights.push(String(m.attrs.highlight)); + m.type === state.schema.marks.pFontFamily && activeFamilies.add(m.attrs.family); + m.type === state.schema.marks.pFontColor && activeColors.add(m.attrs.color); + m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize); + m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight)); }); } - return { activeFamilies, activeSizes, activeColors, activeHighlights }; + return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) }; } getMarksInSelection(state: EditorState) { @@ -239,7 +220,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { //finds all active marks on selection in given group getActiveMarksOnSelection() { let activeMarks: MarkType[] = []; - if (!this.view || !this.TextView.props.isSelected(true)) return activeMarks; + if (!this.view || !this.TextView?.props.isSelected(true)) return activeMarks; const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); @@ -292,28 +273,15 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this._superscriptActive = false; activeMarks.forEach(mark => { + // prettier-ignore switch (mark.name) { - case 'noAutoLinkAnchor': - this._noLinkActive = true; - break; - case 'strong': - this._boldActive = true; - break; - case 'em': - this._italicsActive = true; - break; - case 'underline': - this._underlineActive = true; - break; - case 'strikethrough': - this._strikethroughActive = true; - break; - case 'subscript': - this._subscriptActive = true; - break; - case 'superscript': - this._superscriptActive = true; - break; + case 'noAutoLinkAnchor': this._noLinkActive = true; break; + case 'strong': this._boldActive = true; break; + case 'em': this._italicsActive = true; break; + case 'underline': this._underlineActive = true; break; + case 'strikethrough': this._strikethroughActive = true; break; + case 'subscript': this._subscriptActive = true; break; + case 'superscript': this._superscriptActive = true; break; } }); } @@ -355,14 +323,13 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { if (this.view.state.selection.from === 1 && this.view.state.selection.empty && (!this.view.state.doc.nodeAt(1) || !this.view.state.doc.nodeAt(1)?.marks.some(m => m.type.name === fontSize))) { this.TextView.dataDoc.fontSize = fontSize; this.view.focus(); - this.updateMenu(this.view, undefined, this.props); } else { const fmark = this.view.state.schema.marks.pFontSize.create({ fontSize }); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); - this.updateMenu(this.view, undefined, this.props); } - } + } else Doc.UserDoc()._fontSize = fontSize; + this.updateMenu(this.view, undefined, this.props); }; setFontFamily = (family: string) => { @@ -370,8 +337,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family }); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); - this.updateMenu(this.view, undefined, this.props); - } + } else Doc.UserDoc()._fontFamily = family; + this.updateMenu(this.view, undefined, this.props); }; setHighlight(color: String, view: EditorView, dispatch: any) { @@ -381,20 +348,20 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.setMark(highlightMark, view.state, dispatch, false); } - setColor(color: String, view: EditorView, dispatch: any) { + setColor(color: string) { if (this.view) { - const colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color }); + const colorMark = this.view.state.schema.mark(this.view.state.schema.marks.pFontColor, { color }); this.setMark(colorMark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(colorMark)), true); - view.focus(); - this.updateMenu(this.view, undefined, this.props); - } + this.view.focus(); + } else Doc.UserDoc().fontColor = color; + this.updateMenu(this.view, undefined, this.props); } // TODO: remove doesn't work // remove all node type and apply the passed-in one to the selected text changeListType = (mapStyle: string) => { const active = this.view?.state && RichTextMenu.Instance.getActiveListStyle(); - const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? "" : mapStyle }); + const nodeType = this.view?.state.schema.nodes.ordered_list.create({ mapStyle: active === mapStyle ? '' : mapStyle }); if (!this.view || nodeType?.attrs.mapStyle === '') return; const nextIsOL = this.view.state.selection.$from.nodeAfter?.type === schema.nodes.ordered_list; @@ -443,7 +410,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { - if (this.TextView.props.isSelected(true)) { + if (this.TextView?.props.isSelected(true)) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 1916b94bf..2eb62c38d 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -80,7 +80,7 @@ export class RichTextRules { textDoc.inlineTextCount = numInlines + 1; const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); + const textDocInline = Docs.Create.TextDocument('', { _layoutKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _fitWidth: true, _autoHeight: true, _fontSize: '9px', title: 'inline comment' }); textDocInline.title = inlineFieldKey; // give the annotation its own title textDocInline['title-custom'] = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point @@ -343,8 +343,14 @@ export class RichTextRules { const node = (state.doc.resolve(start) as any).nodeAfter; if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_tag) !== -1) return state.tr.removeMark(start, end, schema.marks.user_tag); - - return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + if (node?.marks.findIndex((m: any) => m.type === schema.marks.user_mark) !== -1) { + } + return node + ? state.tr + .removeMark(start, end, schema.marks.user_mark) + .addMark(start, end, schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })) + .addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: tag, modified: Math.round(Date.now() / 1000 / 60) })) + : state.tr; }), new InputRule(new RegExp(/%\(/), (state, match, start, end) => { |
