diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/FilterPanel.tsx | 4 | ||||
-rw-r--r-- | src/client/views/PropertiesView.tsx | 150 | ||||
-rw-r--r-- | src/client/views/StyleProvider.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx | 15 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/nodes/CollectionFreeFormDocumentView.tsx | 7 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/button/FontIconBox.tsx | 20 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 10 | ||||
-rw-r--r-- | src/fields/Doc.ts | 3 |
11 files changed, 85 insertions, 137 deletions
diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index a237249c1..d17a4ea25 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -145,8 +145,8 @@ export class FilterPanel extends React.Component<filterProps> { const set = new Set<string>([String.fromCharCode(127) + '--undefined--']); if (facetHeader === 'tags') allCollectionDocs.forEach(child => - Field.toString(child[facetHeader] as Field) - .split(':') + StrListCast(child[facetHeader]) + .filter(h => h) .forEach(key => set.add(key)) ); else diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 6582c3f2a..fff8390b3 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -7,7 +7,7 @@ import { intersection } from 'lodash'; import { action, computed, Lambda, observable } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import { AclAdmin, AclSym, DataSym, Doc, Field, HeightSym, HierarchyMapping, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc'; +import { AclAdmin, AclSym, DataSym, Doc, Field, FieldResult, HeightSym, HierarchyMapping, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { List } from '../../fields/List'; @@ -152,116 +152,55 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { return 0; }; - @computed get expandedField() { + editableFields = (filter: (key: string) => boolean, reqdKeys: string[]) => { + const rows: JSX.Element[] = []; if (this.dataDoc && this.selectedDoc) { - const ids: { [key: string]: string } = {}; - const docs = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); - docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); - const rows: JSX.Element[] = []; - for (const key of Object.keys(ids).slice().sort()) { - const docvals = new Set<any>(); - docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? '-multiple' : docs[0][key]; - if (key[0] === '#') { - rows.push( - <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '2px' }} key={key}> - <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key}</span> - - </div> - ); - } else { - const contentElement = ( - <EditableView - key="editableView" - contents={contents !== undefined ? Field.toString(contents as Field) : 'null'} - height={13} - fontSize={10} - GetValue={() => (contents !== undefined ? Field.toString(contents as Field) : 'null')} - SetValue={(value: string) => { - docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); - return true; - }} - /> - ); - rows.push( - <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}> - <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span> - - {contentElement} - </div> - ); - } - } + const ids = new Set<string>(reqdKeys); + const docs: Doc[] = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); + docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && ids.add(key))); + + // prettier-ignore + Array.from(ids).filter(filter).sort().map(key => { + const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set<FieldResult>()).keys()).length > 1; + const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key); + const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field); + const contentElement = key[0] === '#' ? <></> : ( + <EditableView + key="editableView" + contents={displayContents} + height={13} + fontSize={10} + GetValue={() => editableContents} + SetValue={(value: string) => { + value !== '-multiple-' && docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); + return true; + }} + />); + rows.push( + <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}> + <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span> + + {contentElement} + </div> + ); + }); + rows.push( - <div className="propertiesView-field" key={'newKeyValue'} style={{ marginTop: '3px' }}> + <div className="propertiesView-field" key="newKeyValue" style={{ marginTop: '3px' }}> <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} /> </div> ); - return rows; } + return rows; + }; + + @computed get expandedField() { + return this.editableFields(returnTrue, []); } @computed get noviceFields() { - if (this.dataDoc) { - const ids: { [key: string]: string } = {}; - const docs = SelectionManager.Views().length < 2 ? [this.dataDoc] : SelectionManager.Views().map(dv => dv.dataDoc); - docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); - const rows: JSX.Element[] = []; - const noviceReqFields = ['author', 'creationDate', 'tags']; - const noviceLayoutFields = ['_curPage']; - const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl'))), ...noviceReqFields, ...noviceLayoutFields]; - for (const key of noviceKeys.sort()) { - const docvals = new Set<any>(); - docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? '-multiple' : docs[0][key]; - if (key[0] === '#') { - rows.push( - <div className="propertiesView-uneditable-field" key={key}> - <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key}</span> - - </div> - ); - } else if (contents !== undefined) { - const value = Field.toString(contents as Field); - if (noviceReqFields.includes(key) || key.indexOf('lastModified') !== -1) { - rows.push( - <div className="propertiesView-uneditable-field" key={key}> - <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ': '}</span> - <div style={{ whiteSpace: 'nowrap', overflowX: 'hidden' }}>{value}</div> - </div> - ); - } else { - const contentElement = ( - <EditableView - key="editableView" - contents={value} - height={13} - fontSize={10} - GetValue={() => (contents !== undefined ? Field.toString(contents as Field) : 'null')} - SetValue={(value: string) => { - docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); - return true; - }} - /> - ); - - rows.push( - <div style={{ display: 'flex', overflowY: 'visible', marginBottom: '-1px' }} key={key}> - <span style={{ fontWeight: 'bold', whiteSpace: 'nowrap' }}>{key + ':'}</span> - - {contentElement} - </div> - ); - } - } - } - rows.push( - <div className="propertiesView-field" key={'newKeyValue'} style={{ marginTop: '3px' }}> - <EditableView key="editableView" oneLine contents={'add key:value or #tags'} height={13} fontSize={10} GetValue={() => ''} SetValue={this.setKeyValue} /> - </div> - ); - return rows; - } + const noviceReqFields = ['author', 'creationDate', 'tags', '_curPage']; + return this.editableFields(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl')), noviceReqFields); } @undoBatch @@ -280,9 +219,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } else if (value[0] === '#') { const newVal = value + `:'${value}'`; doc[DataSym][value] = value; - const tags = StrCast(doc.tags, ':'); - if (!tags.includes(`${value}:`)) { - doc[DataSym].tags = `${tags + value + ':'}`; + const tags = StrListCast(doc.tags); + if (!tags.includes(value)) { + tags.push(value); + doc[DataSym].tags = tags.length ? new List<string>(tags) : undefined; } return true; } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index d13052f71..192a501f2 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -303,13 +303,13 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps } } case StyleProp.PointerEvents: - const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name); + const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString; if (docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.() !== undefined) return docProps?.DocumentView?.().ComponentView?.overridePointerEvents?.(); if (MainView.Instance._exploreMode || doc?.unrendered) return isInk ? 'visiblePainted' : 'all'; if (doc?.pointerEvents) return StrCast(doc.pointerEvents); if (props?.contentPointerEvents) return StrCast(props.contentPointerEvents); if (props?.pointerEvents?.() === 'none') return 'none'; - if (opacity() === 0 || doc?.isInkMask) return 'none'; + if (opacity() === 0) return 'none'; if (props?.isDocumentActive?.()) return isInk ? 'visiblePainted' : 'all'; return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active case StyleProp.Decorations: diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index c1f3c5aa6..fee4705e6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -133,20 +133,15 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do let nonNumbers = 0; const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author'; childPairs.map(pair => { - const lval = - pivotFieldKey === '#' || pivotFieldKey === 'tags' - ? Array.from(Object.keys(Doc.GetProto(pair.layout))) - .filter(k => k.startsWith('#')) - .map(k => k.substring(1)) - : Cast(pair.layout[pivotFieldKey], listSpec('string'), null); + const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null); const num = toNumber(pair.layout[pivotFieldKey]); if (num === undefined || Number.isNaN(num)) { nonNumbers++; } const val = Field.toString(pair.layout[pivotFieldKey] as Field); - if (lval) { - lval.forEach((val, i) => { + if (listValue) { + listValue.forEach((val, i) => { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); pivotColumnGroups.get(val)!.docs.push(pair.layout); pivotColumnGroups.get(val)!.replicas.push(i.toString()); @@ -159,8 +154,8 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do docMap.set(pair.layout[Id], { x: 0, y: 0, - zIndex: -99, - width: 0, + zIndex: 0, + width: 0, // should make doc hidden in CollectionFreefromDocumentView height: 0, pair, replica: '', diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9cc732008..25da868e0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1158,7 +1158,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action bringToFront = (doc: Doc, sendToBack?: boolean) => { if (sendToBack) { - doc.zIndex = 0; + const docs = this.childLayoutPairs.map(pair => pair.layout).slice(); + docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); + let zfirst = docs.length ? NumCast(docs[0].zIndex) : 0; + doc.zIndex = zfirst - 1; } else if (doc.isInkMask) { doc.zIndex = 5000; } else { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 24b9f3b25..b68bcc263 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -16,6 +16,8 @@ import { StyleProp } from '../StyleProvider'; import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); +import { DocumentType } from '../../documents/DocumentTypes'; +import { InkingStroke } from '../InkingStroke'; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; @@ -201,9 +203,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF PanelWidth: this.panelWidth, PanelHeight: this.panelHeight, }; + const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString && !this.layoutDoc._isInkMask; return ( <div - className={'collectionFreeFormDocumentView-container'} + className="collectionFreeFormDocumentView-container" style={{ width: this.panelWidth(), height: this.panelHeight(), @@ -211,7 +214,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF transformOrigin: '50% 50%', transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)), zIndex: this.ZInd, - display: this.ZInd === -99 ? 'none' : undefined, + pointerEvents: isInk ? 'none' : undefined, }}> {this.props.renderCutoffProvider(this.props.Document) ? ( <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} /> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3bdd2bf6e..f1f5f7e10 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -870,7 +870,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents); @computed get contents() { TraceMobx(); - const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name); + const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString; return ( <div className="documentView-contentsView" diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index f04678d8b..26515da30 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -224,12 +224,12 @@ export class FontIconBox extends DocComponent<ButtonProps>() { </div> <div className="dropbox-background" - onClick={e => { + onClick={action(e => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; this.noTooltip = false; Doc.UnBrushAllDocs(); - }} + })} /> </div> ) : null} @@ -336,11 +336,11 @@ export class FontIconBox extends DocComponent<ButtonProps>() { style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }} onClick={ dropdown - ? () => { + ? action(() => { this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); - } + }) : undefined }> {dropdown ? null : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />} @@ -358,12 +358,12 @@ export class FontIconBox extends DocComponent<ButtonProps>() { </div> <div className="dropbox-background" - onClick={e => { + onClick={action(e => { e.stopPropagation(); this.rootDoc.dropDownOpen = false; this.noTooltip = false; Doc.UnBrushAllDocs(); - }} + })} /> </div> ) : null} @@ -582,17 +582,19 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b } else if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); + const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber).backgroundColor ?? 'transparent'; + return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent'; } selectedViews.forEach(dv => { + const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed if (contentFrameNumber !== undefined) { - CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { backgroundColor: color }); + CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); } else { - dv.rootDoc._backgroundColor = color; + dv.rootDoc['_' + fieldKey] = color; } }); } else { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index eb28ffbd7..1d668d6a9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -69,6 +69,7 @@ import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); import { RTFMarkup } from '../../../util/RTFMarkup'; +import { List } from '../../../../fields/List'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @@ -318,6 +319,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const removed = curTags.filter(tag => !accumTags.includes(tag)); removed.forEach(r => (dataDoc[r] = undefined)); added.forEach(a => (dataDoc[a] = a)); + dataDoc.tags = curTags.length ? new List<string>(curTags) : undefined; let unchanged = true; if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index cc19d12bd..68b209332 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,7 +1,8 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; -import { DataSym, Doc } from '../../../../fields/Doc'; +import { DataSym, Doc, StrListCast } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; import { ComputedField } from '../../../../fields/ScriptField'; import { NumCast, StrCast } from '../../../../fields/Types'; import { normalizeEmail } from '../../../../fields/util'; @@ -325,9 +326,10 @@ export class RichTextRules { const tag = match[1]; if (!tag) return state.tr; this.Document[DataSym]['#' + tag] = '#' + tag; - const tags = StrCast(this.Document[DataSym].tags, ':'); - if (!tags.includes(`#${tag}:`)) { - this.Document[DataSym].tags = `${tags + '#' + tag + ':'}`; + const tags = StrListCast(this.Document[DataSym].tags); + if (!tags.includes(tag)) { + tags.push(tag); + this.Document[DataSym].tags = new List<string>(tags); } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index a9be24c8b..03ae91c50 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1387,7 +1387,8 @@ export namespace Doc { if (typeof value === 'string') { value = value.replace(`,${Utils.noRecursionHack}`, ''); } - const fieldVal = key === '#' ? (StrCast(doc.tags).includes(':#' + value + ':') ? StrCast(doc.tags) : undefined) : doc[key]; + const tagsString = doc.tags ? ':' + StrListCast(doc.tags).join(':') + ':' : ''; + const fieldVal = key === '#' ? (tagsString.includes(':#' + value + ':') ? tagsString : undefined) : doc[key]; if (Cast(fieldVal, listSpec('string'), []).length) { const vals = Cast(fieldVal, listSpec('string'), []); const docs = vals.some(v => (v as any) instanceof Doc); |