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,9 @@
import { ItemEvent } from '@joplin/lib/components/shared/config/plugins/types';
import { Linking } from 'react-native';
import getPluginHelpUrl from '@joplin/lib/services/plugins/utils/getPluginHelpUrl';
const openWebsiteForPlugin = ({ item }: ItemEvent) => {
return Linking.openURL(getPluginHelpUrl(item.manifest.id));
};
export default openWebsiteForPlugin;

查看文件

@@ -0,0 +1,82 @@
import { ItemEvent, OnPluginSettingChangeEvent } from '@joplin/lib/components/shared/config/plugins/types';
import useOnDeleteHandler from '@joplin/lib/components/shared/config/plugins/useOnDeleteHandler';
import useOnInstallHandler from '@joplin/lib/components/shared/config/plugins/useOnInstallHandler';
import NavService from '@joplin/lib/services/NavService';
import { PluginSettings, defaultPluginSetting } from '@joplin/lib/services/plugins/PluginService';
import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
import { useCallback, useMemo, useRef, useState } from 'react';
interface Props {
updatePluginStates: (settingValue: PluginSettings)=> void;
pluginSettings: PluginSettings;
repoApi: RepositoryApi;
}
export type PluginCallback = (event: ItemEvent)=> void;
export interface PluginCallbacks {
onToggle: PluginCallback;
onUpdate: PluginCallback;
onInstall: PluginCallback;
onDelete: PluginCallback;
onShowPluginLog: PluginCallback;
}
const usePluginCallbacks = (props: Props) => {
const onPluginSettingsChange = useCallback((event: OnPluginSettingChangeEvent) => {
props.updatePluginStates(event.value);
}, [props.updatePluginStates]);
const updatePluginEnabled = useCallback((pluginId: string, enabled: boolean) => {
const newSettings = { ...props.pluginSettings };
newSettings[pluginId] = {
...defaultPluginSetting(),
...newSettings[pluginId],
enabled,
};
props.updatePluginStates(newSettings);
}, [props.pluginSettings, props.updatePluginStates]);
const onToggle = useCallback((event: ItemEvent) => {
const pluginId = event.item.manifest.id;
const settings = props.pluginSettings[pluginId] ?? defaultPluginSetting();
updatePluginEnabled(pluginId, !settings.enabled);
}, [props.pluginSettings, updatePluginEnabled]);
const pluginSettingsRef = useRef(props.pluginSettings);
pluginSettingsRef.current = props.pluginSettings;
const onDelete = useOnDeleteHandler(pluginSettingsRef, onPluginSettingsChange, true);
const [updatingPluginIds, setUpdatingPluginIds] = useState<Record<string, boolean>>({});
const onUpdate = useOnInstallHandler(setUpdatingPluginIds, pluginSettingsRef, props.repoApi, onPluginSettingsChange, true);
const [installingPluginIds, setInstallingPluginIds] = useState<Record<string, boolean>>({});
const onInstall = useOnInstallHandler(
setInstallingPluginIds, pluginSettingsRef, props.repoApi, onPluginSettingsChange, false,
);
const onShowPluginLog = useCallback((event: ItemEvent) => {
const pluginId = event.item.manifest.id;
void NavService.go('Log', { defaultFilter: pluginId });
}, []);
const callbacks = useMemo((): PluginCallbacks => {
return {
onToggle,
onDelete,
onUpdate,
onInstall,
onShowPluginLog,
};
}, [onToggle, onDelete, onUpdate, onInstall, onShowPluginLog]);
return {
callbacks,
updatingPluginIds,
installingPluginIds,
};
};
export default usePluginCallbacks;

查看文件

