diff options
Diffstat (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx')
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 147 |
1 files changed, 141 insertions, 6 deletions
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 5b435e44a..e1ea93c3f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -3,7 +3,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { baseKeymap, selectAll } from 'prosemirror-commands'; import { history } from 'prosemirror-history'; @@ -65,6 +65,8 @@ import { removeMarkWithAttrs } from './prosemirrorPatches'; import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; +import { URLField } from '../../../../fields/URLField'; +import { gptImageLabel } from '../../../apis/gpt/GPT'; // import * as applyDevTools from 'prosemirror-dev-tools'; export interface FormattedTextBoxProps extends FieldViewProps { @@ -112,6 +114,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB private _rules: RichTextRules | undefined; private _forceUncollapse = true; // if the cursor doesn't move between clicks, then the selection will disappear for some reason. This flags the 2nd click as happening on a selection which allows bullet points to toggle private _break = true; + @observable private _editLabel = false; public ProseRef?: HTMLDivElement; public get EditorView() { return this._editorView; @@ -181,7 +184,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB @observable private gptRes: string = ''; - public makeAIFlashcards: () => void = unimplementedFunction; + // public makeAIFlashcards: () => void = unimplementedFunction; public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined; public static PasteOnLoad: ClipboardEvent | undefined; @@ -359,10 +362,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const rtField = (layoutData !== prevData ? layoutData : undefined) ?? protoData; if (this._applyingChange !== this.fieldKey && (force || textChange || removeSelection(newJson) !== removeSelection(prevData?.Data))) { this._applyingChange = this.fieldKey; - textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); if ((!prevData && !protoData && !layoutData) || newText || (!newText && !protoData && !layoutData)) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && (textChange || removeSelection(newJson) !== removeSelection(prevData?.Data)))) { + textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); const numstring = NumCast(dataDoc[this.fieldKey], null); dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : newText || (DocCast(dataDoc.proto)?.[this.fieldKey] === undefined && this.layoutDoc[this.fieldKey] === undefined) ? new RichTextField(newJson, newText) : undefined; @@ -371,6 +374,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB unchanged = false; } } else if (rtField) { + textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); // if we've deleted all the text in a note driven by a template, then restore the template data dataDoc[this.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse(rtField.Data))); @@ -816,6 +820,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB isTargetToggler = (anchor: Doc) => BoolCast(anchor.followLinkToggle); specificContextMenu = (e: React.MouseEvent): void => { + if (this._props.dontSelect?.()) return; const cm = ContextMenu.Instance; let target = e.target as any; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span> @@ -906,7 +911,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ); const appearance = cm.findByDescription('Appearance...'); const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : []; - + // appearanceItems.push({ + // description: 'Find image tags', + // event: this.findImageTags, + // icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye', + // }); appearanceItems.push({ description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => { @@ -969,7 +978,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB icon: 'star', }); optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); - optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' }); + // optionItems.push({ description: `Make AI Flashcards`, event: () => this.makeAIFlashcards(), icon: 'lightbulb' }); optionItems.push({ description: `Ask GPT-3`, event: this.askGPT, icon: 'lightbulb' }); this._props.renderDepth && optionItems.push({ @@ -994,6 +1003,61 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); }; + findImageTags = async () => { + const c = this.ProseRef?.getElementsByTagName('img'); + if (c) { + for (let i of c) { + console.log(i); + + // console.log(canvas.toDataURL()); + // canvas.style.zIndex = '2000000'; + // document.body.appendChild(canvas); + if (i.className !== 'ProseMirror-separator') this.getImageDesc(i.src); + } + } + // console.log('HI' + this.ProseRef?.getElementsByTagName('img')); + }; + + static imageUrlToBase64 = async (imageUrl: string): Promise<string> => { + try { + const response = await fetch(imageUrl); + const blob = await response.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = error => reject(error); + }); + } catch (error) { + console.error('Error:', error); + throw error; + } + }; + + getImageDesc = async (u: string) => { + // if (StrCast(this.dataDoc.description)) return StrCast(this.dataDoc.description); // Return existing description + // const { href } = (u as URLField).url; + const hrefParts = u.split('.'); + const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; + try { + const hrefBase64 = await FormattedTextBox.imageUrlToBase64(u); + const response = await gptImageLabel( + hrefBase64, + 'Make flashcards out of this text and image with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: ' + (this.dataDoc.text as RichTextField)?.Text + ); + //const response = await gptImageLabel(u, 'Make flashcards out of this text with each question and answer labeled as question and answer. Do not label each flashcard and do not include asterisks: '); + // console.log(response); + AnchorMenu.Instance.transferToFlashcard(response || 'Something went wrong', NumCast(this.dataDoc['x']), NumCast(this.dataDoc['y'])); + // this._props.addto_; + // this.Document[DocData].description = response.trim(); + // return response; // Return the response + } catch (error) { + console.log('Error'); + } + // return ''; + }; + animateRes = (resIndex: number, newText: string) => { if (resIndex < newText.length) { const marks = this._editorView?.state.storedMarks ?? []; @@ -1341,7 +1405,33 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB { fireImmediately: true } ); this.tryUpdateScrollHeight(); + + if (this.Document.image) { + // const node = schema.nodes.dashDoc.create({ + // width: 200, + // height: 200, + // title: 'dashDoc', + // docId: DocCast(this.Document.image)[Id], + // float: 'unset', + // }); + + // DocCast(this.Document.image)._freeform_fitContentsToBox = true; + // Doc.SetContainer(DocCast(this.Document.image), this.Document); + // const view = this._editorView!; + // try { + // this._inDrop = true; + // const pos = view.posAtCoords({ left: 0, top: 0 })?.pos; + // pos && view.dispatch(view.state.tr.insert(pos, node)); + // } catch (err) { + // console.log('Drop failed', err); + // } + // console.log('LKSDFLJ'); + this.addDocument?.(DocCast(this.Document.image)); + } + + //if (this.Document.image) this.addDocument?.(DocCast(this.Document.image)); setTimeout(this.tryUpdateScrollHeight, 250); + AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument; } clipboardTextSerializer = (slice: Slice): string => { @@ -2001,6 +2091,49 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB </Tooltip> ); } + + @computed get answerIcon() { + return ( + <Tooltip + title={ + <div className="answer-tooltip" style={{ minWidth: '150px' }}> + {StrCast(this.Document.quiz)} + </div> + }> + <div className="answer-tool-tip"> + <FontAwesomeIcon className="q-icon" icon="circle" color="white" /> + <FontAwesomeIcon className="answer-icon" icon="question" /> + </div> + </Tooltip> + ); + } + + @computed get editAnswer() { + return ( + <Tooltip + title={ + <div className="answer-tooltip" style={{ minWidth: '150px' }}> + {this._editLabel ? 'save' : 'edit correct answer'} + </div> + }> + <div className="answer-tool-tip" onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => this.editLabelAnswer())}> + <FontAwesomeIcon className="edit-icon" color={this._editLabel ? 'white' : 'black'} icon="pencil" size="sm" /> + </div> + </Tooltip> + ); + } + + editLabelAnswer = () => { + // when click the pencil, set the text to the quiz content. when click off, set the quiz text to that and set textbox to nothing. + if (!this._editLabel) { + this.dataDoc.text = StrCast(this.Document.quiz); + } else { + this.Document.quiz = RTFCast(this.dataDoc.text).Text; + this.dataDoc.text = ''; + } + this._editLabel = !this._editLabel; + }; + get fieldKey() { return this._fieldKey; } @@ -2116,9 +2249,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB /> </div> {this.noSidebar || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection} - {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden ? null : this.sidebarHandle} + {this.noSidebar || this.Document._layout_noSidebar || this.Document._createDocOnCR || this.layoutDoc._chromeHidden || this.Document.quiz ? null : this.sidebarHandle} {this.audioHandle} {this.layoutDoc._layout_enableAltContentUI && !this.layoutDoc._chromeHidden ? this.overlayAlternateIcon : null} + {this.Document.showQuiz ? this.answerIcon : null} + {this.Document.showQuiz ? this.editAnswer : null} </div> </div> ); |