diff --git a/packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts b/packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts index b5d5a28c9..b81fb8dc1 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts @@ -5,16 +5,18 @@ import bridge from '../../../services/bridge'; import { ContextMenuItemType, ContextMenuOptions, ContextMenuItems, resourceInfo, textToDataUri, svgUriToPng, svgDimensions } from './contextMenuUtils'; const Menu = bridge().Menu; const MenuItem = bridge().MenuItem; -import Resource from '@joplin/lib/models/Resource'; +import Resource, { resourceOcrStatusToString } from '@joplin/lib/models/Resource'; import BaseItem from '@joplin/lib/models/BaseItem'; import BaseModel, { ModelType } from '@joplin/lib/BaseModel'; import { processPastedHtml } from './resourceHandling'; -import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types'; +import { NoteEntity, ResourceEntity, ResourceOcrStatus } from '@joplin/lib/services/database/types'; import { TinyMceEditorEvents } from '../NoteBody/TinyMCE/utils/types'; import { itemIsReadOnlySync, ItemSlice } from '@joplin/lib/models/utils/readOnly'; import Setting from '@joplin/lib/models/Setting'; import ItemChange from '@joplin/lib/models/ItemChange'; import { HtmlToMarkdownHandler, MarkupToHtmlHandler } from './types'; +import shim from '@joplin/lib/shim'; +import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils'; const fs = require('fs-extra'); const { writeFile } = require('fs-extra'); const { clipboard } = require('electron'); @@ -135,6 +137,21 @@ export function menuItems(dispatch: Function, htmlToMd: HtmlToMarkdownHandler, m }, isActive: (itemType: ContextMenuItemType, options: ContextMenuOptions) => !options.textToCopy && itemType === ContextMenuItemType.Image || itemType === ContextMenuItemType.Resource, }, + copyOcrText: { + label: _('View OCR text'), + onAction: async (options: ContextMenuOptions) => { + const { resource } = await resourceInfo(options); + + if (resource.ocr_status === ResourceOcrStatus.Done) { + const tempFilePath = `${Setting.value('tempDir')}/${resource.id}_ocr.txt`; + await shim.fsDriver().writeFile(tempFilePath, resource.ocr_text, 'utf8'); + await openFileWithExternalEditor(tempFilePath, bridge()); + } else { + bridge().showInfoMessageBox(_('This attachment does not have OCR data (Status: %s)', resourceOcrStatusToString(resource.ocr_status))); + } + }, + isActive: (itemType: ContextMenuItemType, _options: ContextMenuOptions) => itemType === ContextMenuItemType.Resource, + }, copyPathToClipboard: { label: _('Copy path to clipboard'), onAction: async (options: ContextMenuOptions) => { diff --git a/packages/lib/models/Resource.ts b/packages/lib/models/Resource.ts index db26dd2cd..a69c3a602 100644 --- a/packages/lib/models/Resource.ts +++ b/packages/lib/models/Resource.ts @@ -26,6 +26,17 @@ import ActionLogger from '../utils/ActionLogger'; import isSqliteSyntaxError from '../services/database/isSqliteSyntaxError'; import { internalUrl, isResourceUrl, isSupportedImageMimeType, resourceFilename, resourceFullPath, resourcePathToId, resourceRelativePath, resourceUrlToId } from './utils/resourceUtils'; +export const resourceOcrStatusToString = (status: ResourceOcrStatus) => { + const s = { + [ResourceOcrStatus.Todo]: _('Idle'), + [ResourceOcrStatus.Processing]: _('Processing'), + [ResourceOcrStatus.Error]: _('Error'), + [ResourceOcrStatus.Done]: _('Done'), + }; + + return s[status]; +}; + export default class Resource extends BaseItem { public static IMAGE_MAX_DIMENSION = 1920; @@ -630,4 +641,8 @@ export default class Resource extends BaseItem { return output; } + public static load(id: string, options: LoadOptions = null): Promise { + return super.load(id, options); + } + }