You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Desktop: Performance: Faster startup and smaller application size (#12366)
This commit is contained in:
		| @@ -578,6 +578,7 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js | ||||
| packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js | ||||
| packages/app-desktop/services/sortOrder/notesSortOrderUtils.js | ||||
| packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js | ||||
| packages/app-desktop/tools/bundleJs.js | ||||
| packages/app-desktop/tools/copy7Zip.js | ||||
| packages/app-desktop/tools/generateLatestArm64Yml.js | ||||
| packages/app-desktop/tools/githubReleasesUtils.js | ||||
| @@ -592,6 +593,7 @@ packages/app-desktop/utils/customProtocols/constants.js | ||||
| packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js | ||||
| packages/app-desktop/utils/customProtocols/handleCustomProtocols.js | ||||
| packages/app-desktop/utils/customProtocols/registerCustomProtocols.js | ||||
| packages/app-desktop/utils/getAssetPath.js | ||||
| packages/app-desktop/utils/initializeCommandService.js | ||||
| packages/app-desktop/utils/isSafeToOpen.test.js | ||||
| packages/app-desktop/utils/isSafeToOpen.js | ||||
|   | ||||
| @@ -23,6 +23,7 @@ module.exports = { | ||||
| 		'FileSystemCreateWritableOptions': 'readonly', | ||||
| 		'FileSystemHandle': 'readonly', | ||||
| 		'IDBTransactionMode': 'readonly', | ||||
| 		'globalThis': 'readonly', | ||||
|  | ||||
| 		// ServiceWorker | ||||
| 		'ExtendableEvent': 'readonly', | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -46,6 +46,7 @@ sync_staging.sh | ||||
| TODO.md | ||||
| packages/tools/commit_hook.txt | ||||
| packages/tools/github_oauth_token.txt | ||||
| packages/app-desktop/main-html-out.js | ||||
| lerna-debug.log | ||||
| .env | ||||
| docs/**/*.mustache | ||||
| @@ -552,6 +553,7 @@ packages/app-desktop/services/sortOrder/PerFolderSortOrderService.js | ||||
| packages/app-desktop/services/sortOrder/notesSortOrderUtils.test.js | ||||
| packages/app-desktop/services/sortOrder/notesSortOrderUtils.js | ||||
| packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js | ||||
| packages/app-desktop/tools/bundleJs.js | ||||
| packages/app-desktop/tools/copy7Zip.js | ||||
| packages/app-desktop/tools/generateLatestArm64Yml.js | ||||
| packages/app-desktop/tools/githubReleasesUtils.js | ||||
| @@ -566,6 +568,7 @@ packages/app-desktop/utils/customProtocols/constants.js | ||||
| packages/app-desktop/utils/customProtocols/handleCustomProtocols.test.js | ||||
| packages/app-desktop/utils/customProtocols/handleCustomProtocols.js | ||||
| packages/app-desktop/utils/customProtocols/registerCustomProtocols.js | ||||
| packages/app-desktop/utils/getAssetPath.js | ||||
| packages/app-desktop/utils/initializeCommandService.js | ||||
| packages/app-desktop/utils/isSafeToOpen.test.js | ||||
| packages/app-desktop/utils/isSafeToOpen.js | ||||
|   | ||||
| @@ -1,6 +1,22 @@ | ||||
|  | ||||
| # We remove the `canvas` optional dependency because electron-rebuild fails to build it, and | ||||
| # the `canvas` API is already part of Electron | ||||
| diff --git a/build/pdf.js b/build/pdf.js | ||||
| index 4acf16b1d6f9351bda1a98649ea4f926618fe617..f63dbc6050ca63ca8e8ed982edea134103fa15dd 100644 | ||||
| --- a/build/pdf.js | ||||
| +++ b/build/pdf.js | ||||
| @@ -6244,8 +6244,9 @@ class NodeFilterFactory extends _base_factory.BaseFilterFactory {} | ||||
|  exports.NodeFilterFactory = NodeFilterFactory; | ||||
|  class NodeCanvasFactory extends _base_factory.BaseCanvasFactory { | ||||
|    _createCanvas(width, height) { | ||||
| -    const Canvas = require("canvas"); | ||||
| -    return Canvas.createCanvas(width, height); | ||||
| +    throw new Error('Node canvas disabled'); | ||||
| +    // const Canvas = require("canvas"); | ||||
| +    // return Canvas.createCanvas(width, height); | ||||
|    } | ||||
|  } | ||||
|  exports.NodeCanvasFactory = NodeCanvasFactory; | ||||
| diff --git a/package.json b/package.json | ||||
| index 105811f53d508486e08a60dc1b6e437cd24d7427..dea6a4e6612c4a4006cc482e46ff5270dcfda1e5 100644 | ||||
| --- a/package.json | ||||
|   | ||||
| @@ -15,9 +15,9 @@ | ||||
|   "scripts": { | ||||
|     "buildApiDoc": "yarn workspace joplin start apidoc ../../readme/api/references/rest_api.md", | ||||
|     "buildScriptIndexes": "node packages/tools/gulp/tasks/buildScriptIndexesRun.js", | ||||
|     "buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological run build && yarn tsc", | ||||
|     "buildParallel": "yarn workspaces foreach --verbose --interlaced --parallel --jobs 2 --topological-dev run build && yarn tsc", | ||||
|     "buildPluginDoc": "cd packages/generate-plugin-doc && yarn buildPluginDoc_", | ||||
|     "buildSequential": "yarn workspaces foreach --verbose --interlaced --topological run build && yarn tsc", | ||||
|     "buildSequential": "yarn workspaces foreach --verbose --interlaced --topological-dev run build && yarn tsc", | ||||
|     "buildServerDocker": "node packages/tools/buildServerDocker.js", | ||||
|     "buildSettingJsonSchema": "yarn workspace joplin start settingschema ../../../joplin-website/docs/schema/settings.json", | ||||
|     "buildTranslations": "node packages/tools/build-translation.js", | ||||
| @@ -110,6 +110,9 @@ | ||||
|     "app-builder-lib@24.13.3": "patch:app-builder-lib@npm%3A24.13.3#./.yarn/patches/app-builder-lib-npm-24.13.3-86a66c0bf3.patch", | ||||
|     "react-native-sqlite-storage@6.0.1": "patch:react-native-sqlite-storage@npm%3A6.0.1#./.yarn/patches/react-native-sqlite-storage-npm-6.0.1-8369d747bd.patch", | ||||
|     "react-native-paper@5.13.1": "patch:react-native-paper@npm%3A5.13.1#./.yarn/patches/react-native-paper-npm-5.13.1-f153e542e2.patch", | ||||
|     "react-native-popup-menu@0.17.0": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch" | ||||
|     "react-native-popup-menu@0.17.0": "patch:react-native-popup-menu@npm%3A0.17.0#./.yarn/patches/react-native-popup-menu-npm-0.17.0-8b745d88dd.patch", | ||||
|     "pdfjs-dist@2.16.105": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch", | ||||
|     "pdfjs-dist@*": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch", | ||||
|     "pdfjs-dist@3.11.174": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import Note from '@joplin/lib/models/Note'; | ||||
| import Tag from '@joplin/lib/models/Tag'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| import { reg } from '@joplin/lib/registry.js'; | ||||
| import { fileExtension } from '@joplin/lib/path-utils'; | ||||
| import { dirname, fileExtension } from '@joplin/lib/path-utils'; | ||||
| import { splitCommandString } from '@joplin/utils'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import { pathExists, readFile, readdirSync } from 'fs-extra'; | ||||
| @@ -397,8 +397,12 @@ class Application extends BaseApplication { | ||||
| 	} | ||||
|  | ||||
| 	public async start(argv: string[]) { | ||||
| 		const keychainEnabled = this.checkIfKeychainEnabled(argv); | ||||
| 		// TODO: Currently, `pluginAssetDir` needs to be set differently for each platform and requires | ||||
| 		// a call to Setting.setConstant. Ideally, this would be done in a way that requires users to | ||||
| 		// set this constant on startup. | ||||
| 		Setting.setConstant('pluginAssetDir', `${dirname(require.resolve('@joplin/renderer'))}/assets`); | ||||
|  | ||||
| 		const keychainEnabled = this.checkIfKeychainEnabled(argv); | ||||
| 		argv = await super.start(argv, { keychainEnabled }); | ||||
|  | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
|   | ||||
							
								
								
									
										4
									
								
								packages/app-desktop/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								packages/app-desktop/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -25,3 +25,7 @@ build/7zip/7za | ||||
| build/7zip/7za.exe | ||||
| sentry.properties | ||||
| downloads/ | ||||
|  | ||||
| # Bundler output | ||||
| *.js.meta.json | ||||
| *.bundle.js | ||||
|   | ||||
| @@ -246,7 +246,7 @@ export class Bridge { | ||||
| 	// version of electron-context-menu. | ||||
| 	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied | ||||
| 	public setupContextMenu(_spellCheckerMenuItemsHandler: Function) { | ||||
| 		require('electron-context-menu')({ | ||||
| 		require('./services/electron-context-menu')({ | ||||
| 			allWindows: [this.mainWindow()], | ||||
|  | ||||
| 			electronApp: this.electronApp(), | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { _ } from '@joplin/lib/locale'; | ||||
| import { stateUtils } from '@joplin/lib/reducer'; | ||||
| import ExternalEditWatcher from '@joplin/lib/services/ExternalEditWatcher'; | ||||
| import Note from '@joplin/lib/models/Note'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import bridge from '../services/bridge'; | ||||
|  | ||||
| export const declaration: CommandDeclaration = { | ||||
| 	name: 'startExternalEditing', | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import * as React from 'react'; | ||||
| import ButtonBar from './ConfigScreen/ButtonBar'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import bridge from '../services/bridge'; | ||||
|  | ||||
| const { connect } = require('react-redux'); | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| const { themeStyle } = require('@joplin/lib/theme'); | ||||
| const Shared = require('@joplin/lib/components/shared/dropbox-login-shared'); | ||||
|  | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import * as React from 'react'; | ||||
| import { FolderIcon, FolderIconType } from '@joplin/lib/services/database/types'; | ||||
| import EmojiBox from './EmojiBox'; | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import ButtonBar from './ConfigScreen/ButtonBar'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import { clipboard } from 'electron'; | ||||
| import Button, { ButtonLevel } from './Button/Button'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import { uuidgen } from '@joplin/lib/uuid'; | ||||
| import { Dispatch } from 'redux'; | ||||
| import { reducer, defaultState, generateApplicationConfirmUrl, checkIfLoginWasSuccessful } from '@joplin/lib/services/joplinCloudUtils'; | ||||
| @@ -12,6 +11,7 @@ import { AppState } from '../app.reducer'; | ||||
| import Logger from '@joplin/utils/Logger'; | ||||
| import { reg } from '@joplin/lib/registry'; | ||||
| import JoplinCloudSignUpCallToAction from './JoplinCloudSignUpCallToAction'; | ||||
| import bridge from '../services/bridge'; | ||||
|  | ||||
| const logger = Logger.create('JoplinCloudLoginScreen'); | ||||
| const { connect } = require('react-redux'); | ||||
| @@ -62,7 +62,7 @@ const JoplinCloudScreenComponent = (props: Props) => { | ||||
|  | ||||
| 	const onAuthorizeClicked = async () => { | ||||
| 		const url = await generateApplicationConfirmUrl(confirmUrl(applicationAuthId)); | ||||
| 		bridge().openExternal(url); | ||||
| 		void bridge().openExternal(url); | ||||
| 		onButtonUsed(); | ||||
| 	}; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import * as React from 'react'; | ||||
| import { useEffect, useImperativeHandle, useState, useRef, useCallback, forwardRef } from 'react'; | ||||
| import { PluginStates } from '@joplin/lib/services/plugins/reducer'; | ||||
|  | ||||
| import * as CodeMirror from 'codemirror'; | ||||
| import CodeMirror from 'codemirror'; | ||||
|  | ||||
| import 'codemirror/addon/comment/comment'; | ||||
| import 'codemirror/addon/dialog/dialog'; | ||||
| @@ -32,54 +32,41 @@ import Setting from '@joplin/lib/models/Setting'; | ||||
|  | ||||
| // import eventManager from '@joplin/lib/eventManager'; | ||||
|  | ||||
| import { reg } from '@joplin/lib/registry'; | ||||
| import { focus } from '@joplin/lib/utils/focusHandler'; | ||||
|  | ||||
| // Based on http://pypl.github.io/PYPL.html | ||||
| const topLanguages = [ | ||||
| 	'python', | ||||
| 	'clike', | ||||
| 	'javascript', | ||||
| 	'jsx', | ||||
| 	'php', | ||||
| 	'r', | ||||
| 	'swift', | ||||
| 	'go', | ||||
| 	'vb', | ||||
| 	'vbscript', | ||||
| 	'ruby', | ||||
| 	'rust', | ||||
| 	'dart', | ||||
| 	'lua', | ||||
| 	'groovy', | ||||
| 	'perl', | ||||
| 	'cobol', | ||||
| 	'julia', | ||||
| 	'haskell', | ||||
| 	'pascal', | ||||
| 	'css', | ||||
| import 'codemirror/mode/python/python'; | ||||
| import 'codemirror/mode/clike/clike'; | ||||
| import 'codemirror/mode/javascript/javascript'; | ||||
| import 'codemirror/mode/jsx/jsx'; | ||||
| import 'codemirror/mode/php/php'; | ||||
| import 'codemirror/mode/r/r'; | ||||
| import 'codemirror/mode/swift/swift'; | ||||
| import 'codemirror/mode/go/go'; | ||||
| import 'codemirror/mode/vb/vb'; | ||||
| import 'codemirror/mode/vbscript/vbscript'; | ||||
| import 'codemirror/mode/ruby/ruby'; | ||||
| import 'codemirror/mode/rust/rust'; | ||||
| import 'codemirror/mode/dart/dart'; | ||||
| import 'codemirror/mode/lua/lua'; | ||||
| import 'codemirror/mode/groovy/groovy'; | ||||
| import 'codemirror/mode/perl/perl'; | ||||
| import 'codemirror/mode/cobol/cobol'; | ||||
| import 'codemirror/mode/julia/julia'; | ||||
| import 'codemirror/mode/haskell/haskell'; | ||||
| import 'codemirror/mode/pascal/pascal'; | ||||
| import 'codemirror/mode/css/css'; | ||||
|  | ||||
| 	// Additional languages, not in the PYPL list | ||||
| 	'xml', // For HTML too | ||||
| 	'markdown', | ||||
| 	'yaml', | ||||
| 	'shell', | ||||
| 	'dockerfile', | ||||
| 	'diff', | ||||
| 	'erlang', | ||||
| 	'sql', | ||||
| ]; | ||||
| // Load Top Modes | ||||
| for (let i = 0; i < topLanguages.length; i++) { | ||||
| 	const mode = topLanguages[i]; | ||||
| // Additional languages, not in the PYPL lis; | ||||
| import 'codemirror/mode/xml/xml'; // For HTML too | ||||
| import 'codemirror/mode/markdown/markdown'; | ||||
| import 'codemirror/mode/yaml/yaml'; | ||||
| import 'codemirror/mode/shell/shell'; | ||||
| import 'codemirror/mode/dockerfile/dockerfile'; | ||||
| import 'codemirror/mode/diff/diff'; | ||||
| import 'codemirror/mode/erlang/erlang'; | ||||
| import 'codemirror/mode/sql/sql'; | ||||
|  | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 	if (CodeMirror.modeInfo.find((m: any) => m.mode === mode)) { | ||||
| 		require(`codemirror/mode/${mode}/${mode}`); | ||||
| 	} else { | ||||
| 		reg.logger().error('Cannot find CodeMirror mode: ', mode); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| export interface EditorProps { | ||||
| 	value: string; | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import shim from '@joplin/lib/shim'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| import Note from '@joplin/lib/models/Note'; | ||||
| import Resource from '@joplin/lib/models/Resource'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import ResourceFetcher from '@joplin/lib/services/ResourceFetcher'; | ||||
| import htmlUtils from '@joplin/lib/htmlUtils'; | ||||
| import rendererHtmlUtils, { extractHtmlBody, removeWrappingParagraphAndTrailingEmptyElements } from '@joplin/renderer/htmlUtils'; | ||||
| @@ -15,6 +14,7 @@ import { fileExtension, filename, safeFileExtension, safeFilename } from '@jopli | ||||
| const joplinRendererUtils = require('@joplin/renderer').utils; | ||||
| const { clipboard } = require('electron'); | ||||
| import * as mimeUtils from '@joplin/lib/mime-utils'; | ||||
| import bridge from '../../../services/bridge'; | ||||
| const md5 = require('md5'); | ||||
| const path = require('path'); | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,8 @@ import { ForwardedRef, forwardRef, RefObject, useContext, useEffect, useImperati | ||||
| import { WindowIdContext } from './NewWindowOrIFrame'; | ||||
| import useDocument from './hooks/useDocument'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import getAssetPath from '../utils/getAssetPath'; | ||||
| import { toForwardSlashes } from '@joplin/utils/path'; | ||||
|  | ||||
| interface Props { | ||||
| 	// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied | ||||
| @@ -240,7 +242,7 @@ const NoteTextViewer = forwardRef((props: Props, ref: ForwardedRef<NoteViewerCon | ||||
| 			allow='clipboard-write=(self) fullscreen=(self) autoplay=(self) local-fonts=(self) encrypted-media=(self)' | ||||
| 			allowFullScreen={true} | ||||
| 			aria-label={_('Note viewer')} | ||||
| 			src={`joplin-content://note-viewer/${__dirname}/note-viewer/index.html`} | ||||
| 			src={`joplin-content://note-viewer/${toForwardSlashes(getAssetPath('gui/note-viewer/index.html'))}`} | ||||
| 		></iframe> | ||||
| 	); | ||||
| }); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { _ } from '@joplin/lib/locale'; | ||||
| const { connect } = require('react-redux'); | ||||
| import { reg } from '@joplin/lib/registry'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import bridge from '../services/bridge'; | ||||
| const { themeStyle } = require('@joplin/lib/theme'); | ||||
| const { OneDriveApiNodeUtils } = require('@joplin/lib/onedrive-api-node-utils.js'); | ||||
|  | ||||
| @@ -66,7 +66,7 @@ class OneDriveLoginScreenComponent extends React.Component<any, any> { | ||||
| 		const logComps = []; | ||||
| 		for (const l of this.state.authLog) { | ||||
| 			if (l.text.indexOf('http:') === 0) { | ||||
| 				logComps.push(<a key={l.key} style={theme.urlStyle} href="#" onClick={() => { bridge().openExternal(l.text); }}>{l.text}</a>); | ||||
| 				logComps.push(<a key={l.key} style={theme.urlStyle} href="#" onClick={() => { void bridge().openExternal(l.text); }}>{l.text}</a>); | ||||
| 			} else { | ||||
| 				logComps.push(<p key={l.key} style={theme.textStyle}>{l.text}</p>); | ||||
| 			} | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import MoveButtons, { MoveButtonClickEvent } from './MoveButtons'; | ||||
| import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootMessage } from './utils/style'; | ||||
| import type { ResizeCallback, ResizeStartCallback } from 're-resizable'; | ||||
| import Dialog from '../Dialog'; | ||||
| import * as EventEmitter from 'events'; | ||||
| import EventEmitter = require('events'); | ||||
| import LayoutItemContainer from './LayoutItemContainer'; | ||||
|  | ||||
| interface OnResizeEvent { | ||||
|   | ||||
| @@ -343,4 +343,4 @@ const mapStateToProps = (state: any) => ({ | ||||
|  | ||||
| const ResourceScreen = connect(mapStateToProps)(ResourceScreenComponent); | ||||
|  | ||||
| module.exports = { ResourceScreen }; | ||||
| export default ResourceScreen; | ||||
|   | ||||
| @@ -21,7 +21,7 @@ import DialogButtonRow, { ButtonSpec, ClickEvent, ClickEventHandler } from './Di | ||||
| import Dialog from './Dialog'; | ||||
| import StyleSheetContainer from './StyleSheets/StyleSheetContainer'; | ||||
| import ImportScreen from './ImportScreen'; | ||||
| const { ResourceScreen } = require('./ResourceScreen.js'); | ||||
| import ResourceScreen from './ResourceScreen'; | ||||
| import Navigator from './Navigator'; | ||||
| import WelcomeUtils from '@joplin/lib/WelcomeUtils'; | ||||
| import JoplinCloudLoginScreen from './JoplinCloudLoginScreen'; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import { WindowControl } from '../utils/useWindowControl'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import bridge from '../../../services/bridge'; | ||||
|  | ||||
| export const declaration: CommandDeclaration = { | ||||
| 	name: 'print', | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import Folder from '@joplin/lib/models/Folder'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import bridge from '../../../services/bridge'; | ||||
|  | ||||
| export const declaration: CommandDeclaration = { | ||||
| 	name: 'renameFolder', | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; | ||||
| import { _ } from '@joplin/lib/locale'; | ||||
| import Tag from '@joplin/lib/models/Tag'; | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| import bridge from '../../../services/bridge'; | ||||
|  | ||||
| export const declaration: CommandDeclaration = { | ||||
| 	name: 'renameTag', | ||||
|   | ||||
| @@ -4,9 +4,18 @@ const compileSass = require('@joplin/tools/compileSass'); | ||||
| const compilePackageInfo = require('@joplin/tools/compilePackageInfo'); | ||||
| import buildDefaultPlugins from '@joplin/default-plugins/commands/buildAll'; | ||||
| import copy7Zip from './tools/copy7Zip'; | ||||
| import bundleJs from './tools/bundleJs'; | ||||
| import { remove } from 'fs-extra'; | ||||
|  | ||||
| const tasks = { | ||||
| 	bundle: { | ||||
| 		fn: () => bundleJs(false), | ||||
| 	}, | ||||
| 	// Bundles and computes additional information that can be analysed with | ||||
| 	// locally or with https://esbuild.github.io/analyze/. | ||||
| 	bundleWithStats: { | ||||
| 		fn: () => bundleJs(true), | ||||
| 	}, | ||||
| 	compileScripts: { | ||||
| 		fn: require('./tools/compileScripts'), | ||||
| 	}, | ||||
| @@ -54,7 +63,7 @@ const tasks = { | ||||
|  | ||||
| utils.registerGulpTasks(gulp, tasks); | ||||
|  | ||||
| const buildBeforeStartParallel = [ | ||||
| const buildBeforeStartParallel = gulp.parallel( | ||||
| 	'compileScripts', | ||||
| 	'compilePackageInfo', | ||||
| 	'copyPluginAssets', | ||||
| @@ -62,14 +71,21 @@ const buildBeforeStartParallel = [ | ||||
| 	'updateIgnoredTypeScriptBuild', | ||||
| 	'buildScriptIndexes', | ||||
| 	'compileSass', | ||||
| ]; | ||||
| ); | ||||
| const buildRequiresTsc = gulp.series('bundle'); | ||||
|  | ||||
| gulp.task('before-start', gulp.parallel(...buildBeforeStartParallel)); | ||||
| gulp.task('before-start', gulp.series( | ||||
| 	buildRequiresTsc, | ||||
| 	buildBeforeStartParallel, | ||||
| )); | ||||
| gulp.task('before-dist', buildRequiresTsc); | ||||
|  | ||||
| const buildAllSequential = [ | ||||
| 	'before-start', | ||||
| // Since "build" runs before "tsc", exclude tasks that require | ||||
| // other packages to be built (i.e. don't include buildRequiresTsc). | ||||
| const buildSequential = [ | ||||
| 	buildBeforeStartParallel, | ||||
| 	'copyDefaultPluginsAssets', | ||||
| 	'buildDefaultPlugins', | ||||
| ]; | ||||
|  | ||||
| gulp.task('build', gulp.series(buildAllSequential)); | ||||
| gulp.task('build', gulp.series(buildSequential)); | ||||
|   | ||||
| @@ -12,11 +12,11 @@ | ||||
| 		<link rel="stylesheet" href="style.min.css"> | ||||
|  | ||||
| 		<script src="vendor/lib/smalltalk/dist/smalltalk.min.js"></script> | ||||
| 		<script src="./node_modules/tesseract.js/dist/tesseract.min.js"></script> | ||||
| 		<script src="vendor/lib/tesseract.js/dist/tesseract.min.js"></script> | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<div id="react-root"></div> | ||||
| 		<script src="./utils/window/eventHandlerOverrides.js"></script> | ||||
| 		<script src="main-html.js"></script> | ||||
| 		<script src="./main-html.bundle.js"></script> | ||||
| 	</body> | ||||
| </html> | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| import { ElectronApplication, expect, Locator, Page } from '@playwright/test'; | ||||
| import MainScreen from './MainScreen'; | ||||
| import activateMainMenuItem from '../util/activateMainMenuItem'; | ||||
| import { msleep } from '@joplin/utils/time'; | ||||
|  | ||||
| export default class GoToAnything { | ||||
| 	public readonly containerLocator: Locator; | ||||
| @@ -38,6 +39,8 @@ export default class GoToAnything { | ||||
| 		// This expect.poll retries the search if it initially fails. | ||||
| 		await expect.poll(async () => { | ||||
| 			await this.inputLocator.clear(); | ||||
| 			// Pause to help ensure that the change in the search input is detected | ||||
| 			await msleep(300); | ||||
| 			await this.inputLocator.fill(query); | ||||
| 			try { | ||||
| 				await expect(resultLocator).toBeVisible({ timeout: 1000 }); | ||||
| @@ -47,7 +50,7 @@ export default class GoToAnything { | ||||
| 				return error; | ||||
| 			} | ||||
| 			return true; | ||||
| 		}, { timeout: 10_000 }).toBe(true); | ||||
| 		}, { timeout: 15_000 }).toBe(true); | ||||
| 	} | ||||
|  | ||||
| 	public async expectToBeClosed() { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { dirname, resolve } from 'path'; | ||||
| const createStartupArgs = (profileDirectory: string) => { | ||||
| 	// Input paths need to be absolute when running from VSCode | ||||
| 	const baseDirectory = dirname(dirname(__dirname)); | ||||
| 	const mainPath = resolve(baseDirectory, 'main.js'); | ||||
| 	const mainPath = resolve(baseDirectory, 'main.bundle.js'); | ||||
|  | ||||
| 	// We need to run with --env dev to disable the single instance check. | ||||
| 	return [ | ||||
|   | ||||
| @@ -19,6 +19,7 @@ jest.mock('@electron/remote', () => { | ||||
| 				default: {}, | ||||
| 			}; | ||||
| 		}, | ||||
| 		getGlobal: () => ({}), | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| // Disable React message in console "Download the React DevTools for a better development experience" | ||||
| // https://stackoverflow.com/questions/42196819/disable-hide-download-the-react-devtools#42196820 | ||||
| // eslint-disable-next-line no-undef | ||||
| __REACT_DEVTOOLS_GLOBAL_HOOK__ = { | ||||
| window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { | ||||
| 	supportsFiber: true, | ||||
| 	inject: function() {}, | ||||
| 	onCommitFiberRoot: function() {}, | ||||
| @@ -22,9 +22,9 @@ const Setting = require('@joplin/lib/models/Setting').default; | ||||
| const Revision = require('@joplin/lib/models/Revision').default; | ||||
| const Logger = require('@joplin/utils/Logger').default; | ||||
| const FsDriverNode = require('@joplin/lib/fs-driver-node').default; | ||||
| const bridge = require('./services/bridge').default; | ||||
| const shim = require('@joplin/lib/shim').default; | ||||
| const { shimInit } = require('@joplin/lib/shim-init-node.js'); | ||||
| const bridge = require('@electron/remote').require('./bridge').default; | ||||
| const EncryptionService = require('@joplin/lib/services/e2ee/EncryptionService').default; | ||||
| const FileApiDriverLocal = require('@joplin/lib/file-api-driver-local').default; | ||||
| const React = require('react'); | ||||
| @@ -34,6 +34,9 @@ const pdfJs = require('pdfjs-dist'); | ||||
| const { isAppleSilicon } = require('is-apple-silicon'); | ||||
| require('@sentry/electron/renderer'); | ||||
|  | ||||
| // Allows components to use React as a global | ||||
| window.React = React; | ||||
|  | ||||
|  | ||||
| const main = async () => { | ||||
| 	if (bridge().env() === 'dev') { | ||||
| @@ -83,6 +86,7 @@ const main = async () => { | ||||
|  | ||||
| 	Setting.setConstant('appId', bridge().appId()); | ||||
| 	Setting.setConstant('appType', 'desktop'); | ||||
| 	Setting.setConstant('pluginAssetDir', `${__dirname}/pluginAssets`); | ||||
|  | ||||
| 	// eslint-disable-next-line no-console | ||||
| 	console.info(`appId: ${Setting.value('appId')}`); | ||||
|   | ||||
| @@ -11,7 +11,7 @@ const envFromArgs = require('@joplin/lib/envFromArgs'); | ||||
| const packageInfo = require('./packageInfo.js'); | ||||
| const { isCallbackUrl } = require('@joplin/lib/callbackUrlUtils'); | ||||
| const determineBaseAppDirs = require('@joplin/lib/determineBaseAppDirs').default; | ||||
| const registerCustomProtocols = require('./utils/customProtocols/registerCustomProtocols.js').default; | ||||
| const registerCustomProtocols = require('./utils/customProtocols/registerCustomProtocols').default; | ||||
|  | ||||
| // Electron takes the application name from package.json `name` and | ||||
| // displays this in the tray icon toolip and message box titles, however in | ||||
| @@ -74,7 +74,7 @@ const wrapper = new ElectronAppWrapper(electronApp, { | ||||
| 	env, profilePath: rootProfileDir, isDebugMode, initialCallbackUrl, isEndToEndTesting, | ||||
| }); | ||||
|  | ||||
| initBridge(wrapper, appId, appName, rootProfileDir, autoUploadCrashDumps, altInstanceId); | ||||
| globalThis.joplinBridge = initBridge(wrapper, appId, appName, rootProfileDir, autoUploadCrashDumps, altInstanceId); | ||||
|  | ||||
| wrapper.start().catch((error) => { | ||||
| 	console.error('Electron App fatal error:'); | ||||
|   | ||||
| @@ -2,18 +2,19 @@ | ||||
|   "name": "@joplin/app-desktop", | ||||
|   "version": "3.4.1", | ||||
|   "description": "Joplin for Desktop", | ||||
|   "main": "main.js", | ||||
|   "main": "main.bundle.js", | ||||
|   "private": true, | ||||
|   "scripts": { | ||||
|     "dist": "yarn electronRebuild && npx electron-builder", | ||||
|     "dist": "gulp before-dist && yarn electronRebuild && npx electron-builder", | ||||
|     "build": "gulp build", | ||||
|     "bundle": "gulp bundleWithStats", | ||||
|     "electronBuilder": "gulp electronBuilder", | ||||
|     "electronRebuild": "gulp electronRebuild", | ||||
|     "tsc": "tsc --project tsconfig.json", | ||||
|     "watch": "tsc --watch --preserveWatchOutput --project tsconfig.json", | ||||
|     "start": "gulp before-start && electron . --env dev --log-level debug --open-dev-tools --no-welcome", | ||||
|     "test": "jest", | ||||
|     "test-ui": "playwright test", | ||||
|     "test-ui": "gulp before-start && playwright test", | ||||
|     "test-ci": "yarn test", | ||||
|     "modifyReleaseAssets": "node tools/modifyReleaseAssets.js" | ||||
|   }, | ||||
| @@ -131,61 +132,53 @@ | ||||
|   "devDependencies": { | ||||
|     "7zip-bin": "5.2.0", | ||||
|     "@axe-core/playwright": "4.10.1", | ||||
|     "@electron/notarize": "2.3.2", | ||||
|     "@electron/rebuild": "3.6.0", | ||||
|     "@fortawesome/fontawesome-free": "5.15.4", | ||||
|     "@joeattardi/emoji-button": "4.6.4", | ||||
|     "@joplin/default-plugins": "~3.4", | ||||
|     "@joplin/editor": "~3.4", | ||||
|     "@joplin/lib": "~3.4", | ||||
|     "@joplin/renderer": "~3.4", | ||||
|     "@joplin/tools": "~3.4", | ||||
|     "@joplin/utils": "~3.4", | ||||
|     "@playwright/test": "1.51.1", | ||||
|     "@sentry/electron": "4.24.0", | ||||
|     "@testing-library/react-hooks": "8.0.1", | ||||
|     "@types/jest": "29.5.12", | ||||
|     "@types/mustache": "4.2.5", | ||||
|     "@types/node": "18.19.67", | ||||
|     "@types/react": "18.3.3", | ||||
|     "@types/react-dom": "18.3.0", | ||||
|     "@types/react-redux": "7.1.33", | ||||
|     "@types/styled-components": "5.1.32", | ||||
|     "@types/tesseract.js": "2.0.0", | ||||
|     "axios": "^1.7.7", | ||||
|     "electron": "35.2.1", | ||||
|     "electron-builder": "24.13.3", | ||||
|     "glob": "10.4.5", | ||||
|     "gulp": "4.0.2", | ||||
|     "jest": "29.7.0", | ||||
|     "jest-environment-jsdom": "29.7.0", | ||||
|     "js-sha512": "0.9.0", | ||||
|     "nan": "2.19.0", | ||||
|     "react-test-renderer": "18.3.1", | ||||
|     "ts-jest": "29.1.5", | ||||
|     "ts-node": "10.9.2", | ||||
|     "typescript": "5.4.5" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@electron/notarize": "2.3.2", | ||||
|     "@electron/remote": "2.1.2", | ||||
|     "@fortawesome/fontawesome-free": "5.15.4", | ||||
|     "@joeattardi/emoji-button": "4.6.4", | ||||
|     "@joplin/editor": "~3.4", | ||||
|     "@joplin/lib": "~3.4", | ||||
|     "@joplin/renderer": "~3.4", | ||||
|     "@joplin/utils": "~3.4", | ||||
|     "@sentry/electron": "4.24.0", | ||||
|     "@types/mustache": "4.2.5", | ||||
|     "async-mutex": "0.5.0", | ||||
|     "axios": "^1.7.7", | ||||
|     "codemirror": "5.65.9", | ||||
|     "color": "3.2.1", | ||||
|     "compare-versions": "6.1.1", | ||||
|     "countable": "3.0.1", | ||||
|     "debounce": "1.2.1", | ||||
|     "electron": "35.2.1", | ||||
|     "electron-builder": "24.13.3", | ||||
|     "electron-updater": "6.2.1", | ||||
|     "electron-window-state": "5.0.3", | ||||
|     "esbuild": "^0.25.3", | ||||
|     "formatcoords": "1.1.3", | ||||
|     "fs-extra": "11.2.0", | ||||
|     "glob": "10.4.5", | ||||
|     "gulp": "4.0.2", | ||||
|     "highlight.js": "11.10.0", | ||||
|     "immer": "9.0.21", | ||||
|     "is-apple-silicon": "1.1.2", | ||||
|     "keytar": "7.9.0", | ||||
|     "jest": "29.7.0", | ||||
|     "jest-environment-jsdom": "29.7.0", | ||||
|     "js-sha512": "0.9.0", | ||||
|     "mark.js": "8.11.1", | ||||
|     "md5": "2.3.0", | ||||
|     "moment": "2.30.1", | ||||
|     "mustache": "4.2.0", | ||||
|     "nan": "2.19.0", | ||||
|     "node-fetch": "2.6.7", | ||||
|     "node-notifier": "10.0.1", | ||||
|     "node-rsa": "1.1.1", | ||||
| @@ -196,17 +189,27 @@ | ||||
|     "react-dom": "18.3.1", | ||||
|     "react-redux": "8.1.3", | ||||
|     "react-select": "5.8.0", | ||||
|     "react-test-renderer": "18.3.1", | ||||
|     "react-toggle-button": "2.2.0", | ||||
|     "react-tooltip": "4.5.1", | ||||
|     "redux": "4.2.1", | ||||
|     "reselect": "4.1.8", | ||||
|     "roboto-fontface": "0.10.0", | ||||
|     "smalltalk": "2.5.1", | ||||
|     "sqlite3": "5.1.6", | ||||
|     "styled-components": "5.3.11", | ||||
|     "styled-system": "5.1.5", | ||||
|     "taboverride": "4.0.3", | ||||
|     "tesseract.js": "5.1.0", | ||||
|     "tinymce": "6.8.5" | ||||
|     "tinymce": "6.8.5", | ||||
|     "ts-jest": "29.1.5", | ||||
|     "ts-node": "10.9.2", | ||||
|     "typescript": "5.4.5" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@electron/remote": "2.1.2", | ||||
|     "@joplin/onenote-converter": "~3.4", | ||||
|     "fs-extra": "11.2.0", | ||||
|     "keytar": "7.9.0", | ||||
|     "sqlite3": "5.1.6" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| // Just a convenient wrapper to get a typed bridge in TypeScript | ||||
|  | ||||
| import { Bridge } from '../bridge'; | ||||
| import type { Bridge } from '../bridge'; | ||||
|  | ||||
| const remoteBridge = require('@electron/remote').require('./bridge').default; | ||||
| const remoteBridge = require('@electron/remote').getGlobal('joplinBridge'); | ||||
|  | ||||
| export default function bridge(): Bridge { | ||||
| 	return remoteBridge(); | ||||
| 	return remoteBridge; | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import { EventHandlers } from '@joplin/lib/services/plugins/utils/mapEventHandle | ||||
| import shim from '@joplin/lib/shim'; | ||||
| import Logger from '@joplin/utils/Logger'; | ||||
| import getPathToExecutable7Zip from '../../utils/7zip/getPathToExecutable7Zip'; | ||||
| import getAssetPath from '../../utils/getAssetPath'; | ||||
| // import BackOffHandler from './BackOffHandler'; | ||||
| const ipcRenderer = require('electron').ipcRenderer; | ||||
|  | ||||
| @@ -134,7 +135,7 @@ export default class PluginRunner extends BasePluginRunner { | ||||
| 		}; | ||||
|  | ||||
| 		void pluginWindow.loadURL(`${require('url').format({ | ||||
| 			pathname: require('path').join(__dirname, 'plugin_index.html'), | ||||
| 			pathname: getAssetPath('services/plugins/plugin_index.html'), | ||||
| 			protocol: 'file:', | ||||
| 			slashes: true, | ||||
| 		})}?pluginId=${encodeURIComponent(plugin.id)}&pluginScript=${encodeURIComponent(`file://${scriptPath}`)}&libraryData=${encodeURIComponent(JSON.stringify(libraryData))}`); | ||||
|   | ||||
| @@ -12,6 +12,8 @@ import { WindowIdContext } from '../../gui/NewWindowOrIFrame'; | ||||
| import useSubmitHandler from './hooks/useSubmitHandler'; | ||||
| import useFormData from './hooks/useFormData'; | ||||
| import Setting from '@joplin/lib/models/Setting'; | ||||
| import getAssetPath from '../../utils/getAssetPath'; | ||||
| import { toForwardSlashes } from '@joplin/utils/path'; | ||||
|  | ||||
| const logger = Logger.create('UserWebview'); | ||||
|  | ||||
| @@ -124,7 +126,7 @@ function UserWebview(props: Props, ref: any) { | ||||
|  | ||||
| 	const src = useMemo(() => { | ||||
| 		const isolate = Setting.value('featureFlag.plugins.isolatePluginWebViews'); | ||||
| 		const path = `${__dirname}/UserWebviewIndex.html`; | ||||
| 		const path = toForwardSlashes(getAssetPath('services/plugins/UserWebviewIndex.html')); | ||||
| 		if (isolate) { | ||||
| 			return `joplin-content://plugin-webview/${path}`; | ||||
| 		} else { | ||||
|   | ||||
							
								
								
									
										111
									
								
								packages/app-desktop/tools/bundleJs.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								packages/app-desktop/tools/bundleJs.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| import { filename, toForwardSlashes } from '@joplin/utils/path'; | ||||
| import * as esbuild from 'esbuild'; | ||||
| import { existsSync } from 'fs'; | ||||
| import { writeFile } from 'fs/promises'; | ||||
| import { dirname, join, relative } from 'path'; | ||||
|  | ||||
| // Note: Roughly based on js-draw's use of esbuild: | ||||
| // https://github.com/personalizedrefrigerator/js-draw/blob/6fe6d6821402a08a8d17f15a8f48d95e5d7b084f/packages/build-tool/src/BundledFile.ts#L64 | ||||
| const makeBuildContext = (entryPoint: string, renderer: boolean, computeFileSizeStats: boolean) => { | ||||
| 	return esbuild.context({ | ||||
| 		entryPoints: [entryPoint], | ||||
| 		outfile: `${filename(entryPoint)}.bundle.js`, | ||||
| 		bundle: true, | ||||
| 		minify: true, | ||||
| 		sourcemap: true, | ||||
| 		metafile: computeFileSizeStats, | ||||
| 		platform: 'node', | ||||
| 		target: ['node20.0'], | ||||
| 		mainFields: renderer ? ['browser', 'main'] : ['main'], | ||||
| 		plugins: [ | ||||
| 			{ | ||||
| 				// Configures ESBuild to require(...) certain libraries that cause issues if included directly | ||||
| 				// in the bundle. Some of these are transitive dependencies and so need to have relative paths | ||||
| 				// in the final bundle. | ||||
| 				name: 'joplin--relative-imports-for-externals', | ||||
| 				setup: build => { | ||||
| 					const externalRegex = /^(.*\.node|sqlite3|electron|@electron\/remote\/.*|electron\/.*|@mapbox\/node-pre-gyp|jsdom)$/; | ||||
| 					const baseDir = dirname(__dirname); | ||||
| 					const baseNodeModules = join(baseDir, 'node_modules'); | ||||
| 					build.onResolve({ filter: externalRegex }, args => { | ||||
| 						// Electron packages don't need relative requires | ||||
| 						if (args.path === 'electron' || args.path.startsWith('electron/')) { | ||||
| 							return { path: args.path, external: true, namespace: 'node' }; | ||||
| 						} | ||||
|  | ||||
| 						// Other packages may need relative requires | ||||
| 						let path = toForwardSlashes(relative( | ||||
| 							baseDir, | ||||
| 							require.resolve(args.path, { paths: [baseNodeModules, args.resolveDir, baseDir] }), | ||||
| 						)); | ||||
| 						if (!path.startsWith('.')) { | ||||
| 							path = `./${path}`; | ||||
| 						} | ||||
|  | ||||
| 						// Some files have .node.* extensions but are not native modules. These files are often required using | ||||
| 						// require('./something.node') rather than require('./something.node.js'). Skip path remapping for | ||||
| 						// these files: | ||||
| 						if (args.path.endsWith('.node') && (path.endsWith('.ts') || path.endsWith('.js'))) { | ||||
| 							// Normal .ts or .js file -- continue. | ||||
| 							return null; | ||||
| 						} | ||||
|  | ||||
| 						// Log that this is external -- it should be included in "dependencies" and not "devDependencies" in package.json: | ||||
| 						console.log('External path:', path, args.importer); | ||||
| 						return { | ||||
| 							path, | ||||
| 							external: true, | ||||
| 						}; | ||||
| 					}); | ||||
| 				}, | ||||
| 			}, | ||||
| 			{ | ||||
| 				// Rewrite imports to prefer .js files to .ts. Otherwise, certain files are duplicated in the final bundle | ||||
| 				name: 'joplin--prefer-js-imports', | ||||
| 				setup: build => { | ||||
| 					const baseDir = dirname(__dirname); | ||||
| 					const baseNodeModules = join(baseDir, 'node_modules'); | ||||
| 					// Rewrite all relative imports | ||||
| 					build.onResolve({ filter: /^\./ }, args => { | ||||
| 						try { | ||||
| 							const importPath = args.path === '.' ? './index' : args.path; | ||||
| 							let path = require.resolve(importPath, { paths: [args.resolveDir, baseNodeModules, baseDir] }); | ||||
| 							// require.resolve **can** return paths with .ts extensions, presumably because | ||||
| 							// this build script is a .ts file. | ||||
| 							if (path.endsWith('.ts')) { | ||||
| 								const alternative = path.replace(/\.ts$/, '.js'); | ||||
| 								if (existsSync(alternative)) { | ||||
| 									path = alternative; | ||||
| 								} | ||||
| 							} | ||||
| 							return { path }; | ||||
| 						} catch (error) { | ||||
| 							return { | ||||
| 								errors: [{ text: `Failed to import: ${error}`, detail: error }], | ||||
| 							}; | ||||
| 						} | ||||
| 					}); | ||||
| 				}, | ||||
| 			}, | ||||
| 		], | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| const bundleJs = async (writeStats: boolean) => { | ||||
| 	const entryPoints = [ | ||||
| 		{ fileName: 'main.js', renderer: false }, | ||||
| 		{ fileName: 'main-html.js', renderer: true }, | ||||
| 	]; | ||||
| 	for (const { fileName, renderer } of entryPoints) { | ||||
| 		const compiler = await makeBuildContext(fileName, renderer, writeStats); | ||||
| 		const result = await compiler.rebuild(); | ||||
| 		if (writeStats) { | ||||
| 			const outPath = `${dirname(__dirname)}/${fileName}.meta.json`; | ||||
| 			console.log('Writing bundle stats to ', outPath); | ||||
| 			await writeFile(outPath, JSON.stringify(result.metafile, undefined, '\t')); | ||||
| 		} | ||||
| 		await compiler.dispose(); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default bundleJs; | ||||
| @@ -90,6 +90,7 @@ async function main() { | ||||
| 		'smalltalk/dist/smalltalk.min.js', | ||||
| 		'smalltalk/img/IDR_CLOSE_DIALOG_H.png', | ||||
| 		'smalltalk/img/IDR_CLOSE_DIALOG.png', | ||||
| 		'tesseract.js/dist/tesseract.min.js', | ||||
| 		{ | ||||
| 			src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'), | ||||
| 			dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`, | ||||
|   | ||||
| @@ -11,5 +11,5 @@ | ||||
| 		// Exclude gulpfile.ts to prevent Gulp from trying to build from | ||||
| 		// gulpfile.js. | ||||
| 		"gulpfile.ts" | ||||
| 	], | ||||
| 	] | ||||
| } | ||||
							
								
								
									
										9
									
								
								packages/app-desktop/utils/getAssetPath.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/app-desktop/utils/getAssetPath.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import { dirname, join } from 'path'; | ||||
|  | ||||
| const getAssetPath = (path: string) => { | ||||
| 	// __dirname sometimes points to app-desktop/ | ||||
| 	const baseDir = __dirname.match(/utils[/\\]?$/) ? dirname(__dirname) : __dirname; | ||||
| 	return join(baseDir, path); | ||||
| }; | ||||
|  | ||||
| export default getAssetPath; | ||||
| @@ -19,7 +19,7 @@ export default class PluginAssetsLoader { | ||||
| 	} | ||||
|  | ||||
| 	private destDir_() { | ||||
| 		return `${Setting.value('resourceDir')}/pluginAssets`; | ||||
| 		return Setting.value('pluginAssetDir'); | ||||
| 	} | ||||
|  | ||||
| 	private async importAssetsMobile_() { | ||||
|   | ||||
| @@ -527,6 +527,7 @@ async function initialize(dispatch: Dispatch) { | ||||
| 	Setting.setConstant('cacheDir', `${getProfilesRootDir()}/cache`); | ||||
| 	const resourceDir = getResourceDir(currentProfile, isSubProfile); | ||||
| 	Setting.setConstant('resourceDir', resourceDir); | ||||
| 	Setting.setConstant('pluginAssetDir', `${Setting.value('resourceDir')}/pluginAssets`); | ||||
| 	Setting.setConstant('pluginDir', `${getProfilesRootDir()}/plugins`); | ||||
| 	Setting.setConstant('pluginDataDir', getPluginDataDir(currentProfile, isSubProfile)); | ||||
|  | ||||
|   | ||||
| @@ -29,8 +29,9 @@ | ||||
|   "exports": { | ||||
|     ".": "./index.js", | ||||
|     "./lib/uslug": { | ||||
|       "types": "./lib/uslug.ts", | ||||
|       "require": "./lib/uslug.js", | ||||
|       "types": "./lib/uslug.ts" | ||||
|       "default": "./lib/uslug.js" | ||||
|     } | ||||
|   }, | ||||
|   "engines": { | ||||
|   | ||||
| @@ -6,12 +6,12 @@ | ||||
|   "types": "index.ts", | ||||
|   "exports": { | ||||
|     ".": { | ||||
|       "default": "./dist/index.js", | ||||
|       "types": "./index.ts" | ||||
|       "types": "./index.ts", | ||||
|       "default": "./dist/index.js" | ||||
|     }, | ||||
|     "./packToString": { | ||||
|       "default": "./dist/packToString.js", | ||||
|       "types": "./packToString.ts" | ||||
|       "types": "./packToString.ts", | ||||
|       "default": "./dist/packToString.js" | ||||
|     } | ||||
|   }, | ||||
|   "publishConfig": { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import shim from './shim'; | ||||
| import { _ } from './locale'; | ||||
| const { rtrimSlashes } = require('./path-utils.js'); | ||||
| import { rtrimSlashes } from './path-utils'; | ||||
| import JoplinError from './JoplinError'; | ||||
| import { Env } from './models/Setting'; | ||||
| import Logger from '@joplin/utils/Logger'; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import FileApiDriverJoplinServer from './file-api-driver-joplinServer'; | ||||
| import Setting from './models/Setting'; | ||||
| import Synchronizer from './Synchronizer'; | ||||
| import { _ } from './locale.js'; | ||||
| import { _ } from './locale'; | ||||
| import JoplinServerApi, { Session } from './JoplinServerApi'; | ||||
| import BaseSyncTarget from './BaseSyncTarget'; | ||||
| import { FileApi } from './file-api'; | ||||
|   | ||||
| @@ -21,6 +21,16 @@ export interface RemoveOptions { | ||||
| 	recursive?: boolean; | ||||
| } | ||||
|  | ||||
| export interface ZipExtractOptions { | ||||
| 	source: string; | ||||
| 	extractTo: string; | ||||
| } | ||||
|  | ||||
| export interface ZipEntry { | ||||
| 	entryName: string; | ||||
| 	name: string; | ||||
| } | ||||
|  | ||||
|  | ||||
| export default class FsDriverBase { | ||||
|  | ||||
| @@ -258,4 +268,8 @@ export default class FsDriverBase { | ||||
| 		throw new Error('Not implemented: tarCreate'); | ||||
| 	} | ||||
|  | ||||
| 	public async zipExtract(_options: ZipExtractOptions): Promise<ZipEntry[]> { | ||||
| 		throw new Error('Not implemented: zipExtract'); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import FsDriverBase, { Stat } from './fs-driver-base'; | ||||
| import AdmZip = require('adm-zip'); | ||||
| import FsDriverBase, { Stat, ZipEntry, ZipExtractOptions } from './fs-driver-base'; | ||||
| import time from './time'; | ||||
| const md5File = require('md5-file'); | ||||
| const fs = require('fs-extra'); | ||||
| @@ -210,4 +211,9 @@ export default class FsDriverNode extends FsDriverBase { | ||||
| 		await require('tar').create(options, filePaths); | ||||
| 	} | ||||
|  | ||||
| 	public async zipExtract(options: ZipExtractOptions): Promise<ZipEntry[]> { | ||||
| 		const zip = new AdmZip(options.source); | ||||
| 		zip.extractAllTo(options.extractTo, false); | ||||
| 		return zip.getEntries(); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -15,11 +15,14 @@ const { wrapError } = require('./errorUtils'); | ||||
| const { enexXmlToHtml } = require('./import-enex-html-gen.js'); | ||||
| const md5 = require('md5'); | ||||
| const { Base64Decode } = require('base64-stream'); | ||||
| const md5File = require('md5-file'); | ||||
| import * as mime from './mime-utils'; | ||||
| import type * as FsExtra from 'fs-extra'; | ||||
|  | ||||
| // const Promise = require('promise'); | ||||
| const fs = require('fs-extra'); | ||||
| let fs_: typeof FsExtra = null; | ||||
| const fs = () => { | ||||
| 	fs_ ??= shim.requireDynamic('fs-extra'); | ||||
| 	return fs_; | ||||
| }; | ||||
|  | ||||
| function dateToTimestamp(s: string, defaultValue: number = null): number { | ||||
| 	// Most dates seem to be in this format | ||||
| @@ -60,9 +63,9 @@ async function decodeBase64File(sourceFilePath: string, destFilePath: string) { | ||||
| 		// to disk, thus resulting in the calling code to find a | ||||
| 		// file with size 0. | ||||
|  | ||||
| 		const destFile = fs.openSync(destFilePath, 'w'); | ||||
| 		const sourceStream = fs.createReadStream(sourceFilePath); | ||||
| 		const destStream = fs.createWriteStream(destFile, { | ||||
| 		const destFile = fs().openSync(destFilePath, 'w'); | ||||
| 		const sourceStream = fs().createReadStream(sourceFilePath); | ||||
| 		const destStream = fs().createWriteStream(destFilePath, { | ||||
| 			fd: destFile, | ||||
| 			autoClose: false, | ||||
| 		}); | ||||
| @@ -72,8 +75,8 @@ async function decodeBase64File(sourceFilePath: string, destFilePath: string) { | ||||
| 		// because even if the source has finished sending data, the destination might not have | ||||
| 		// finished receiving it and writing it to disk. | ||||
| 		destStream.on('finish', () => { | ||||
| 			fs.fdatasyncSync(destFile); | ||||
| 			fs.closeSync(destFile); | ||||
| 			fs().fdatasyncSync(destFile); | ||||
| 			fs().closeSync(destFile); | ||||
| 			resolve(null); | ||||
| 		}); | ||||
|  | ||||
| @@ -140,7 +143,7 @@ async function processNoteResource(resource: ExtractedResource) { | ||||
| 		if (setId) resource.id = md5(Date.now() + Math.random()); | ||||
| 		resource.size = 0; | ||||
| 		resource.dataFilePath = `${Setting.value('tempDir')}/${resource.id}.empty`; | ||||
| 		await fs.writeFile(resource.dataFilePath, ''); | ||||
| 		await shim.fsDriver().writeFile(resource.dataFilePath, ''); | ||||
| 	}; | ||||
|  | ||||
| 	if (!resource.hasData) { | ||||
| @@ -155,14 +158,14 @@ async function processNoteResource(resource: ExtractedResource) { | ||||
| 			throw new Error(`Cannot decode resource with encoding: ${resource.dataEncoding}`); | ||||
| 		} | ||||
|  | ||||
| 		const stats = fs.statSync(resource.dataFilePath); | ||||
| 		const stats = await shim.fsDriver().stat(resource.dataFilePath); | ||||
| 		resource.size = stats.size; | ||||
|  | ||||
| 		if (!resource.id) { | ||||
| 			// If no resource ID is present, the resource ID is actually the MD5 | ||||
| 			// of the data. This ID will match the "hash" attribute of the | ||||
| 			// corresponding <en-media> tag. resourceId = md5(decodedData); | ||||
| 			resource.id = await md5File(resource.dataFilePath); | ||||
| 			resource.id = await shim.fsDriver().md5File(resource.dataFilePath); | ||||
| 		} | ||||
|  | ||||
| 		if (!resource.id || !resource.size) { | ||||
| @@ -203,7 +206,7 @@ async function saveNoteResources(note: ExtractedNote) { | ||||
| 		const existingResource = await Resource.load(toSave.id); | ||||
| 		if (existingResource) continue; | ||||
|  | ||||
| 		await fs.move(resource.dataFilePath, Resource.fullPath(toSave), { overwrite: true }); | ||||
| 		await shim.fsDriver().move(resource.dataFilePath, Resource.fullPath(toSave)); | ||||
| 		await Resource.save(toSave, { isNew: true }); | ||||
| 		resourcesCreated++; | ||||
| 	} | ||||
| @@ -381,7 +384,7 @@ const parseNotes = async (parentFolderId: string, filePath: string, importOption | ||||
| 			notesTagged: 0, | ||||
| 		}; | ||||
|  | ||||
| 		const stream = fs.createReadStream(fileToProcess); | ||||
| 		const stream = fs().createReadStream(fileToProcess); | ||||
|  | ||||
| 		const options = {}; | ||||
| 		const strict = true; | ||||
| @@ -547,7 +550,7 @@ const parseNotes = async (parentFolderId: string, filePath: string, importOption | ||||
|  | ||||
| 					noteResource.hasData = true; | ||||
|  | ||||
| 					fs.appendFileSync(noteResource.dataFilePath, text); | ||||
| 					fs().appendFileSync(noteResource.dataFilePath, text); | ||||
| 				} else { | ||||
| 					// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 					if (!(n in noteResource)) (noteResource as any)[n] = ''; | ||||
|   | ||||
| @@ -49,6 +49,7 @@ export interface Constants { | ||||
| 	appType: AppType; | ||||
| 	resourceDirName: string; | ||||
| 	resourceDir: string; | ||||
| 	pluginAssetDir: string; | ||||
| 	profileDir: string; | ||||
| 	rootProfileDir: string; | ||||
| 	tempDir: string; | ||||
| @@ -217,6 +218,7 @@ class Setting extends BaseModel { | ||||
| 		appType: 'SET_ME' as any, // 'cli' or 'mobile' | ||||
| 		resourceDirName: '', | ||||
| 		resourceDir: '', | ||||
| 		pluginAssetDir: '', | ||||
| 		profileDir: '', | ||||
| 		rootProfileDir: '', | ||||
| 		tempDir: '', | ||||
|   | ||||
| @@ -18,7 +18,11 @@ import InteropService_Exporter_Md from './InteropService_Exporter_Md'; | ||||
| import InteropService_Exporter_Md_frontmatter from './InteropService_Exporter_Md_frontmatter'; | ||||
| import InteropService_Importer_Base from './InteropService_Importer_Base'; | ||||
| import InteropService_Exporter_Base from './InteropService_Exporter_Base'; | ||||
| import Module, { dynamicRequireModuleFactory, makeExportModule, makeImportModule } from './Module'; | ||||
| import Module, { makeExportModule, makeImportModule } from './Module'; | ||||
| import InteropService_Exporter_Html from './InteropService_Exporter_Html'; | ||||
| import InteropService_Importer_EnexToHtml from './InteropService_Importer_EnexToHtml'; | ||||
| import InteropService_Importer_EnexToMd from './InteropService_Importer_EnexToMd'; | ||||
| import InteropService_Importer_OneNote from './InteropService_Importer_OneNote'; | ||||
| const { sprintf } = require('sprintf-js'); | ||||
| const { fileExtension } = require('../../path-utils'); | ||||
| const EventEmitter = require('events'); | ||||
| @@ -76,7 +80,7 @@ export default class InteropService { | ||||
| 					description: _('Evernote Export File (as HTML)'), | ||||
| 					supportsMobile: false, | ||||
| 					outputFormat: ImportModuleOutputFormat.Html, | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToHtml')), | ||||
| 				}, () => new InteropService_Importer_EnexToHtml()), | ||||
|  | ||||
| 				makeImportModule({ | ||||
| 					format: 'enex', | ||||
| @@ -85,7 +89,7 @@ export default class InteropService { | ||||
| 					description: _('Evernote Export File (as Markdown)'), | ||||
| 					supportsMobile: false, | ||||
| 					isDefault: true, | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToMd')), | ||||
| 				}, () => new InteropService_Importer_EnexToMd()), | ||||
|  | ||||
| 				makeImportModule({ | ||||
| 					format: 'enex', | ||||
| @@ -94,7 +98,7 @@ export default class InteropService { | ||||
| 					description: _('Evernote Export Files (Directory, as HTML)'), | ||||
| 					supportsMobile: false, | ||||
| 					outputFormat: ImportModuleOutputFormat.Html, | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToHtml')), | ||||
| 				}, () => new InteropService_Importer_EnexToHtml()), | ||||
|  | ||||
| 				makeImportModule({ | ||||
| 					format: 'enex', | ||||
| @@ -102,7 +106,7 @@ export default class InteropService { | ||||
| 					sources: [FileSystemItem.Directory], | ||||
| 					description: _('Evernote Export Files (Directory, as Markdown)'), | ||||
| 					supportsMobile: false, | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Importer_EnexToMd')), | ||||
| 				}, () => new InteropService_Importer_EnexToMd()), | ||||
|  | ||||
| 				makeImportModule({ | ||||
| 					format: 'html', | ||||
| @@ -142,7 +146,7 @@ export default class InteropService { | ||||
| 					sources: [FileSystemItem.File], | ||||
| 					isNoteArchive: false, // Tells whether the file can contain multiple notes (eg. Enex or Jex format) | ||||
| 					description: _('OneNote Notebook'), | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Importer_OneNote')), | ||||
| 				}, () => new InteropService_Importer_OneNote()), | ||||
| 			]; | ||||
|  | ||||
| 			const exportModules = [ | ||||
| @@ -178,14 +182,14 @@ export default class InteropService { | ||||
| 					isNoteArchive: false, | ||||
| 					description: _('HTML File'), | ||||
| 					supportsMobile: false, | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Exporter_Html')), | ||||
| 				}, () => new InteropService_Exporter_Html()), | ||||
|  | ||||
| 				makeExportModule({ | ||||
| 					format: ExportModuleOutputFormat.Html, | ||||
| 					target: FileSystemItem.Directory, | ||||
| 					description: _('HTML Directory'), | ||||
| 					supportsMobile: false, | ||||
| 				}, dynamicRequireModuleFactory('./InteropService_Exporter_Html')), | ||||
| 				}, () => new InteropService_Exporter_Html()), | ||||
| 			]; | ||||
|  | ||||
| 			this.defaultModules_ = (importModules as Module[]).concat(exportModules); | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import { MarkupToHtml } from '@joplin/renderer'; | ||||
| import { NoteEntity, ResourceEntity, ResourceLocalStateEntity } from '../database/types'; | ||||
| import { contentScriptsToRendererRules } from '../plugins/utils/loadContentScripts'; | ||||
| import { basename, friendlySafeFilename, rtrimSlashes, dirname } from '../../path-utils'; | ||||
| import htmlpack from '@joplin/htmlpack'; | ||||
| import packToString from '@joplin/htmlpack/packToString'; | ||||
| const { themeStyle } = require('../../theme'); | ||||
| const { escapeHtml } = require('../../string-utils.js'); | ||||
| import { assetsToHeaders } from '@joplin/renderer'; | ||||
| @@ -19,6 +19,7 @@ import Logger from '@joplin/utils/Logger'; | ||||
| import { parseRenderedNoteMetadata } from './utils'; | ||||
| import ResourceLocalState from '../../models/ResourceLocalState'; | ||||
| import { ResourceInfos } from '@joplin/renderer/types'; | ||||
| import { fromFilename } from '../../mime-utils'; | ||||
|  | ||||
| const logger = Logger.create('InteropService_Exporter_Html'); | ||||
|  | ||||
| @@ -138,13 +139,11 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte | ||||
| 			if (metadata.printTitle && item.title) noteContent.push(`<div class="exported-note-title">${escapeHtml(item.title)}</div>`); | ||||
| 			if (result.html) noteContent.push(result.html); | ||||
|  | ||||
| 			const libRootPath = dirname(dirname(__dirname)); | ||||
|  | ||||
| 			// We need to export all the plugin assets too and refer them from the header | ||||
| 			// The source path is a bit hard-coded but shouldn't change. | ||||
| 			for (let i = 0; i < result.pluginAssets.length; i++) { | ||||
| 				const asset = result.pluginAssets[i]; | ||||
| 				const filePath = asset.pathIsAbsolute ? asset.path : `${libRootPath}/node_modules/@joplin/renderer/assets/${asset.name}`; | ||||
| 				const filePath = asset.pathIsAbsolute ? asset.path : `${Setting.value('pluginAssetDir')}/${asset.name}`; | ||||
| 				if (!(await shim.fsDriver().exists(filePath))) { | ||||
| 					logger.warn(`File does not exist and cannot be exported: ${filePath}`); | ||||
| 				} else { | ||||
| @@ -175,8 +174,12 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied | ||||
| 	public async processResource(resource: ResourceEntity, filePath: string) { | ||||
| 		if (!this.resourceDir_) return; | ||||
| 		if (!await shim.fsDriver().exists(this.resourceDir_)) { | ||||
| 			await shim.fsDriver().mkdir(this.resourceDir_); | ||||
| 		} | ||||
|  | ||||
| 		const destResourcePath = `${this.resourceDir_}/${basename(filePath)}`; | ||||
| 		await shim.fsDriver().copy(filePath, destResourcePath); | ||||
| 		const localState: ResourceLocalStateEntity = await ResourceLocalState.load(resource.id); | ||||
| @@ -188,10 +191,37 @@ export default class InteropService_Exporter_Html extends InteropService_Exporte | ||||
|  | ||||
| 	public async close() { | ||||
| 		if (this.packIntoSingleFile_) { | ||||
| 			const tempFilePath = `${this.filePath_}.tmp`; | ||||
| 			await shim.fsDriver().move(this.filePath_, tempFilePath); | ||||
| 			await htmlpack(tempFilePath, this.filePath_); | ||||
| 			await shim.fsDriver().remove(tempFilePath); | ||||
| 			const mainHtml = await shim.fsDriver().readFile(this.filePath_, 'utf8'); | ||||
| 			const resolveToAllowedDir = (path: string) => { | ||||
| 				// TODO: Enable this for all platforms -- at present, this is mobile-only. | ||||
| 				const restrictToDestDir = !!shim.mobilePlatform(); | ||||
| 				if (restrictToDestDir) { | ||||
| 					return shim.fsDriver().resolveRelativePathWithinDir(this.destDir_, path); | ||||
| 				} else { | ||||
| 					return shim.fsDriver().resolve(this.destDir_, path); | ||||
| 				} | ||||
| 			}; | ||||
| 			const packedHtml = await packToString( | ||||
| 				this.destDir_, | ||||
| 				mainHtml, | ||||
| 				{ | ||||
| 					exists: (path) => { | ||||
| 						path = resolveToAllowedDir(path); | ||||
| 						return shim.fsDriver().exists(path); | ||||
| 					}, | ||||
| 					readFileDataUri: async (path) => { | ||||
| 						path = resolveToAllowedDir(path); | ||||
| 						const mimeType = fromFilename(path); | ||||
| 						const content = await shim.fsDriver().readFile(path, 'base64'); | ||||
| 						return `data:${mimeType};base64,${content}`; | ||||
| 					}, | ||||
| 					readFileText: (path) => { | ||||
| 						path = resolveToAllowedDir(path); | ||||
| 						return shim.fsDriver().readFile(path, 'utf8'); | ||||
| 					}, | ||||
| 				}, | ||||
| 			); | ||||
| 			await shim.fsDriver().writeFile(this.filePath_, packedHtml, 'utf8'); | ||||
|  | ||||
| 			for (const d of this.createdDirs_) { | ||||
| 				await shim.fsDriver().remove(d); | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { ImportExportResult, ImportModuleOutputFormat, ImportOptions } from './t | ||||
| import InteropService_Importer_Base from './InteropService_Importer_Base'; | ||||
| import { NoteEntity } from '../database/types'; | ||||
| import { rtrimSlashes } from '../../path-utils'; | ||||
| import * as AdmZip from 'adm-zip'; | ||||
| import InteropService_Importer_Md from './InteropService_Importer_Md'; | ||||
| import { join, resolve, normalize, sep, dirname } from 'path'; | ||||
| import Logger from '@joplin/utils/Logger'; | ||||
| @@ -45,11 +44,9 @@ export default class InteropService_Importer_OneNote extends InteropService_Impo | ||||
| 	public async exec(result: ImportExportResult) { | ||||
| 		const sourcePath = rtrimSlashes(this.sourcePath_); | ||||
| 		const unzipTempDirectory = await this.temporaryDirectory_(true); | ||||
| 		const zip = new AdmZip(sourcePath); | ||||
| 		logger.info('Unzipping files...'); | ||||
| 		zip.extractAllTo(unzipTempDirectory, false); | ||||
| 		const files = await shim.fsDriver().zipExtract({ source: sourcePath, extractTo: unzipTempDirectory }); | ||||
|  | ||||
| 		const files = zip.getEntries(); | ||||
| 		if (files.length === 0) { | ||||
| 			result.warnings.push('Zip file has no files.'); | ||||
| 			return result; | ||||
| @@ -60,7 +57,7 @@ export default class InteropService_Importer_OneNote extends InteropService_Impo | ||||
| 		const notebookBaseDir = join(unzipTempDirectory, baseFolder, sep); | ||||
| 		const outputDirectory2 = join(tempOutputDirectory, baseFolder); | ||||
|  | ||||
| 		const notebookFiles = zip.getEntries().filter(e => e.name !== '.onetoc2' && e.name !== 'OneNote_RecycleBin.onetoc2'); | ||||
| 		const notebookFiles = files.filter(e => e.name !== '.onetoc2' && e.name !== 'OneNote_RecycleBin.onetoc2'); | ||||
| 		const { oneNoteConverter } = shim.requireDynamic('@joplin/onenote-converter'); | ||||
|  | ||||
| 		logger.info('Extracting OneNote to HTML'); | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| import { _ } from '../../locale'; | ||||
| import shim from '../../shim'; | ||||
| import InteropService_Exporter_Base from './InteropService_Exporter_Base'; | ||||
| import InteropService_Importer_Base from './InteropService_Importer_Base'; | ||||
| import { ExportModuleOutputFormat, ExportOptions, FileSystemItem, ImportModuleOutputFormat, ImportOptions, ModuleType } from './types'; | ||||
| @@ -126,16 +125,5 @@ export const makeExportModule = ( | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| // A module factory that uses dynamic requires. | ||||
| // TODO: This is currently only used because some importers/exporters import libraries that | ||||
| // don't work on mobile (e.g. htmlpack or fs). These importers/exporters should be migrated | ||||
| // to fs so that this can be removed. | ||||
| export const dynamicRequireModuleFactory = (fileName: string) => { | ||||
| 	return () => { | ||||
| 		const ModuleClass = shim.requireDynamic(fileName).default; | ||||
| 		return new ModuleClass(); | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| type Module = ImportModule|ExportModule; | ||||
| export default Module; | ||||
|   | ||||
| @@ -68,6 +68,7 @@ import OcrService from '../services/ocr/OcrService'; | ||||
| import { createWorker } from 'tesseract.js'; | ||||
| import { reg } from '../registry'; | ||||
| import { Store } from 'redux'; | ||||
| import { dirname } from '@joplin/utils/path'; | ||||
| import SyncTargetJoplinServerSAML from '../SyncTargetJoplinServerSAML'; | ||||
|  | ||||
| // Each suite has its own separate data and temp directory so that multiple | ||||
| @@ -198,6 +199,7 @@ Setting.setConstant('tempDir', baseTempDir); | ||||
| Setting.setConstant('cacheDir', baseTempDir); | ||||
| Setting.setConstant('resourceDir', baseTempDir); | ||||
| Setting.setConstant('pluginDataDir', `${profileDir}/profile/plugin-data`); | ||||
| Setting.setConstant('pluginAssetDir', `${dirname(require.resolve('@joplin/renderer'))}/assets`); | ||||
| Setting.setConstant('profileDir', profileDir); | ||||
| Setting.setConstant('rootProfileDir', rootProfileDir); | ||||
| Setting.setConstant('env', Env.Dev); | ||||
|   | ||||
| @@ -179,4 +179,7 @@ Haverbeke | ||||
| unfocusable | ||||
| unlocker | ||||
| Tiktok | ||||
| topagency | ||||
| topagency | ||||
| esbuild | ||||
| mapbox | ||||
| outfile | ||||
|   | ||||
| @@ -7,9 +7,7 @@ | ||||
|   "publishConfig": { | ||||
|     "access": "public" | ||||
|   }, | ||||
|   "browser": { | ||||
|     "jsdom": false | ||||
|   }, | ||||
|   "browser": "lib/turndown.browser.cjs.js", | ||||
|   "dependencies": { | ||||
|     "@adobe/css-tools": "4.4.2", | ||||
|     "html-entities": "1.4.0", | ||||
| @@ -41,12 +39,12 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "build-all": "npm run build-cjs && npm run build-es && npm run build-umd && npm run build-iife", | ||||
|     "build": "rollup -c config/rollup.config.cjs.mjs", | ||||
|     "build-cjs": "rollup -c config/rollup.config.cjs.mjs && rollup -c config/rollup.config.browser.cjs.mjs", | ||||
|     "build-es": "rollup -c config/rollup.config.es.mjs && rollup -c config/rollup.config.browser.es.mjs", | ||||
|     "build-umd": "rollup -c config/rollup.config.umd.mjs && rollup -c config/rollup.config.browser.umd.mjs", | ||||
|     "build-iife": "rollup -c config/rollup.config.iife.mjs", | ||||
|     "build-test": "browserify test/turndown-test.js --outfile test/turndown-test.browser.js", | ||||
|     "build": "npm run build-cjs", | ||||
|     "prepare": "npm run build" | ||||
|   }, | ||||
|   "gitHead": "05a29b450962bf05a8642bbd39446a1f679a96ba" | ||||
|   | ||||
							
								
								
									
										27
									
								
								readme/dev/spec/desktop_bundling.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								readme/dev/spec/desktop_bundling.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| # Desktop app bundling | ||||
|  | ||||
| For performance and to reduce the application size, the desktop app is bundled with [esbuild](https://esbuild.github.io/). Bundling packs most of the desktop application's JavaScript into one or two JavaScript files. This occurs as a part of both `yarn dist` and `yarn start`. | ||||
|  | ||||
| ## Why bundle the app? | ||||
|  | ||||
| - **Performance**: Bundling the app [is recommended by the Electron performance guide](https://www.electronjs.org/docs/latest/tutorial/performance#7-bundle-your-code). The guide states that "Loading modules is a surprisingly expensive operation, especially on Windows." Bundling the application reduces the number of `require` calls. | ||||
| - **Application size**: Bundling can reduce the size of the app created by `yarn dist`. | ||||
|  | ||||
| ## How does bundling reduce the application size? | ||||
|  | ||||
| Bundling allows both: | ||||
| 1. Reducing the size of the JavaScript included in the app through [minification](https://esbuild.github.io/api/#minify), and | ||||
| 2. Reducing the number of dependencies included in `node_modules` in the version of the app built with `electron-builder`. Dependencies often include files unnecessary for the final build (e.g. README images, test files). | ||||
|  | ||||
|  | ||||
| ## Excluding dependencies from `node_modules` | ||||
|  | ||||
| After bundling the app, most dependencies are completely included within `main.bundle.js` or `main-html.bundle.js`. As a result, the copies of these dependencies in `node_modules` are completely unused. | ||||
|  | ||||
| Some dependencies need to be included in `node_modules` at runtime. This is the case, for example, if the dependency needs to be required with `shim.requireDynamic`, or if the dependency includes native `.node` assets that can't be bundled. For example, `sqlite3` includes native `.node` assets that need to be in `node_modules` at runtime. | ||||
|  | ||||
| Electron-builder can be instructed to exclude dependencies from the built application [by moving them to `devDependencies`](https://github.com/electron-userland/electron-builder/blob/84657680ba5688f1594bc77be3df5c2c78125723/README.md?plain=1#L73). A dependency should only be in the production `dependencies` if it needs to be included in `node_modules` at runtime. | ||||
|  | ||||
| ## Determining what contributes to the bundle size | ||||
|  | ||||
| To see what contributes to the size of the application's bundled JavaScript, consider using [esbuild's bundle size analyzer](https://esbuild.github.io/analyze/). The size analyzer accepts esbuild metafiles. To build these metafiles, manually run `yarn bundle`. Bundle metadata will be written to the `app-desktop` directory as `.meta.json` files. | ||||
| @@ -2,6 +2,7 @@ | ||||
|     "compilerOptions": { | ||||
| 		"module": "commonjs", | ||||
| 		"target": "es2017", | ||||
| 		"esModuleInterop": false, | ||||
| 		//"lib": ["es2015", "es2020.string", "dom", "dom.iterable"], | ||||
| 		"alwaysStrict": true, | ||||
| 		"forceConsistentCasingInFileNames": true, | ||||
|   | ||||
							
								
								
									
										267
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										267
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -7384,6 +7384,181 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/aix-ppc64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/aix-ppc64@npm:0.25.3" | ||||
|   conditions: os=aix & cpu=ppc64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/android-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/android-arm64@npm:0.25.3" | ||||
|   conditions: os=android & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/android-arm@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/android-arm@npm:0.25.3" | ||||
|   conditions: os=android & cpu=arm | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/android-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/android-x64@npm:0.25.3" | ||||
|   conditions: os=android & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/darwin-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/darwin-arm64@npm:0.25.3" | ||||
|   conditions: os=darwin & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/darwin-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/darwin-x64@npm:0.25.3" | ||||
|   conditions: os=darwin & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/freebsd-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/freebsd-arm64@npm:0.25.3" | ||||
|   conditions: os=freebsd & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/freebsd-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/freebsd-x64@npm:0.25.3" | ||||
|   conditions: os=freebsd & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-arm64@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-arm@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-arm@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=arm | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-ia32@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-ia32@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=ia32 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-loong64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-loong64@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=loong64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-mips64el@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-mips64el@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=mips64el | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-ppc64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-ppc64@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=ppc64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-riscv64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-riscv64@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=riscv64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-s390x@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-s390x@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=s390x | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/linux-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/linux-x64@npm:0.25.3" | ||||
|   conditions: os=linux & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/netbsd-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/netbsd-arm64@npm:0.25.3" | ||||
|   conditions: os=netbsd & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/netbsd-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/netbsd-x64@npm:0.25.3" | ||||
|   conditions: os=netbsd & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/openbsd-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/openbsd-arm64@npm:0.25.3" | ||||
|   conditions: os=openbsd & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/openbsd-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/openbsd-x64@npm:0.25.3" | ||||
|   conditions: os=openbsd & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/sunos-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/sunos-x64@npm:0.25.3" | ||||
|   conditions: os=sunos & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/win32-arm64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/win32-arm64@npm:0.25.3" | ||||
|   conditions: os=win32 & cpu=arm64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/win32-ia32@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/win32-ia32@npm:0.25.3" | ||||
|   conditions: os=win32 & cpu=ia32 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@esbuild/win32-x64@npm:0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "@esbuild/win32-x64@npm:0.25.3" | ||||
|   conditions: os=win32 & cpu=x64 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": | ||||
|   version: 4.4.0 | ||||
|   resolution: "@eslint-community/eslint-utils@npm:4.4.0" | ||||
| @@ -8727,6 +8902,7 @@ __metadata: | ||||
|     "@joplin/default-plugins": ~3.4 | ||||
|     "@joplin/editor": ~3.4 | ||||
|     "@joplin/lib": ~3.4 | ||||
|     "@joplin/onenote-converter": ~3.4 | ||||
|     "@joplin/renderer": ~3.4 | ||||
|     "@joplin/tools": ~3.4 | ||||
|     "@joplin/utils": ~3.4 | ||||
| @@ -8752,6 +8928,7 @@ __metadata: | ||||
|     electron-builder: 24.13.3 | ||||
|     electron-updater: 6.2.1 | ||||
|     electron-window-state: 5.0.3 | ||||
|     esbuild: ^0.25.3 | ||||
|     formatcoords: 1.1.3 | ||||
|     fs-extra: 11.2.0 | ||||
|     glob: 10.4.5 | ||||
| @@ -24052,6 +24229,92 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "esbuild@npm:^0.25.3": | ||||
|   version: 0.25.3 | ||||
|   resolution: "esbuild@npm:0.25.3" | ||||
|   dependencies: | ||||
|     "@esbuild/aix-ppc64": 0.25.3 | ||||
|     "@esbuild/android-arm": 0.25.3 | ||||
|     "@esbuild/android-arm64": 0.25.3 | ||||
|     "@esbuild/android-x64": 0.25.3 | ||||
|     "@esbuild/darwin-arm64": 0.25.3 | ||||
|     "@esbuild/darwin-x64": 0.25.3 | ||||
|     "@esbuild/freebsd-arm64": 0.25.3 | ||||
|     "@esbuild/freebsd-x64": 0.25.3 | ||||
|     "@esbuild/linux-arm": 0.25.3 | ||||
|     "@esbuild/linux-arm64": 0.25.3 | ||||
|     "@esbuild/linux-ia32": 0.25.3 | ||||
|     "@esbuild/linux-loong64": 0.25.3 | ||||
|     "@esbuild/linux-mips64el": 0.25.3 | ||||
|     "@esbuild/linux-ppc64": 0.25.3 | ||||
|     "@esbuild/linux-riscv64": 0.25.3 | ||||
|     "@esbuild/linux-s390x": 0.25.3 | ||||
|     "@esbuild/linux-x64": 0.25.3 | ||||
|     "@esbuild/netbsd-arm64": 0.25.3 | ||||
|     "@esbuild/netbsd-x64": 0.25.3 | ||||
|     "@esbuild/openbsd-arm64": 0.25.3 | ||||
|     "@esbuild/openbsd-x64": 0.25.3 | ||||
|     "@esbuild/sunos-x64": 0.25.3 | ||||
|     "@esbuild/win32-arm64": 0.25.3 | ||||
|     "@esbuild/win32-ia32": 0.25.3 | ||||
|     "@esbuild/win32-x64": 0.25.3 | ||||
|   dependenciesMeta: | ||||
|     "@esbuild/aix-ppc64": | ||||
|       optional: true | ||||
|     "@esbuild/android-arm": | ||||
|       optional: true | ||||
|     "@esbuild/android-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/android-x64": | ||||
|       optional: true | ||||
|     "@esbuild/darwin-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/darwin-x64": | ||||
|       optional: true | ||||
|     "@esbuild/freebsd-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/freebsd-x64": | ||||
|       optional: true | ||||
|     "@esbuild/linux-arm": | ||||
|       optional: true | ||||
|     "@esbuild/linux-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/linux-ia32": | ||||
|       optional: true | ||||
|     "@esbuild/linux-loong64": | ||||
|       optional: true | ||||
|     "@esbuild/linux-mips64el": | ||||
|       optional: true | ||||
|     "@esbuild/linux-ppc64": | ||||
|       optional: true | ||||
|     "@esbuild/linux-riscv64": | ||||
|       optional: true | ||||
|     "@esbuild/linux-s390x": | ||||
|       optional: true | ||||
|     "@esbuild/linux-x64": | ||||
|       optional: true | ||||
|     "@esbuild/netbsd-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/netbsd-x64": | ||||
|       optional: true | ||||
|     "@esbuild/openbsd-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/openbsd-x64": | ||||
|       optional: true | ||||
|     "@esbuild/sunos-x64": | ||||
|       optional: true | ||||
|     "@esbuild/win32-arm64": | ||||
|       optional: true | ||||
|     "@esbuild/win32-ia32": | ||||
|       optional: true | ||||
|     "@esbuild/win32-x64": | ||||
|       optional: true | ||||
|   bin: | ||||
|     esbuild: bin/esbuild | ||||
|   checksum: 1f9af51aa1d7d1f57e7294823d19ed69b0f6da413b7b0e8123abcebd1bb4011ef19961e2e6679c07301fcd00a85c4d102160fc40a91c25ceeaf594932509d84d | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "escalade@npm:^3.1.1": | ||||
|   version: 3.1.1 | ||||
|   resolution: "escalade@npm:3.1.1" | ||||
| @@ -37986,7 +38249,7 @@ __metadata: | ||||
| 
 | ||||
| "pdfjs-dist@patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch::locator=root%40workspace%3A.": | ||||
|   version: 3.11.174 | ||||
|   resolution: "pdfjs-dist@patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch::version=3.11.174&hash=4ecadb&locator=root%40workspace%3A." | ||||
|   resolution: "pdfjs-dist@patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch::version=3.11.174&hash=d35ec8&locator=root%40workspace%3A." | ||||
|   dependencies: | ||||
|     path2d-polyfill: ^2.0.1 | ||||
|   dependenciesMeta: | ||||
| @@ -37994,7 +38257,7 @@ __metadata: | ||||
|       optional: true | ||||
|     path2d-polyfill: | ||||
|       optional: true | ||||
|   checksum: bc7597789b13b3ea59ee4ff29db40a15f0a20094ef8f6fa9ba59a80db0501a9b94d0fb3602515666057ebe9c92e59d938ac157f4d2852a125ccc1511c9d8adf6 | ||||
|   checksum: aef2f87e478a66541311228716cd91834d4c83320d0fbf9bb0fcbb427f503c125edfb6c5def77d2d4c57db27b92de652752783b50c3edb4a059c4d2b84e1c913 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user