@@ -0,0 +1,40 @@
import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { PluginManifest } from '@joplin/lib/services/plugins/utils/types';
import { useMemo, useRef } from 'react';
import usePlugin from '@joplin/lib/hooks/plugins/usePlugin';
// initialItem is used when the plugin is not installed. For example, if the plugin item is being
// created from search results.
const usePluginItem = (id: string, pluginSettings: PluginSettings, initialItem: PluginItem|null): PluginItem => {
const plugin = usePlugin(id);
const lastManifest = useRef<PluginManifest|null>(null);
if (plugin) {
lastManifest.current = plugin.manifest;
} else if (!lastManifest.current) {
lastManifest.current = initialItem?.manifest;
}
const manifest = lastManifest.current;
return useMemo(() => {
if (!manifest) return null;
const settings = pluginSettings[id];
const installed = !!settings || !!plugin;
return {
id,
manifest,
installed,
enabled: settings?.enabled ?? installed,
deleted: settings?.deleted ?? false,
hasBeenUpdated: settings?.hasBeenUpdated ?? false,
devMode: plugin?.devMode ?? false,
builtIn: plugin?.builtIn ?? false,
};
}, [plugin, id, pluginSettings, manifest]);
};
export default usePluginItem;

查看文件

@@ -0,0 +1,51 @@
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import Logger from '@joplin/utils/Logger';
import RepositoryApi, { InstallMode } from '@joplin/lib/services/plugins/RepositoryApi';
import Setting, { AppType } from '@joplin/lib/models/Setting';
import { useMemo } from 'react';
import shim from '@joplin/lib/shim';
import PluginService from '@joplin/lib/services/plugins/PluginService';
const logger = Logger.create('useRepoApi');
interface Props {
reloadRepoCounter: number;
setRepoApiError: (error: string|null)=> void;
onRepoApiLoaded: (repoApi: RepositoryApi)=> void;
}
let repoApi_: RepositoryApi|null = null;
export const resetRepoApi = () => {
repoApi_ = null;
};
const useRepoApi = ({ reloadRepoCounter, setRepoApiError, onRepoApiLoaded }: Props) => {
const repoApi = useMemo(() => {
const installMode = shim.mobilePlatform() === 'ios' ? InstallMode.Restricted : InstallMode.Default;
const appInfo = { type: AppType.Mobile, version: PluginService.instance().appVersion };
repoApi_ ??= RepositoryApi.ofDefaultJoplinRepo(Setting.value('tempDir'), appInfo, installMode);
return repoApi_;
}, []);
useAsyncEffect(async event => {
if (reloadRepoCounter > 0) {
logger.info(`Reloading the plugin repository -- try #${reloadRepoCounter + 1}`);
}
setRepoApiError(null);
try {
await repoApi.reinitialize();
} catch (error) {
logger.error(error);
setRepoApiError(error);
}
if (!event.cancelled) {
onRepoApiLoaded(repoApi);
}
}, [setRepoApiError, reloadRepoCounter, repoApi]);
return repoApi;
};
export default useRepoApi;

查看文件

@@ -0,0 +1,39 @@
import { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
import { useMemo } from 'react';
export enum UpdateState {
Idle = 1,
CanUpdate = 2,
Updating = 3,
HasBeenUpdated = 4,
}
interface Props {
pluginId: string;
pluginSettings: PluginSettings;
updatingPluginIds: Record<string, boolean>;
updatablePluginIds: Record<string, boolean>;
}
const useUpdateState = ({ pluginId, pluginSettings, updatablePluginIds, updatingPluginIds }: Props) => {
return useMemo(() => {
const settings = pluginSettings[pluginId];
// Uninstalled
if (!settings) return UpdateState.Idle;
if (settings.hasBeenUpdated) {
return UpdateState.HasBeenUpdated;
}
if (updatingPluginIds[pluginId]) {
return UpdateState.Updating;
}
if (updatablePluginIds[pluginId]) {
return UpdateState.CanUpdate;
}
return UpdateState.Idle;
}, [pluginSettings, updatingPluginIds, pluginId, updatablePluginIds]);
};
export default useUpdateState;