From c75ffd4900acea74c55b6bf275a5e8082c15d573 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 26 Apr 2020 19:29:50 -0700 Subject: formatted textbox disposers refactor, paragraph chunked rich text initialization and buxton importer updates --- src/client/documents/Documents.ts | 1 + .../views/collections/CollectionTreeView.tsx | 21 ++++++-- src/client/views/nodes/FormattedTextBox.tsx | 60 ++++++++-------------- src/scraping/buxton/final/BuxtonImporter.ts | 17 +++--- 4 files changed, 49 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1268c97b0..6bd2eee3c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -407,6 +407,7 @@ export namespace Docs { const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true }); const deviceProto = Doc.GetProto(doc); deviceProto.hero = new ImageField(constructed[0].url); + deviceProto.fontFamily = "Arial"; Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: deviceProto } }); Doc.AddDocToList(parentProto, "data", doc); } else if (errors) { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 7f5dc98f1..49eb90bc2 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -730,18 +730,31 @@ export class CollectionTreeView extends CollectionSubView ({ + type: "paragraph", + content: [{ type: "dashField", attrs: { fieldKey } }] + })) + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + textDoc.data = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work? heroView.proto!.layout = ImageBox.LayoutString("hero"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 425af8ccf..dd0df9f3f 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -83,17 +83,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _lastX = 0; private _lastY = 0; private _undoTyping?: UndoManager.Batch; - private _searchReactionDisposer?: Lambda; - private _recordReactionDisposer: Opt; - private _scrollToRegionReactionDisposer: Opt; - private _reactionDisposer: Opt; - private _heightReactionDisposer: Opt; - private _proxyReactionDisposer: Opt; - private _pullReactionDisposer: Opt; - private _pushReactionDisposer: Opt; - private _buttonBarReactionDisposer: Opt; - private _linkMakerDisposer: Opt; - private _scrollDisposer: Opt; + private _disposers: { [name: string]: IReactionDisposer } = {}; private dropDisposer?: DragManager.DragDropDisposer; @computed get _recording() { return this.dataDoc.audioState === "recording"; } @@ -564,7 +554,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } componentDidMount() { - this._buttonBarReactionDisposer = reaction( + this._disposers.buttonBar = reaction( () => DocumentButtonBar.Instance, instance => { if (instance) { @@ -573,7 +563,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - this._linkMakerDisposer = reaction( + this._disposers.linkMaker = reaction( () => this.props.makeLink?.(), (linkDoc: Opt) => { if (linkDoc) { @@ -584,8 +574,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); - - this._reactionDisposer = reaction( + this._disposers.editorState = reaction( () => { if (this.dataDoc[this.props.fieldKey + "-noTemplate"] || !this.props.Document[this.props.fieldKey + "-textTemplate"]) { return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; @@ -600,8 +589,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - - this._pullReactionDisposer = reaction( + this._disposers.pullDoc = reaction( () => this.props.Document[Pulls], () => { if (!DocumentButtonBar.hasPulledHack) { @@ -611,8 +599,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - - this._pushReactionDisposer = reaction( + this._disposers.pushDoc = reaction( () => this.props.Document[Pushes], () => { if (!DocumentButtonBar.hasPushedHack) { @@ -621,19 +608,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } ); - - this._heightReactionDisposer = reaction( + this._disposers.height = reaction( () => [this.layoutDoc[WidthSym](), this.layoutDoc._autoHeight], () => this.tryUpdateHeight() ); this.setupEditor(this.config, this.props.fieldKey); - this._searchReactionDisposer = reaction(() => this.rootDoc.searchMatch, + this._disposers.search = reaction(() => this.rootDoc.searchMatch, search => search ? this.highlightSearchTerms([Doc.SearchQuery()]) : this.unhighlightSearchTerms(), { fireImmediately: true }); - this._recordReactionDisposer = reaction(() => this._recording, + this._disposers.record = reaction(() => this._recording, () => { if (this._recording) { setTimeout(action(() => { @@ -643,8 +629,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else setTimeout(() => this.stopDictation(true), 0); } ); - - this._scrollToRegionReactionDisposer = reaction( + this._disposers.scrollToRegion = reaction( () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { const findLinkFrag = (frag: Fragment, editor: EditorView) => { @@ -689,8 +674,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); - - this._scrollDisposer = reaction(() => NumCast(this.props.Document.scrollPos), + this._disposers.scroll = reaction(() => NumCast(this.props.Document.scrollPos), pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true } ); @@ -876,7 +860,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { - this._editorView.dispatch(this._editorView.state.tr.insertText(startupText)); + const paragraphSegments = startupText.split("\n\n"); + const { state: { tr }, dispatch } = this._editorView; + if (paragraphSegments.length) { + for (const paragraph of paragraphSegments) { + dispatch(tr.insertText(paragraph)); + } + } else { + dispatch(tr.insertText(startupText)); + } } } @@ -906,17 +898,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } componentWillUnmount() { - this._scrollDisposer?.(); - this._scrollToRegionReactionDisposer?.(); - this._reactionDisposer?.(); - this._proxyReactionDisposer?.(); - this._pushReactionDisposer?.(); - this._pullReactionDisposer?.(); - this._heightReactionDisposer?.(); - this._searchReactionDisposer?.(); - this._recordReactionDisposer?.(); - this._buttonBarReactionDisposer?.(); - this._linkMakerDisposer?.(); + Object.values(this._disposers).forEach(disposer => disposer?.()); this._editorView?.destroy(); } diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index 122415460..64b988610 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -16,6 +16,7 @@ interface DocumentContents { hyperlinks: string[]; captions: string[]; embeddedFileNames: string[]; + longDescriptionParagraphs: string[]; } export interface DeviceDocument { @@ -186,10 +187,6 @@ const RegexMap = new Map>([ exp: /Short Description:\s+(.*)Bill Buxton[’']s Notes/, transformer: Utilities.correctSentences }], - ["longDescription", { - exp: /Bill Buxton[’']s Notes(.*)Device Details/, - transformer: Utilities.correctSentences - }], ]); const sourceDir = path.resolve(__dirname, "source"); @@ -267,7 +264,12 @@ async function extractFileContents(pathToDocument: string): Promise node.text()); + const captionTargets = document.find(tableCellXPath).map(node => node.text().trim()); + + const paragraphs = document.find('//*[name()="w:p"]').map(node => Utilities.correctSentences(node.text()).transformed!); + const start = paragraphs.indexOf(paragraphs.find(el => /Bill Buxton[’']s Notes/.test(el))!) + 1; + const end = paragraphs.indexOf("Device Details"); + const longDescriptionParagraphs = paragraphs.slice(start, end); const { length } = captionTargets; strictEqual(length > 3, true, "No captions written."); @@ -290,7 +292,7 @@ async function extractFileContents(pathToDocument: string): Promise { } function analyze(fileName: string, contents: DocumentContents): AnalysisResult { - const { body, imageData, captions, hyperlinks, embeddedFileNames } = contents; + const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescriptionParagraphs } = contents; const device: any = { hyperlinks, captions, @@ -376,6 +378,7 @@ function analyze(fileName: string, contents: DocumentContents): AnalysisResult { return { errors }; } + device.longDescription = longDescriptionParagraphs.join("\n\n"); return { device }; } -- cgit v1.2.3-70-g09d2 From fc470b25759e8f051dc527066f9bebcaf5e7707d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 26 Apr 2020 23:55:23 -0700 Subject: various buxton fixes --- src/client/documents/Documents.ts | 1 - .../views/collections/CollectionTreeView.tsx | 24 ++++++++-------- src/client/views/nodes/DocumentContentsView.tsx | 8 +++--- src/client/views/nodes/FormattedTextBox.tsx | 9 +----- src/scraping/buxton/final/BuxtonImporter.ts | 32 ++++++++++++---------- 5 files changed, 36 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1651a6d55..5e0890e76 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -408,7 +408,6 @@ export namespace Docs { const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true }); const deviceProto = Doc.GetProto(doc); deviceProto.hero = new ImageField(constructed[0].url); - deviceProto.fontFamily = "Arial"; Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: deviceProto } }); Doc.AddDocToList(parentProto, "data", doc); } else if (errors) { diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 362d43ee7..dcb5e116c 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -733,19 +733,21 @@ export class CollectionTreeView extends CollectionSubView { const style: { [key: string]: any } = {}; const divKeys = OmitKeys(this.props, ["children", "htmltag", "RootDoc", "Document", "key", "onInput", "onClick", "__proto__"]).omit; Object.keys(divKeys).map((prop: string) => { - let p = (this.props as any)[prop] as string; + const p = (this.props as any)[prop] as string; const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ self: this.props.RootDoc, this: this.props.Document }).result as string || ""; }; @@ -178,9 +178,9 @@ export class DocumentContentsView extends React.Component 1 ? splits[0] + splits[1].replace(/{([^{}]|(?R))*}/, replacer4) : ""; // might have been more elegant if javascript supported recursive patterns diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8d4b90c41..d98172823 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -860,15 +860,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { - const paragraphSegments = startupText.split("\n\n"); const { state: { tr }, dispatch } = this._editorView; - if (paragraphSegments.length) { - for (const paragraph of paragraphSegments) { - dispatch(tr.insertText(paragraph)); - } - } else { - dispatch(tr.insertText(startupText)); - } + dispatch(tr.insertText(startupText)); } } diff --git a/src/scraping/buxton/final/BuxtonImporter.ts b/src/scraping/buxton/final/BuxtonImporter.ts index 64b988610..713207a07 100644 --- a/src/scraping/buxton/final/BuxtonImporter.ts +++ b/src/scraping/buxton/final/BuxtonImporter.ts @@ -16,7 +16,7 @@ interface DocumentContents { hyperlinks: string[]; captions: string[]; embeddedFileNames: string[]; - longDescriptionParagraphs: string[]; + longDescription: string; } export interface DeviceDocument { @@ -269,7 +269,7 @@ async function extractFileContents(pathToDocument: string): Promise Utilities.correctSentences(node.text()).transformed!); const start = paragraphs.indexOf(paragraphs.find(el => /Bill Buxton[’']s Notes/.test(el))!) + 1; const end = paragraphs.indexOf("Device Details"); - const longDescriptionParagraphs = paragraphs.slice(start, end); + const longDescription = paragraphs.slice(start, end).filter(paragraph => paragraph.length).join("\n\n"); const { length } = captionTargets; strictEqual(length > 3, true, "No captions written."); @@ -292,7 +292,7 @@ async function extractFileContents(pathToDocument: string): Promise { const imageEntries = allEntries.filter(name => imageEntry.test(name)); const imageUrls: ImageData[] = []; - for (const mediaPath of imageEntries) { - const getImageStream = () => new Promise((resolve, reject) => { - zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream)); - }); + const valid: any[] = []; + + const getImageStream = (mediaPath: string) => new Promise((resolve, reject) => { + zip.stream(mediaPath, (error: any, stream: any) => error ? reject(error) : resolve(stream)); + }); + for (const mediaPath of imageEntries) { const { width, height, type } = await new Promise(async resolve => { const sizeStream = (createImageSizeStream() as PassThrough).on('size', (dimensions: Dimensions) => { readStream.destroy(); resolve(dimensions); }).on("error", () => readStream.destroy()); - const readStream = await getImageStream(); + const readStream = await getImageStream(mediaPath); readStream.pipe(sizeStream); }); - if (Math.abs(width - height) < 10) { - continue; + + if (Math.abs(width - height) > 10) { + valid.push({ width, height, type, mediaPath }); } + } + for (const { type, width, height, mediaPath } of valid) { const generatedFileName = `upload_${Utils.GenerateGuid()}.${type.toLowerCase()}`; - await DashUploadUtils.outputResizedImages(getImageStream, generatedFileName, imageDir); - + await DashUploadUtils.outputResizedImages(() => getImageStream(mediaPath), generatedFileName, imageDir); imageUrls.push({ url: `/files/images/buxton/${generatedFileName}`, nativeWidth: width, @@ -339,11 +343,12 @@ async function writeImages(zip: any): Promise { } function analyze(fileName: string, contents: DocumentContents): AnalysisResult { - const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescriptionParagraphs } = contents; + const { body, imageData, captions, hyperlinks, embeddedFileNames, longDescription } = contents; const device: any = { hyperlinks, captions, embeddedFileNames, + longDescription, __images: imageData }; const errors: { [key: string]: string } = { fileName }; @@ -378,7 +383,6 @@ function analyze(fileName: string, contents: DocumentContents): AnalysisResult { return { errors }; } - device.longDescription = longDescriptionParagraphs.join("\n\n"); return { device }; } -- cgit v1.2.3-70-g09d2