feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled

这个提交包含在:
chen
2026-11-04 11:29:39 +08:00
当前提交 301194963b
共有 8663 个文件被更改,包括 1962691 次插入 和 0 次删除

查看文件

@@ -0,0 +1,140 @@
import * as React from 'react';
import { _ } from '@joplin/lib/locale';
import Logger from '@joplin/utils/Logger';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import ExtendedWebView from '../../ExtendedWebView';
import { OnMessageEvent, WebViewControl } from '../../ExtendedWebView/types';
import { WebViewErrorEvent } from 'react-native-webview/lib/WebViewTypes';
import { clearAutosave, writeAutosave } from './autosave';
import { DialogContext } from '../../DialogManager';
import BackButtonService from '../../../services/BackButtonService';
import useWebViewSetup, { ImageEditorControl } from '../../../contentScripts/imageEditorBundle/useWebViewSetup';
const logger = Logger.create('ImageEditor');
type OnSaveCallback = (svgData: string)=> Promise<void>;
type OnCancelCallback = ()=> void;
interface Props {
themeId: number;
resourceFilename: string|null;
onSave: OnSaveCallback;
onExit: OnCancelCallback;
}
const ImageEditor = (props: Props) => {
const webViewRef = useRef<WebViewControl|null>(null);
const [imageChanged, setImageChanged] = useState(false);
const editorControlRef = useRef<ImageEditorControl|null>(null);
const dialogs = useContext(DialogContext);
const onRequestCloseEditor = useCallback((promptIfUnsaved = true) => {
const discardChangesAndClose = async () => {
await clearAutosave();
props.onExit();
};
if (!imageChanged || !promptIfUnsaved) {
void discardChangesAndClose();
return;
}
dialogs.prompt(
_('Save changes?'), _('This drawing may have unsaved changes.'), [
{
text: _('Discard changes'),
onPress: discardChangesAndClose,
style: 'destructive',
},
{
text: _('Save changes'),
onPress: () => {
// saveDrawing calls props.onSave(...) which may close the
// editor.
void editorControlRef.current.saveThenExit();
},
},
],
);
}, [dialogs, props.onExit, imageChanged]);
useEffect(() => {
const hardwareBackPressListener = () => {
onRequestCloseEditor(true);
return true;
};
BackButtonService.addHandler(hardwareBackPressListener);
return () => {
BackButtonService.removeHandler(hardwareBackPressListener);
};
}, [onRequestCloseEditor]);
const { pageSetup, api: editorControl, webViewEventHandlers } = useWebViewSetup({
webViewRef,
themeId: props.themeId,
onSetImageChanged: setImageChanged,
onAutoSave: writeAutosave,
onSave: props.onSave,
onRequestCloseEditor,
resourceFilename: props.resourceFilename,
});
editorControlRef.current = editorControl;
const [html, setHtml] = useState('');
useEffect(() => {
setHtml(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"/>
<style>
${pageSetup.css}
</style>
</head>
<body></body>
</html>
`);
// Only set HTML initially (and don't reset). Changing the HTML reloads
// the page.
//
// We need the HTML to initially have the correct CSS to prevent color
// changes on load.
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps
}, []);
const onError = useCallback((event: WebViewErrorEvent) => {
logger.error('ImageEditor: WebView error: ', event);
}, []);
const onWebViewMessage = webViewEventHandlers.onMessage;
const onMessage = useCallback((event: OnMessageEvent) => {
const data = event.nativeEvent.data;
if (typeof data === 'string' && data.startsWith('error:')) {
logger.error(data);
return;
}
onWebViewMessage(event);
}, [onWebViewMessage]);
return (
<ExtendedWebView
html={html}
injectedJavaScript={pageSetup.js}
allowFileAccessFromJs={true}
onMessage={onMessage}
onLoadEnd={webViewEventHandlers.onLoadEnd}
onError={onError}
ref={webViewRef}
webviewInstanceId={'image-editor-js-draw'}
/>
);
};
export default ImageEditor;

查看文件

@@ -0,0 +1,26 @@
import Logger from '@joplin/utils/Logger';
import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim';
export const autosaveFilename = 'autosaved-drawing.joplin.svg';
const logger = Logger.create('ImageEditor/autosave');
export const getAutosaveFilepath = () => {
return `${Setting.value('resourceDir')}/${autosaveFilename}`;
};
export const writeAutosave = async (data: string) => {
const filePath = getAutosaveFilepath();
logger.info(`Auto-saving drawing to ${JSON.stringify(filePath)}`);
await shim.fsDriver().writeFile(filePath, data, 'utf8');
};
export const readAutosave = async (): Promise<string|null> => {
return await shim.fsDriver().readFile(getAutosaveFilepath());
};
export const clearAutosave = async () => {
await shim.fsDriver().remove(getAutosaveFilepath());
};

查看文件

@@ -0,0 +1,6 @@
const isEditableResource = (resourceMime: string) => {
return resourceMime === 'image/svg+xml';
};
export default isEditableResource;

查看文件

@@ -0,0 +1,37 @@
import { _ } from '@joplin/lib/locale';
import shim from '@joplin/lib/shim';
import { Alert } from 'react-native';
import { clearAutosave, getAutosaveFilepath, readAutosave } from './autosave';
export type RestoreAutosaveCallback = (data: string)=> void;
const promptRestoreAutosave = async (onRestoreAutosave: RestoreAutosaveCallback) => {
const autosavePath = getAutosaveFilepath();
if (await shim.fsDriver().exists(autosavePath)) {
const title: string|null = null;
const message = _(
'An autosaved drawing was found. Attach a copy of it to the note?',
);
Alert.alert(title, message, [
{
text: _('Discard'),
onPress: async () => {
await clearAutosave();
},
},
{
text: _('Attach'),
onPress: async () => {
const autosaveData = await readAutosave();
await clearAutosave();
onRestoreAutosave(autosaveData);
},
},
]);
}
};
export default promptRestoreAutosave;