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
一些检查失败了
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
这个提交包含在:
@@ -0,0 +1,137 @@
|
||||
import { produce } from 'immer';
|
||||
import Setting from '../../../models/Setting';
|
||||
import shim from '../../../shim';
|
||||
import PluginService, { defaultPluginSetting, DefaultPluginsInfo, Plugins, PluginSettings } from '../PluginService';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import { join } from 'path';
|
||||
|
||||
const logger = Logger.create('defaultPluginsUtils');
|
||||
|
||||
|
||||
// Use loadAndRunDefaultPlugins
|
||||
// Exported for testing.
|
||||
export const getDefaultPluginPathsAndSettings = async (
|
||||
defaultPluginsDir: string,
|
||||
defaultPluginsInfo: DefaultPluginsInfo,
|
||||
pluginSettings: PluginSettings,
|
||||
pluginService: PluginService,
|
||||
) => {
|
||||
const pluginPaths: string[] = [];
|
||||
|
||||
if (!await shim.fsDriver().exists(defaultPluginsDir)) {
|
||||
logger.info(`Could not find default plugins' directory: ${defaultPluginsDir} - skipping installation.`);
|
||||
return { pluginPaths, pluginSettings };
|
||||
}
|
||||
|
||||
const defaultPluginsPaths = await shim.fsDriver().readDirStats(defaultPluginsDir);
|
||||
if (defaultPluginsPaths.length <= 0) {
|
||||
logger.info(`Default plugins' directory is empty: ${defaultPluginsDir} - no default plugins will be installed.`);
|
||||
}
|
||||
|
||||
for (const pluginStat of defaultPluginsPaths) {
|
||||
// Each plugin should be within a folder with the same ID as the plugin
|
||||
const pluginFileName = pluginStat.path;
|
||||
const pluginIdMatch = pluginFileName.match(/^(.*)+\.jpl$/);
|
||||
|
||||
// Previously, default plugins were stored as
|
||||
// default-plugin-id/plugin.jpl
|
||||
// We handle this case by skipping files that don't match the format
|
||||
// default-plugin-id.jpl
|
||||
if (!pluginIdMatch) {
|
||||
logger.warn(`Default plugin filename ${pluginFileName} is not a .JPL file. Skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pluginId = pluginIdMatch[1];
|
||||
|
||||
if (!defaultPluginsInfo.hasOwnProperty(pluginId)) {
|
||||
logger.warn(`Default plugin ${pluginId} is missing in defaultPluginsInfo. Not loading.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We skip plugins that are already loaded -- attempting to unpack a different version of a plugin
|
||||
// that has already been loaded causes errors (see #9832).
|
||||
if (pluginService.isPluginLoaded(pluginId)) {
|
||||
logger.info(`Not loading default plugin ${pluginId} -- a plugin with the same ID is already loaded.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
pluginPaths.push(join(defaultPluginsDir, pluginFileName));
|
||||
|
||||
pluginSettings = produce(pluginSettings, (draft: PluginSettings) => {
|
||||
// Default plugins can be overridden but not uninstalled (as they're part of
|
||||
// the app bundle). When overriding and unoverriding a default plugin, the plugin's
|
||||
// state may be deleted.
|
||||
// As such, we recreate the plugin state if necessary.
|
||||
if (!draft[pluginId]) {
|
||||
draft[pluginId] = defaultPluginSetting();
|
||||
|
||||
const enabledByDefault = defaultPluginsInfo[pluginId].enabled;
|
||||
if (typeof enabledByDefault === 'boolean') {
|
||||
draft[pluginId].enabled = enabledByDefault;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { pluginSettings, pluginPaths };
|
||||
};
|
||||
|
||||
export const loadAndRunDefaultPlugins = async (
|
||||
service: PluginService,
|
||||
defaultPluginsDir: string,
|
||||
defaultPluginsInfo: DefaultPluginsInfo,
|
||||
originalPluginSettings: PluginSettings,
|
||||
): Promise<PluginSettings> => {
|
||||
const { pluginPaths, pluginSettings } = await getDefaultPluginPathsAndSettings(
|
||||
defaultPluginsDir, defaultPluginsInfo, originalPluginSettings, service,
|
||||
) ?? { pluginPaths: [], pluginSettings: originalPluginSettings };
|
||||
|
||||
await service.loadAndRunPlugins(pluginPaths, pluginSettings, { builtIn: true, devMode: false });
|
||||
return pluginSettings;
|
||||
};
|
||||
|
||||
// Applies setting overrides and marks default plugins as installed.
|
||||
// Should be called after plugins have finished loading.
|
||||
export const afterDefaultPluginsLoaded = async (
|
||||
allLoadedPlugins: Plugins,
|
||||
defaultPluginsInfo: DefaultPluginsInfo,
|
||||
pluginSettings: PluginSettings,
|
||||
) => {
|
||||
const installedDefaultPlugins: string[] = Setting.value('installedDefaultPlugins');
|
||||
const allDefaultPlugins = Object.keys(defaultPluginsInfo);
|
||||
|
||||
const isFirstLoadOfDefaultPlugin = (pluginId: string) => {
|
||||
// Not installed?
|
||||
if (!pluginSettings[pluginId]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not the first load
|
||||
if (installedDefaultPlugins.includes(pluginId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true only if the plugin is built-in (and not a user-installed
|
||||
// copy).
|
||||
//
|
||||
// This avoids overriding existing user-set settings.
|
||||
return allLoadedPlugins[pluginId]?.builtIn ?? false;
|
||||
};
|
||||
|
||||
for (const pluginId of allDefaultPlugins) {
|
||||
// if pluginId is present in pluginSettings and not in installedDefaultPlugins array,
|
||||
// then it's a new default plugin and needs overrides applied.
|
||||
if (isFirstLoadOfDefaultPlugin(pluginId)) {
|
||||
// Postprocess: Apply setting overrides
|
||||
for (const settingName of Object.keys(defaultPluginsInfo[pluginId].settings ?? {})) {
|
||||
if (!installedDefaultPlugins.includes(pluginId) && Setting.keyExists(`plugin-${pluginId}.${settingName}`)) {
|
||||
Setting.setValue(`plugin-${pluginId}.${settingName}`, defaultPluginsInfo[pluginId].settings[settingName]);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the plugin as installed so that postprocessing won't be done again.
|
||||
Setting.setArrayValue('installedDefaultPlugins', pluginId);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DefaultPluginsInfo } from '../PluginService';
|
||||
import Setting from '../../../models/Setting';
|
||||
import shim from '../../../shim';
|
||||
|
||||
const getDefaultPluginsInfo = (): DefaultPluginsInfo => {
|
||||
const defaultPlugins = {
|
||||
'io.github.jackgruber.backup': {
|
||||
settings: {
|
||||
'path': `${Setting.value('homeDir')}`,
|
||||
'createSubfolderPerProfile': true,
|
||||
'backupRetention': 7,
|
||||
},
|
||||
|
||||
// Joplin Portable is more likely to run on a device with low write speeds
|
||||
// and memory. Because Simple Backup has auto-backup enabled by default,
|
||||
// we disable it in Joplin Portable.
|
||||
enabled: !shim.isPortable(),
|
||||
},
|
||||
|
||||
'io.github.personalizedrefrigerator.js-draw': {
|
||||
|
||||
},
|
||||
};
|
||||
return defaultPlugins;
|
||||
};
|
||||
export default getDefaultPluginsInfo;
|
||||
在新工单中引用
屏蔽一个用户