From dc942e6228e003caa3754a72c0e126d64332a004 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Nov 2022 18:29:11 -0400 Subject: fixes for goldenlayout to initialize more cleanly. updated all old ReactDOM.render() to ReactDom.createRoot(). fixes for PDF/Web sidebar sizing. added text from pdf selection anchors to sidebar notes. fixed PDF text selection to align properly. --- src/client/views/SidebarAnnos.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/client/views/SidebarAnnos.tsx') diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 12f41394d..df1eb72ce 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -15,6 +15,7 @@ import { SearchBox } from './search/SearchBox'; import './SidebarAnnos.scss'; import { StyleProp } from './StyleProvider'; import React = require('react'); +import { RichTextField } from '../../fields/RichTextField'; interface ExtraProps { fieldKey: string; @@ -72,6 +73,27 @@ export class SidebarAnnos extends React.Component { FormattedTextBox.DontSelectInitialText = true; this.allMetadata.map(tag => (target[tag] = tag)); DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); + + Doc.GetProto(target).text = new RichTextField( + JSON.stringify({ + doc: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false } }], + }, + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + }, + ], + }, + selection: { type: 'text', anchor: 4, head: 4 }, + }), + '' + ); this.addDocument(target); this._stackRef.current?.focusDocument(target, {}); return target; -- cgit v1.2.3-70-g09d2 From 1a8024dce302acc1637cd1f7d74b0238c5979c15 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 2 Nov 2022 10:42:25 -0400 Subject: select docs when title header is edited. fixed doc decoration top border. sort sidebar annos by y coord --- src/client/views/DocumentDecorations.scss | 4 +++- src/client/views/SidebarAnnos.tsx | 9 ++++++++- src/client/views/collections/CollectionStackingView.tsx | 12 ++++++++---- src/client/views/nodes/DocumentView.tsx | 5 ++++- 4 files changed, 23 insertions(+), 7 deletions(-) (limited to 'src/client/views/SidebarAnnos.tsx') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index b41962c73..ccac5ffe4 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,7 +1,7 @@ @import 'global/globalCssVariables'; $linkGap: 3px; -$headerHeight: 25px; +$headerHeight: 20px; $resizeHandler: 8px; .documentDecorations-Dark, @@ -424,7 +424,9 @@ $resizeHandler: 8px; background: $light-gray; opacity: 0.2; pointer-events: all; + transition: opacity 1s; &:hover { + transition: unset; opacity: 1; } } diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index df1eb72ce..869caabd1 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import { Doc, DocListCast, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; -import { NumCast, StrCast } from '../../fields/Types'; +import { DocCast, NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; @@ -66,6 +66,7 @@ export class SidebarAnnos extends React.Component { _height: 50, _fitWidth: true, _autoHeight: true, + _isLinkButton: true, _fontSize: StrCast(Doc.UserDoc().fontSize), _fontFamily: StrCast(Doc.UserDoc().fontFamily), }); @@ -133,6 +134,11 @@ export class SidebarAnnos extends React.Component { docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])]; showTitle = () => 'title'; setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight()); + sortByLinkAnchorY = (a: Doc, b: Doc) => { + const ay = DocListCast(a.links).length && DocCast(DocListCast(a.links)[0].anchor1).y; + const by = DocListCast(b.links).length && DocCast(DocListCast(b.links)[0].anchor1).y; + return NumCast(ay) - NumCast(by); + }; render() { const renderTag = (tag: string) => { const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`); @@ -184,6 +190,7 @@ export class SidebarAnnos extends React.Component { PanelWidth={this.panelWidth} docFilters={this.docFilters} scaleField={this.sidebarKey + '-scale'} + sortFunc={this.sortByLinkAnchorY} setHeight={this.setHeightCallback} isAnnotationOverlay={false} select={emptyFunction} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 77b47ed82..175051d5c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -3,12 +3,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CursorProperty } from 'csstype'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; @@ -33,6 +33,7 @@ import { CollectionSubView } from './CollectionSubView'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { + sortFunc?: (a: Doc, b: Doc) => number; chromeHidden?: boolean; // view type is stacking viewType?: CollectionViewType; @@ -73,7 +74,9 @@ export class CollectionStackingView extends CollectionSubView pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + const children = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); + if (this.props.sortFunc) children.sort(this.props.sortFunc); + return children; } // how much margin we give the header @computed get headerMargin() { @@ -305,6 +308,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)) ? true : this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined; + isChildButtonContentActive = () => (this.props.childDocumentsActive?.() === false || this.rootDoc.childDocumentsActive === false ? false : undefined); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, width: () => number) { @@ -326,7 +330,7 @@ export class CollectionStackingView extends CollectionSubView (showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle)} + GetValue={() => { + this.props.select(false); + return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle; + }} SetValue={undoBatch((input: string) => { if (input?.startsWith('#')) { if (this.props.showTitle) { -- cgit v1.2.3-70-g09d2 From b171b939743de6126f3fb5ca1e2d34bba7cbe428 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 2 Nov 2022 12:54:18 -0400 Subject: fix sidebar so active filters add proper metadata to newly created documents --- src/client/views/SidebarAnnos.tsx | 72 ++++++++++++++-------- src/client/views/nodes/DocumentView.tsx | 1 + .../views/nodes/formattedText/FormattedTextBox.tsx | 5 +- 3 files changed, 52 insertions(+), 26 deletions(-) (limited to 'src/client/views/SidebarAnnos.tsx') diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 869caabd1..78480da03 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -1,6 +1,6 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, DocListCast, StrListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { DocCast, NumCast, StrCast } from '../../fields/Types'; @@ -39,12 +39,13 @@ export class SidebarAnnos extends React.Component { } _stackRef = React.createRef(); @computed get allMetadata() { - const keys = new Set(); - DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys.keys()) - .filter(key => key[0]) - .filter(key => key[0] !== '_' && key[0] === key[0].toUpperCase()) - .sort(); + const keys = new Map>(); + DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => + SearchBox.documentKeys(doc) + .filter(key => key[0] && key[0] !== '_' && key[0] === key[0].toUpperCase()) + .map(key => keys.set(key, doc[key])) + ); + return keys; } @computed get allUsers() { const keys = new Set(); @@ -72,26 +73,47 @@ export class SidebarAnnos extends React.Component { }); FormattedTextBox.SelectOnLoad = target[Id]; FormattedTextBox.DontSelectInitialText = true; - this.allMetadata.map(tag => (target[tag] = tag)); DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); + const taggedContent = this.docFilters() + .filter(data => data.split(':')[0]) + .map(data => { + const key = data.split(':')[0]; + const val = Field.Copy(this.allMetadata.get(key)); + Doc.GetProto(target)[key] = val; + return { + type: 'dashField', + attrs: { fieldKey: key, docid: '', hideKey: false, editable: true }, + marks: [ + { type: 'pFontSize', attrs: { fontSize: '12px' } }, + { type: 'pFontFamily', attrs: { family: 'Arial' } }, + { type: 'pFontColor', attrs: { color: 'black' } }, + { type: 'strong' }, + { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }, + ], + }; + }); + const textLines = [ + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false } }], + }, + { type: 'paragraph', attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null } }, + ]; + const metadatatext = { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: taggedContent, + }; + if (taggedContent.length) textLines.push(metadatatext); Doc.GetProto(target).text = new RichTextField( JSON.stringify({ doc: { type: 'doc', - content: [ - { - type: 'paragraph', - attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false } }], - }, - { - type: 'paragraph', - attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - }, - ], + content: textLines, }, - selection: { type: 'text', anchor: 4, head: 4 }, + selection: { type: 'text', anchor: 4, head: 4 }, // set selection to middle paragraph }), '' ); @@ -148,10 +170,10 @@ export class SidebarAnnos extends React.Component { ); }; - const renderMeta = (tag: string) => { - const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:exists`); + const renderMeta = (tag: string, dflt: FieldResult) => { + const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${dflt}:exists`); return ( -
Doc.setDocFilter(this.props.rootDoc, tag, tag, 'exists', true, this.sidebarKey, e.shiftKey)}> +
Doc.setDocFilter(this.props.rootDoc, tag, dflt, 'exists', true, this.sidebarKey, e.shiftKey)}> {tag}
); @@ -178,7 +200,9 @@ export class SidebarAnnos extends React.Component { }}>
e.stopPropagation()}> {this.allUsers.map(renderUsers)} - {this.allMetadata.map(renderMeta)} + {Array.from(this.allMetadata.keys()) + .sort() + .map(key => renderMeta(key, this.allMetadata.get(key)))}
0)) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 0e48dd93a..ee97e5e62 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1398,9 +1398,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent selectOnLoad && this._editorView!.focus(), 0); + else selectOnLoad && this._editorView!.focus(); + FormattedTextBox.DontSelectInitialText = false; // 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 = [ -- cgit v1.2.3-70-g09d2 From fe98c7d46df1852a74cd84dbe9ad010bfb3d5550 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 4 Nov 2022 12:43:59 -0400 Subject: more fixes to pdf and text to allow dashfieldview nodes to be link anchors and make sidebar annotations work better. --- src/client/views/SidebarAnnos.tsx | 34 +++++++---- src/client/views/linking/LinkMenuItem.tsx | 1 + src/client/views/nodes/LinkAnchorBox.tsx | 1 + src/client/views/nodes/LinkDocPreview.tsx | 2 + .../views/nodes/formattedText/DashFieldView.scss | 9 ++- .../views/nodes/formattedText/DashFieldView.tsx | 65 ++++++++-------------- .../views/nodes/formattedText/FormattedTextBox.tsx | 7 ++- .../formattedText/FormattedTextBoxComment.tsx | 14 +++-- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/nodes/formattedText/marks_rts.ts | 4 +- 10 files changed, 71 insertions(+), 68 deletions(-) (limited to 'src/client/views/SidebarAnnos.tsx') diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 78480da03..c285b2e34 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -67,7 +67,6 @@ export class SidebarAnnos extends React.Component { _height: 50, _fitWidth: true, _autoHeight: true, - _isLinkButton: true, _fontSize: StrCast(Doc.UserDoc().fontSize), _fontFamily: StrCast(Doc.UserDoc().fontFamily), }); @@ -84,20 +83,33 @@ export class SidebarAnnos extends React.Component { return { type: 'dashField', attrs: { fieldKey: key, docid: '', hideKey: false, editable: true }, - marks: [ - { type: 'pFontSize', attrs: { fontSize: '12px' } }, - { type: 'pFontFamily', attrs: { family: 'Arial' } }, - { type: 'pFontColor', attrs: { color: 'black' } }, - { type: 'strong' }, - { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }, - ], + marks: [{ type: 'pFontSize', attrs: { fontSize: '12px' } }, { type: 'strong' }, { type: 'user_mark', attrs: { userid: Doc.CurrentUserEmail, modified: 0 } }], }; }); - const textLines = [ + + const textLines: any = [ { type: 'paragraph', attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false } }], + content: [ + { + type: 'dashField', + marks: [ + { + type: 'linkAnchor', + attrs: { + allAnchors: [{ href: `/doc/${target[Id]}`, title: 'Anchored Selection', noPreview: true, anchorId: `${target[Id]}` }], + location: 'add:right', + title: 'Anchored Selection', + docref: false, + }, + }, + { type: 'pFontSize', attrs: { fontSize: '8px' } }, + { type: 'em' }, + ], + attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false }, + }, + ], }, { type: 'paragraph', attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null } }, ]; @@ -118,7 +130,7 @@ export class SidebarAnnos extends React.Component { '' ); this.addDocument(target); - this._stackRef.current?.focusDocument(target, {}); + setTimeout(() => this._stackRef.current?.focusDocument(target, {})); return target; }; makeDocUnfiltered = (doc: Doc) => { diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 3f9db2612..387e0e3d5 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -155,6 +155,7 @@ export class LinkMenuItem extends React.Component { linkDoc: this.props.linkDoc, showHeader: false, location: [this._drag.current?.getBoundingClientRect().right ?? 100, this._drag.current?.getBoundingClientRect().top ?? e.clientY], + noPreview: false, }) } onPointerDown={this.onLinkButtonDown}> diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 5102eae51..d6cf79f87 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -144,6 +144,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { linkDoc: this.rootDoc, showHeader: true, location: [e.clientX, e.clientY + 20], + noPreview: false, }) } onPointerDown={this.onPointerDown} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 27e79a83b..0e66214d1 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -26,6 +26,7 @@ interface LinkDocPreviewProps { location: number[]; hrefs?: string[]; showHeader?: boolean; + noPreview?: boolean; } @observer export class LinkDocPreview extends React.Component { @@ -111,6 +112,7 @@ export class LinkDocPreview extends React.Component { this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } this._toolTipText = ''; + if (LinkDocPreview.LinkInfo?.noPreview) this.followLink(); } }) ); diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index c36e6804b..f17579853 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -1,3 +1,5 @@ +@import '../../global/globalCssVariables'; + .dashFieldView { position: relative; display: inline-flex; @@ -22,7 +24,7 @@ position: relative; display: inline-block; font-weight: normal; - background: rgba(0,0,0,0.1); + background: rgba(0, 0, 0, 0.1); } .dashFieldView-fieldSpan { min-width: 8px; @@ -31,11 +33,12 @@ padding-left: 2px; display: inline-block; background-color: rgba(155, 155, 155, 0.24); - font-weight: bold; span { min-width: 100%; display: inline-block; } } } - \ No newline at end of file +.ProseMirror-selectedNode { + outline: solid 1px $light-blue !important; +} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index f088b39d1..fbcf25215 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -16,21 +16,20 @@ import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { Tooltip } from '@material-ui/core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { NodeSelection } from 'prosemirror-state'; +import { FormattedTextBoxComment } from './FormattedTextBoxComment'; export class DashFieldView { dom: HTMLDivElement; // container for label and value root: any; constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { - const { boolVal, strVal } = DashFieldViewInternal.fieldContent(tbox.props.Document, tbox.rootDoc, node.attrs.fieldKey); - this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; this.dom.style.height = node.attrs.height; - this.dom.style.fontWeight = 'bold'; this.dom.style.position = 'relative'; this.dom.style.display = 'inline-block'; - this.dom.textContent = node.attrs.fieldKey.startsWith('#') ? node.attrs.fieldKey : node.attrs.fieldKey + ' ' + strVal; + //this.dom.contentEditable = 'true'; this.dom.onkeypress = function (e: any) { e.stopPropagation(); }; @@ -45,12 +44,20 @@ export class DashFieldView { }; this.root = ReactDOM.createRoot(this.dom); - this.root.render(); + this.root.render( + + ); } destroy() { //this.root.unmount(); } - selectNode() {} + deselectNode() { + this.dom.classList.remove('ProseMirror-selectednode'); + } + selectNode() { + this.dom.classList.add('ProseMirror-selectednode'); + console.log('SELECT'); + } } interface IDashFieldViewInternal { @@ -61,6 +68,8 @@ interface IDashFieldViewInternal { width: number; height: number; editable: boolean; + node: any; + getPos: any; } @observer @@ -127,7 +136,13 @@ export class DashFieldViewInternal extends React.Component r && this.updateText(r.textContent!, false)); r?.addEventListener( 'pointerdown', - action(e => e.stopPropagation()) + action(e => { + // let target = e.target as any; // hrefs are stored on the dataset of the node that wraps the hyerlink + // while (target && !target.dataset?.targethrefs) target = target.parentElement; + this.props.tbox.EditorView!.dispatch(this.props.tbox.EditorView!.state.tr.setSelection(new NodeSelection(this.props.tbox.EditorView!.state.doc.resolve(this.props.getPos())))); + // FormattedTextBoxComment.update(this.props.tbox, this.props.tbox.EditorView!, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); + // e.stopPropagation(); + }) ); }}> {strVal} @@ -162,42 +177,6 @@ export class DashFieldViewInternal extends React.Component { if (nodeText) { const newText = nodeText.startsWith(':=') || nodeText.startsWith('=:=') ? ':=-computed-' : nodeText; - - // const json = { - // doc: { - // type: 'doc', - // content: [ - // { - // type: 'paragraph', - // attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - // content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: '7e02a420-8add-49b0-ad20-54680567575f', hideKey: true, editable: false }, marks: [{ type: 'strong' }] }], - // }, - // { - // type: 'paragraph', - // attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - // content: [{ type: 'text', marks: [{ type: 'user_mark', attrs: { userid: 'mart@bar.com', modified: 1667334077 } }] }], - // }, - // ], - // }, - // }; - - // const json = { - // doc: { - // type: 'doc', - // content: [ - // { - // type: 'paragraph', - // attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - // content: [{ type: 'dashField', attrs: { fieldKey: 'hello', docid: '', hideKey: true, editable: true }, marks: [{ type: 'strong' }, { type: 'user_mark', attrs: { userid: 'mart@bar.com', modified: 1667334006 } }] }], - // }, - // { - // type: 'paragraph', - // attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, - // content: [{ type: 'text', marks: [{ type: 'user_mark', attrs: { userid: 'mart@bar.com', modified: 1667334088 } }] }], - // }, - // ], - // }, - // }; // look for a document whose id === the fieldKey being displayed. If there's a match, then that document // holds the different enumerated values for the field in the titles of its collected documents. // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down. diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e9f59f17d..4378c10f7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -393,13 +393,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(); const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords); if (this._editorView?.state.doc.textContent) { + const isNodeSel = this._editorView.state.selection instanceof NodeSelection; const f = this._editorView.state.selection.from; const t = this._editorView.state.selection.to; var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); 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))); + tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : 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); @@ -1485,7 +1486,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - if (!this._editorView?.state.selection.empty && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu(); + if (!this._editorView?.state.selection.empty && !(this._editorView?.state.selection instanceof NodeSelection) && FormattedTextBox._canAnnotate && !(e.nativeEvent as any).dash) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; if (this.props.isContentActive(true) && !(e.nativeEvent as any).dash) { @@ -1494,7 +1495,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent node that wraps the hyerlink while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc); + FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.noPreview); } if (e.button === 0 && this.props.isSelected(true) && !e.altKey) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index bdf59863b..e7ca26d5c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -1,5 +1,5 @@ import { Mark, ResolvedPos } from 'prosemirror-model'; -import { EditorState } from 'prosemirror-state'; +import { EditorState, NodeSelection } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { Doc } from '../../../../fields/Doc'; import { DocServer } from '../../../DocServer'; @@ -92,7 +92,7 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.display = ''; } - static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '') { + static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = '', linkDoc: string = '', noPreview: boolean = false) { FormattedTextBoxComment.textBox = textBox; if (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection)) { FormattedTextBoxComment.setupPreview( @@ -102,12 +102,13 @@ export class FormattedTextBoxComment { ?.trim() .split(' ') .filter(h => h), - linkDoc + linkDoc, + noPreview ); } } - static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string) { + static setupPreview(view: EditorView, textBox: FormattedTextBox, hrefs?: string[], linkDoc?: string, noPreview?: boolean) { const state = view.state; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { @@ -130,8 +131,8 @@ export class FormattedTextBoxComment { if (state.selection.$from && hrefs?.length) { const nbef = findStartOfMark(state.selection.$from, view, findLinkMark); const naft = findEndOfMark(state.selection.$from, view, findLinkMark) || nbef; - nbef && - naft && + //nbef && + naft && LinkDocPreview.SetLinkInfo({ docProps: textBox.props, linkSrc: textBox.rootDoc, @@ -139,6 +140,7 @@ export class FormattedTextBoxComment { location: (pos => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), hrefs, showHeader: true, + noPreview, }); } } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index dc5d8ada8..e3c67ad2e 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -274,7 +274,7 @@ export class RichTextRules { const num = value.match(/^[0-9.]$/); this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid, hideKey: true }); + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid, hideKey: false }); return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); }), diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 00c41e187..4206a1006 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -75,6 +75,7 @@ export const marks: { [index: string]: MarkSpec } = { allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, location: { default: null }, title: { default: null }, + noPreview: { default: false }, docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text }, inclusive: false, @@ -85,6 +86,7 @@ export const marks: { [index: string]: MarkSpec } = { return { location: dom.getAttribute('location'), title: dom.getAttribute('title'), + noPreview: dom.getAttribute('noPreview'), }; }, }, @@ -111,7 +113,7 @@ export const marks: { [index: string]: MarkSpec } = { ['br'], ] : //node.attrs.allLinks.length === 1 ? - ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, location: node.attrs.location, style: `text-decoration: underline` }, 0]; + ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, noPreview: node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline` }, 0]; // ["div", { class: "prosemirror-anchor" }, // ["span", { class: "prosemirror-linkBtn" }, // ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0], -- cgit v1.2.3-70-g09d2 From 31a51e9dda07e48c88166bffbc8f1ad7166cd624 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 4 Nov 2022 14:34:46 -0400 Subject: more fixes to sidebar annotations of pdfs to improve link following. --- src/client/util/LinkFollower.ts | 17 +++++++++++++++-- src/client/views/DocumentDecorations.tsx | 2 ++ src/client/views/SidebarAnnos.tsx | 3 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +++- .../views/nodes/formattedText/DashDocCommentView.tsx | 9 +++++---- src/client/views/nodes/formattedText/DashFieldView.tsx | 3 --- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/formattedText/marks_rts.ts | 2 +- 8 files changed, 29 insertions(+), 13 deletions(-) (limited to 'src/client/views/SidebarAnnos.tsx') diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index f75ac24f5..ea0531fa2 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,9 +1,10 @@ -import { action, observable, observe } from 'mobx'; +import { action, observable, observe, runInAction } from 'mobx'; import { computedFn } from 'mobx-utils'; import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc'; import { List } from '../../fields/List'; import { ProxyField } from '../../fields/Proxy'; import { BoolCast, Cast, StrCast } from '../../fields/Types'; +import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; import { DocumentManager } from './DocumentManager'; @@ -59,7 +60,19 @@ export class LinkFollower { docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget }); } }; - LinkFollower.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); + runInAction(() => (DocumentDecorations.Instance.overrideBounds = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value + LinkFollower.traverseLink( + linkDoc, + sourceDoc, + createViewFunc, + BoolCast(sourceDoc.followLinkZoom, zoom), + docViewProps.ContainingCollectionDoc, + action(() => { + batch.end(); + DocumentDecorations.Instance.overrideBounds = false; + }), + altKey ? true : undefined + ); }; public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 47347284c..cec03c991 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -70,8 +70,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); } + @observable overrideBounds = false; @computed get Bounds() { + if (this.overrideBounds) return { x: 0, y: 0, r: 0, b: 0 }; const views = SelectionManager.Views(); return views .filter(dv => dv.props.renderDepth > 0) diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index c285b2e34..ec68a6b36 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -98,9 +98,10 @@ export class SidebarAnnos extends React.Component { { type: 'linkAnchor', attrs: { - allAnchors: [{ href: `/doc/${target[Id]}`, title: 'Anchored Selection', noPreview: true, anchorId: `${target[Id]}` }], + allAnchors: [{ href: `/doc/${target[Id]}`, title: 'Anchored Selection', anchorId: `${target[Id]}` }], location: 'add:right', title: 'Anchored Selection', + noPreview: true, docref: false, }, }, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2f246e74f..ac3777aa6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1229,6 +1229,8 @@ export class CollectionFreeFormView extends CollectionSubView screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0; if (screen.right - screen.left < bounds.right - bounds.left || screen.bot - screen.top < bounds.bot - bounds.top) { return { panX: (bounds.left + bounds.right) / 2, @@ -1238,7 +1240,7 @@ export class CollectionFreeFormView extends CollectionSubView node that wraps the hyerlink while (target && !target.dataset?.targethrefs) target = target.parentElement; - FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.noPreview); + FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview); } if (e.button === 0 && this.props.isSelected(true) && !e.altKey) { diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 4206a1006..97549830c 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -113,7 +113,7 @@ export const marks: { [index: string]: MarkSpec } = { ['br'], ] : //node.attrs.allLinks.length === 1 ? - ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, noPreview: node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline` }, 0]; + ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline` }, 0]; // ["div", { class: "prosemirror-anchor" }, // ["span", { class: "prosemirror-linkBtn" }, // ["a", { ...node.attrs, class: linkids, "data-targetids": targetids, title: `${node.attrs.title}` }, 0], -- cgit v1.2.3-70-g09d2