diff options
author | bobzel <zzzman@gmail.com> | 2025-04-30 14:31:55 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-04-30 14:31:55 -0400 |
commit | 082ffee0a2757f9e820fc708fceb5b38aca50f0f (patch) | |
tree | 9dd4c133a9c9cefbc52341fe6e5f76c301dc2cae /src | |
parent | 0e6d7b45c14301d426f85eeef0a96ab8dceebc25 (diff) | |
parent | 2296c314be710f983d595de37c9d8039d73568a6 (diff) |
Merge branch 'master' into aarav_edit
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/views/DocumentButtonBar.tsx | 2 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 107 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.scss | 42 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 34 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 63 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 3 | ||||
-rw-r--r-- | src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx | 15 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 1 | ||||
-rw-r--r-- | src/client/views/search/FaceRecognitionHandler.tsx | 2 | ||||
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 1 | ||||
-rw-r--r-- | src/server/ApiManagers/FireflyManager.ts | 40 |
14 files changed, 156 insertions, 161 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e694419a4..a4a668085 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -535,8 +535,6 @@ export class DocumentOptions { * The list of embedded Doc instances in each Scrapbook slot */ scrapbookContents?: List<Doc>; - - _outpaintingMetadata?: STRt = new StrInfo('serialized JSON metadata needed for image outpainting', false); } export const DocOptions = new DocumentOptions(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 8a850467a..bc669fc4e 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -507,7 +507,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( )} {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ? <div className="documentButtonBar-button">{this.endLinkButton} </div> : null} - <div className="documentButtonBar-button">{this.templateButton}</div> + {Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>} {!DocumentView.Selected().some(v => v.allLinks.length) ? null : <div className="documentButtonBar-button">{this.followLinkButton}</div>} <div className="documentButtonBar-button">{this.pinButton}</div> <div className="documentButtonBar-button">{this.recordButton}</div> diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3f11a4713..ab665e984 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -445,6 +445,12 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora @action onPointerDown = (e: React.PointerEvent): void => { SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + DocumentView.Selected() + .filter(dv => e.shiftKey && dv.ComponentView instanceof ImageBox) + .forEach(dv => { + dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalWidth'] = NumCast(dv.Document._width); + dv.Document[dv.ComponentView!.fieldKey + '_outpaintOriginalHeight'] = NumCast(dv.Document._height); + }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); @@ -484,50 +490,32 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const dragHdl = this._resizeHdlId.split(' ')[0].replace('documentDecorations-', '').replace('Resizer', ''); const thisPt = // do snapping of drag point - fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft') + fixedAspect && (dragHdl === 'bottomRight' || dragHdl === 'topLeft') && e.ctrlKey ? DragManager.snapDragAspect(this.projectDragToAspect(e, first, fixedAspect), fixedAspect) : DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y); const { scale, refPt } = this.getResizeVals(thisPt, dragHdl); !this._interactionLock && - runInAction(async () => { + runInAction(() => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) this._interactionLock = true; this._snapPt = thisPt; - // Special handling for shift+click (outpainting mode) - if (e.shiftKey && DocumentView.Selected().some(dv => dv.ComponentView instanceof ImageBox)) { - DocumentView.Selected().forEach(docView => { - if (docView.ComponentView instanceof ImageBox) { - // Set flag for outpainting mode - docView.Document._outpaintingResize = true; + const outpainted = e.shiftKey ? DocumentView.Selected().filter(dv => dv.ComponentView instanceof ImageBox) : []; + const notOutpainted = e.shiftKey ? DocumentView.Selected().filter(dv => !outpainted.includes(dv)) : DocumentView.Selected(); - // Adjust only the document dimensions without scaling internal content - this.resizeViewForOutpainting(docView, refPt, scale, { dragHdl, shiftKey: e.shiftKey }); - } else { - // Handle regular resize for non-image components - e.ctrlKey && !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions(); - const hasFixedAspect = this.hasFixedAspect(docView.Document); - const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; - this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey }); - } - }); - } else { - // Regular resize behavior (existing code) - e.ctrlKey && DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); - const hasFixedAspect = DocumentView.Selected() - .map(dv => dv.Document) - .some(this.hasFixedAspect); - const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; - DocumentView.Selected().forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey: e.ctrlKey })); - } + // Special handling for shift-drag resize (outpainting of Images by resizing without scaling content - fill in with firefly GAI) + e.shiftKey && outpainted.forEach(dv => this.resizeViewForOutpainting(dv, refPt, scale, { dragHdl, shiftKey: e.shiftKey })); - await new Promise<void>(res => { - setTimeout(() => { - res((this._interactionLock = undefined)); - }); - }); + // Special handling for not outpainted Docs when ctrl-resizing (setup native dimesions for modification) + e.ctrlKey && notOutpainted.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions()); + + const hasFixedAspect = notOutpainted.map(dv => dv.Document).some(this.hasFixedAspect); + const scaleAspect = { x: scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y }; + notOutpainted.forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, freezeNativeDims: e.ctrlKey })); + + new Promise<void>(res => setTimeout(() => res((this._interactionLock = undefined)))); }); return false; @@ -545,11 +533,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora return; } - if (!doc._outpaintingOriginalWidth || !doc._outpaintingOriginalHeight) { - doc._outpaintingOriginalWidth = NumCast(doc._width); - doc._outpaintingOriginalHeight = NumCast(doc._height); - } - // Calculate new boundary dimensions const originalWidth = NumCast(doc._width); const originalHeight = NumCast(doc._height); @@ -564,41 +547,14 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const { deltaX, deltaY } = this.realignRefPt(doc, refCent, originalWidth, originalHeight); doc.x = NumCast(doc.x) + deltaX; doc.y = NumCast(doc.y) + deltaY; - doc._layout_modificationDate = new DateField(); - - // Trigger outpainting - doc._needsOutpainting = true; - - // Store metadata needed for outpainting - doc._outpaintingMetadata = JSON.stringify({ - originalWidth: doc._outpaintingOriginalWidth, - originalHeight: doc._outpaintingOriginalHeight, - newWidth, - newHeight, - scaleX: scale.x, - scaleY: scale.y, - anchorHandle: opts.dragHdl, - }); }; @action - onPointerUp = (): void => { + onPointerUp = () => { SnappingManager.SetIsResizing(undefined); SnappingManager.clearSnapLines(); - // Check if any outpainting needs to be processed - DocumentView.Selected().forEach(view => { - if (view.Document._needsOutpainting && view.ComponentView instanceof ImageBox) { - // Trigger outpainting process in the ImageBox component - (view.ComponentView as ImageBox).processOutpainting(); - - // Clear flags - view.Document._needsOutpainting = false; - view.Document._outpaintingResize = false; - } - }); - this._resizeHdlId = ''; this._resizeUndo?.end(); @@ -647,7 +603,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora // // resize a single DocumentView about the specified reference point, possibly setting/updating the native dimensions of the Doc // - resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; ctrlKey: boolean }) => { + resizeView = (docView: DocumentView, refPt: number[], scale: { x: number; y: number }, opts: { dragHdl: string; freezeNativeDims: boolean }) => { const doc = docView.Document; if (doc.isGroup) { DocListCast(doc.data) @@ -660,25 +616,24 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora const [nwidth, nheight] = [docView.nativeWidth, docView.nativeHeight]; const [initWidth, initHeight] = [NumCast(doc._width, 1), NumCast(doc._height)]; - const modifyNativeDim = - (opts.ctrlKey && doc._layout_nativeDimEditable) || // e.g., PDF or web page - (doc._layout_reflowHorizontal && opts.dragHdl !== 'bottom' && opts.dragHdl !== 'top') || // eg rtf or some web pages - (doc._layout_reflowVertical && (opts.dragHdl === 'bottom' || opts.dragHdl === 'top' || opts.ctrlKey)); // eg rtf, web, pdf - if (nwidth && nheight && !modifyNativeDim) { - // eg., dragging right resizer on PDF -- enforce native dimensions because not expliclty overridden with ctrl or bottom resize drag + const cornerReflow = !opts.freezeNativeDims && doc._layout_reflowHorizontal && doc._layout_reflowVertical && ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'].includes(opts.dragHdl); + const horizontalReflow = !opts.freezeNativeDims && doc._layout_reflowHorizontal && ['left', 'right'].includes(opts.dragHdl); // eg rtf or some web pages + const verticalReflow = !opts.freezeNativeDims && doc._layout_reflowVertical && ['bottom', 'top'].includes(opts.dragHdl); // eg rtf, web, pdf + // preserve aspect ratio if Doc has a native ize and drag won't cause reflow (eg, ctrl-dragging a Doc's corner doesn't allow reflow, or dragging right side of a PDF which isn't horizontally reflowable) + if (nwidth && nheight && !cornerReflow && !horizontalReflow && !verticalReflow) { scale.x === 1 ? (scale.x = scale.y) : (scale.y = scale.x); } - if (['right', 'left', 'bottomRight'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeWidth(doc)) { + if ((horizontalReflow || cornerReflow) && Doc.NativeWidth(doc)) { const setData = Doc.NativeWidth(doc[DocData]) === doc.nativeWidth; - doc.nativeWidth = scale.x * Doc.NativeWidth(doc); + doc._nativeWidth = scale.x * Doc.NativeWidth(doc); if (setData) Doc.SetNativeWidth(doc[DocData], NumCast(doc.nativeWidth)); if (doc._layout_reflowVertical && !NumCast(doc.nativeHeight)) { doc._nativeHeight = (initHeight / initWidth) * nwidth; // initializes the nativeHeight for a PDF } } - if (['bottom', 'top', 'bottomRight'].includes(opts.dragHdl) && modifyNativeDim && Doc.NativeHeight(doc)) { - const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && (!doc.layout_reflowVertical || opts.ctrlKey); + if ((verticalReflow || cornerReflow) && Doc.NativeHeight(doc)) { + const setData = Doc.NativeHeight(doc[DocData]) === doc.nativeHeight && !doc.layout_reflowVertical; doc._nativeHeight = scale.y * Doc.NativeHeight(doc); if (setData) Doc.SetNativeHeight(doc[DocData], NumCast(doc._nativeHeight)); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index ea6259a32..13c3eb72f 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -443,7 +443,7 @@ export class CollectionDockingView extends CollectionSubView() { window.addEventListener('mouseup', this.onPointerUp); if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) { const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : ''; - if (className.includes('lm_maximise')) { + if (className.includes('lm_maximise') || className.includes('lm_close_tab')) { // this._flush = UndoManager.StartBatch('tab maximize'); } else { const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement); diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index ad05a798b..624c85beb 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -24,6 +24,7 @@ import { FaceRecognitionHandler } from '../../search/FaceRecognitionHandler'; import { CollectionStackingView } from '../CollectionStackingView'; import './FaceCollectionBox.scss'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; +import { returnEmptyDocViewList } from '../../StyleProvider'; /** * This code is used to render the sidebar collection of unique recognized faces, where each @@ -268,6 +269,8 @@ export class FaceCollectionBox extends ViewBoxBaseComponent<FieldViewProps>() { {...this._props} // styleProvider={this.stackingStyleProvider} Document={Doc.ActiveDashboard} + DocumentView={undefined} + docViewPath={returnEmptyDocViewList} fieldKey="myUniqueFaces" moveDocument={this.moveDocument} addDocument={this.addDocument} diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 9fc20ffd4..ac1a6ece9 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -106,7 +106,7 @@ height: 100%; img { object-fit: contain; - height: 100%; + height: fit-content; } .imageBox-fadeBlocker, @@ -249,27 +249,29 @@ background: white; padding: 20px; border-radius: 8px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); z-index: 10000; - - h3 { margin-top: 0; } - + + h3 { + margin-top: 0; + } + input { - width: 300px; - padding: 8px; - margin-bottom: 10px; + width: 300px; + padding: 8px; + margin-bottom: 10px; } - + .buttons { - display: flex; - justify-content: flex-end; - gap: 10px; - - .generate-btn { - background: #0078d4; - color: white; - border: none; - padding: 8px 16px; - } + display: flex; + justify-content: flex-end; + gap: 10px; + + .generate-btn { + background: #0078d4; + color: white; + border: none; + padding: 8px 16px; + } } - }
\ No newline at end of file +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 999d7089b..31a135fa7 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -170,13 +170,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }, { fireImmediately: true } ); - this._disposers.outpainting = reaction( - () => this.Document?._needsOutpainting, - needsOutpainting => { - if (needsOutpainting && this.Document?._outpaintingResize) { - this.processOutpainting(); - } - } + this._disposers.outpaint = reaction( + () => this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined && !SnappingManager.ShiftKey, + complete => complete && this.openOutpaintPrompt(), + { fireImmediately: true } ); } @@ -212,7 +209,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { drop = undoable( action((e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData && this._props.rejectDrop?.(de, this.DocumentView?.())) { + if (de.complete.docDragData && !this._props.rejectDrop?.(de, this.DocumentView?.())) { let added: boolean | undefined; const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => { if (!ele) return false; @@ -306,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const anchy = NumCast(cropping.y); const anchw = NumCast(cropping._width); const anchh = NumCast(cropping._height); - const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) / anchh; + const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw; cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width); cropping.y = NumCast(this.Document.y); cropping.onClick = undefined; @@ -380,13 +377,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (!field) return; - const origWidth = NumCast(this.Document._outpaintingOriginalWidth); - const origHeight = NumCast(this.Document._outpaintingOriginalHeight); - - if (!origWidth || !origHeight) { - console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.'); - return; - } + const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); + const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); // Set flag that outpainting is in progress this._outpaintingInProgress = true; @@ -421,7 +413,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, - originalDimensions: { width: origWidth, height: origHeight }, + originalDimensions: { width: Math.min(newWidth, origWidth), height: Math.min(newHeight, origHeight) }, newDimensions: { width: newWidth, height: newHeight }, }); @@ -452,6 +444,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.Document.$ai = true; this.Document.$ai_outpainted = true; this.Document.$ai_outpaint_prompt = customPrompt; + this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; + this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; } else { this.Document._width = origWidth; this.Document._height = origHeight; @@ -471,11 +465,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; - processOutpainting = () => this.openOutpaintPrompt(); - componentUI = () => !this._showOutpaintPrompt ? null : ( - <div className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> + <div key="imageBox-componentui" className="imageBox-regenerate-dialog" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> <h3>Outpaint Image</h3> <EditableText placeholder="Enter a prompt for extending the image:" @@ -732,7 +724,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ref={action((r: HTMLImageElement | null) => (this.imageRef = r))} key="paths" src={srcpath} - style={{ transform, transformOrigin }} + style={{ transform, transformOrigin, height: this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9897a0062..d6fa3172d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1149,7 +1149,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB // Since we also monitor all component height changes, this will update the document's height. resetNativeHeight = action((scrollHeight: number) => { this.layoutDoc['_' + this.fieldKey + '_height'] = scrollHeight; - if (!this.layoutDoc.isTemplateForField) this.layoutDoc._nativeHeight = scrollHeight; + if (!this.layoutDoc.isTemplateForField && NumCast(this.layoutDoc._nativeHeight)) this.layoutDoc._nativeHeight = scrollHeight; }); addPlugin = (plugin: Plugin) => { @@ -1344,8 +1344,62 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return text; }; - handlePaste = (view: EditorView, event: Event /* , slice: Slice */): boolean => { - const pdfAnchorId = (event as ClipboardEvent).clipboardData?.getData('dash/pdfAnchor'); + handlePaste = (view: EditorView, event: ClipboardEvent /* , slice: Slice */): boolean => { + return this.doPaste(view, event.clipboardData); + }; + doPaste = (view: EditorView, data: DataTransfer | null) => { + const html = data?.getData('text/html'); + const pdfAnchorId = data?.getData('dash/pdfAnchor'); + if (html && !pdfAnchorId) { + const replaceDivsWithParagraphs = (expr: string) => { + // Create a temporary DOM container + const container = document.createElement('div'); + container.innerHTML = expr; + + // Recursive function to process all divs + function processDivs(node: HTMLElement) { + // Get all div elements in the current node (live collection) + const divs = node.getElementsByTagName('div'); + + // We need to convert to array because we'll be modifying the DOM + const divsArray = Array.from(divs); + + for (const div of divsArray) { + // Create replacement paragraph + const p = document.createElement('p'); + + // Copy all attributes + for (const attr of div.attributes) { + p.setAttribute(attr.name, attr.value); + } + + // Move all child nodes + while (div.firstChild) { + p.appendChild(div.firstChild); + } + + // Replace the div with the paragraph + div.parentNode?.replaceChild(p, div); + + // Process any nested divs that were moved into the new paragraph + processDivs(p); + } + } + + // Start processing from the container + processDivs(container); + + return container.innerHTML; + }; + const fixedHTML = replaceDivsWithParagraphs(html); + // .replace(/<div\b([^>]*)>(.*?)<\/div>/g, '<p$1>$2</p>'); // prettier-ignore + this._inDrop = true; + view.pasteHTML(html.split('<p').length < 2 ? fixedHTML : html); + this._inDrop = false; + + return true; + } + return !!(pdfAnchorId && this.addPdfReference(pdfAnchorId)); }; @@ -1485,9 +1539,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } if (this._props.isContentActive()) this.prepareForTyping(); if (this.EditorView && FormattedTextBox.PasteOnLoad) { - const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor'); + this.doPaste(this.EditorView, FormattedTextBox.PasteOnLoad.clipboardData); FormattedTextBox.PasteOnLoad = undefined; - pdfAnchorId && this.addPdfReference(pdfAnchorId); } if (this._props.autoFocus) setTimeout(() => this.EditorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it. } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 26ccf6931..f26a75fe4 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -349,7 +349,8 @@ export class RichTextRules { let count = 0; // ignore first return value which will be the notation that chat is pending a result Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { if (count) { - const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); + this.TextBox.EditorView?.pasteText(' ' + (gptval as string), undefined); + const tr = this.TextBox.EditorView?.state.tr; //.insertText(' ' + (gptval as string)); tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length)))); RichTextMenu.Instance?.elideSelection(this.TextBox.EditorView?.state, true); } diff --git a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx index c02a1eb94..e580c7070 100644 --- a/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx +++ b/src/client/views/nodes/imageEditor/imageMeshTool/imageMeshToolButton.tsx @@ -13,20 +13,11 @@ interface ButtonContainerProps { btnText: string; imageWidth: number; imageHeight: number; - gridXSize: number; // X subdivisions - gridYSize: number; // Y subdivisions + gridXSize: number; // X subdivisions + gridYSize: number; // Y subdivisions } -export function MeshTransformButton({ - loading, - onClick, - onReset, - btnText, - imageWidth, - imageHeight, - gridXSize, - gridYSize -}: ButtonContainerProps) { +export function MeshTransformButton({ loading, onClick, onReset, btnText, imageWidth, imageHeight, gridXSize, gridYSize }: ButtonContainerProps) { const [showGrid, setShowGrid] = React.useState(false); const [isGridInteractive, setIsGridInteractive] = React.useState(false); // Controls the dragging of control points const imageRef = React.useRef<HTMLImageElement>(null); // Reference to the image element diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 041fc0de7..fc2567fbc 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -146,6 +146,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { e.clipboardData.setData('dash/pdfAnchor', anchor[DocData][Id]); } e.preventDefault(); + e.stopPropagation(); } }; diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index cb837e3ab..3ad5bc844 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -43,7 +43,7 @@ export class FaceRecognitionHandler { * Loads an image */ private static loadImage = (imgUrl: ImageField): Promise<HTMLImageElement> => { - const [name, type] = ImageCastToNameType(imgUrl.url.href); + const [name, type] = ImageCastToNameType(imgUrl); const imageURL = `${name}_o.${type}`; return new Promise((resolve, reject) => { diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 2283ef965..4f0cd3978 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -576,6 +576,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { type="text" autoFocus value={this._userInput} + onPointerDown={e => e.stopPropagation()} onChange={action(e => this._canInteract && (this._userInput = e.target.value))} placeholder="Enter item to draw" onKeyDown={this.handleKeyPress} diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts index 0b19f66e0..1b8a85a5c 100644 --- a/src/server/ApiManagers/FireflyManager.ts +++ b/src/server/ApiManagers/FireflyManager.ts @@ -86,25 +86,23 @@ export default class FireflyManager extends ApiManager { .then(link => resolve(link.result.link)) .catch(linkErr => reject(new Error('Failed to get temporary link: ' + linkErr.message))) ) - .catch(uploadErr => reject(new Error('Failed to upload file to Dropbox: ' + uploadErr.message))); + .catch(uploadErr => { + if (user?.dropboxRefresh) { + console.log('Attempting to refresh Dropbox token for user:', user.email); + this.refreshDropboxToken(user) + .then(token => { + if (!token) return reject(new Error('Failed to refresh Dropbox token.' + user.email)); - uploadToDropbox(dbx).catch(e => { - if (user?.dropboxRefresh) { - console.log('Attempting to refresh Dropbox token for user:', user.email); - this.refreshDropboxToken(user) - .then(token => { - if (!token) { - return reject(new Error('Failed to refresh Dropbox token.' + user.email)); - } + const dbxNew = new Dropbox({ accessToken: token }); + uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message))); + }) + .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message))); + } else { + reject(new Error('Dropbox error: ' + uploadErr.message)); + } + }); - const dbxNew = new Dropbox({ accessToken: token }); - uploadToDropbox(dbxNew).catch(finalErr => reject(new Error('Failed to refresh Dropbox token:' + finalErr.message))); - }) - .catch(refreshErr => reject(new Error('Failed to refresh Dropbox token: ' + refreshErr.message))); - } else { - reject(new Error('Dropbox error: ' + e.message)); - } - }); + uploadToDropbox(dbx); }); }); @@ -345,10 +343,10 @@ export default class FireflyManager extends ApiManager { numVariations: 1, placement: { inset: { - left: 0, - top: 0, - right: Math.round(req.body.newDimensions.width - req.body.originalDimensions.width), - bottom: Math.round(req.body.newDimensions.height - req.body.originalDimensions.height), + left: Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + top: Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), + right: Math.round((req.body.newDimensions.width - req.body.originalDimensions.width) / 2), + bottom: Math.round((req.body.newDimensions.height - req.body.originalDimensions.height) / 2), }, alignment: { horizontal: 'center', |