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,39 @@
import CommandService from '@joplin/lib/services/CommandService';
import shim from '@joplin/lib/shim';
import { _ } from '@joplin/lib/locale';
const commandService = CommandService.instance();
const getLabel = (commandName: string): string => {
if (commandService.exists(commandName)) return commandService.label(commandName, true);
// Some commands are not registered in CommandService at the moment
// Following hard-coded labels are used as a workaround
switch (commandName) {
case 'quit':
return _('Quit');
case 'zoomActualSize':
return _('Actual Size');
case 'help':
return _('Website and documentation');
case 'hideApp':
return _('Hide Joplin');
case 'minimizeWindow':
return _('Minimise');
case 'closeWindow':
return _('Close Window');
case 'config':
return shim.isMac() ? _('Preferences') : _('Options');
}
// We don't throw an error if a command is not found because if for
// example a command is removed from one version to the next, or a
// command is renamed, we still want the keymap editor to work. So in
// that case, we simply display the command name and it is up to the
// user to fix the shortcut if needed.
return `${commandName} (${_('Invalid')})`;
};
export default getLabel;

查看文件

@@ -0,0 +1,34 @@
import { useState } from 'react';
import KeymapService from '@joplin/lib/services/KeymapService';
const keymapService = KeymapService.instance();
interface CommandStatus {
[commandName: string]: boolean;
}
const useCommandStatus = (): [CommandStatus, (commandName: string)=> void, (commandName: string)=> void] => {
const [status, setStatus] = useState<CommandStatus>(() =>
keymapService.getCommandNames().reduce((accumulator: CommandStatus, command: string) => {
accumulator[command] = false;
return accumulator;
}, {}),
);
const disableStatus = (commandName: string) => setStatus(prevStatus => ({ ...prevStatus, [commandName]: false }));
const enableStatus = (commandName: string) => setStatus(prevStatus => {
// Falsify all the commands; Only one command should be truthy at any given time
const newStatus = Object.keys(prevStatus).reduce((accumulator: CommandStatus, command: string) => {
accumulator[command] = false;
return accumulator;
}, {});
// Make the appropriate command truthful
newStatus[commandName] = true;
return newStatus;
});
return [status, enableStatus, disableStatus];
};
export default useCommandStatus;

查看文件

@@ -0,0 +1,96 @@
import { useState, useEffect } from 'react';
import KeymapService, { KeymapItem } from '@joplin/lib/services/KeymapService';
import getLabel from './getLabel';
const keymapService = KeymapService.instance();
// This custom hook provides a synchronized snapshot of the keymap residing at KeymapService
// All the logic regarding altering and interacting with the keymap is isolated from the components
function allKeymapItems() {
const output = keymapService.getKeymapItems().slice();
output.sort((a: KeymapItem, b: KeymapItem) => {
return getLabel(a.command).toLocaleLowerCase() < getLabel(b.command).toLocaleLowerCase() ? -1 : +1;
});
return output;
}
const useKeymap = (): [
KeymapItem[],
Error,
(keymapItems: KeymapItem[])=> void,
(commandName: string, accelerator: string)=> void,
(commandName: string)=> void,
] => {
const [keymapItems, setKeymapItems] = useState<KeymapItem[]>(() => allKeymapItems());
const [keymapError, setKeymapError] = useState<Error>(null);
const [mustSave, setMustSave] = useState(false);
const setAccelerator = (commandName: string, accelerator: string) => {
setKeymapItems(prevKeymap => {
const newKeymap = [...prevKeymap];
newKeymap.find(item => item.command === commandName).accelerator = accelerator || null /* Disabled */;
return newKeymap;
});
setMustSave(true);
};
const resetAccelerator = (commandName: string) => {
const defaultAccelerator = keymapService.getDefaultAccelerator(commandName);
setKeymapItems(prevKeymap => {
const newKeymap = [...prevKeymap];
newKeymap.find(item => item.command === commandName).accelerator = defaultAccelerator;
return newKeymap;
});
setMustSave(true);
};
const overrideKeymapItems = (customKeymapItems: KeymapItem[]) => {
const oldKeymapItems = [...customKeymapItems];
keymapService.resetKeymap(); // Start with a fresh keymap
try {
// First, try to update the in-memory keymap of KeymapService
// This function will throw if there are any issues with the new custom keymap
keymapService.overrideKeymap(customKeymapItems);
// Then, update the state with the data from KeymapService
// Side-effect: Changes will also be saved to the disk
setKeymapItems(keymapService.getKeymapItems());
setMustSave(true);
} catch (error) {
// oldKeymapItems includes even the unchanged keymap items
// However, it is not an issue because the logic accounts for such scenarios
keymapService.overrideKeymap(oldKeymapItems);
throw error;
}
};
useEffect(() => {
if (!mustSave) return;
setMustSave(false);
async function saveKeymap() {
try {
keymapService.overrideKeymap(keymapItems);
await keymapService.saveCustomKeymap();
setKeymapError(null);
} catch (error) {
error.message = `Could not save file: ${error.message}`;
setKeymapError(error);
}
}
void saveKeymap();
}, [keymapItems, mustSave]);
return [keymapItems, keymapError, overrideKeymapItems, setAccelerator, resetAccelerator];
};
export default useKeymap;