Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,8 @@ EXTRA_DIST = \
favicon.ico \
scripts/create-sbom.py \
scripts/refresh-git-hooks \
scripts/unocommands.py
scripts/unocommands.py \
scripts/unoshortcuts.py

if ENABLE_LIBFUZZER
CLEANUP_COMMAND=true
Expand Down
7 changes: 6 additions & 1 deletion browser/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ QUIET_STYLELINT_0 = @echo " STYLELINT" $@;
QUIET_UNOCOMMANDS = $(QUIET_UNOCOMMANDS_$(V))
QUIET_UNOCOMMANDS_ = $(QUIET_UNOCOMMANDS_$(AM_DEFAULT_VERBOSITY))
QUIET_UNOCOMMANDS_0 = @echo " UNOCOMMANDS " $@;
QUIET_UNOSHORTCUTS = $(QUIET_UNOSHORTCUTS_$(V))
QUIET_UNOSHORTCUTS_ = $(QUIET_UNOSHORTCUTS_$(AM_DEFAULT_VERBOSITY))
QUIET_UNOSHORTCUTS_0 = @echo " UNOSHORTCUTS " $@;

MOCHA_DIR = $(srcdir)/mocha_tests
MOCHA_JS_DIR = $(MOCHA_DIR)/workdir
Expand Down Expand Up @@ -325,6 +328,7 @@ COOL_JS_LST =\
src/Leaflet.js \
src/errormessages.js \
src/unocommands.js \
src/unoshortcuts.js \
src/UNO/Key.js \
src/core/Util.js \
src/core/Class.js \
Expand Down Expand Up @@ -949,9 +953,10 @@ $(INTERMEDIATE_DIR)/admin-src.js: $(COOL_ADMIN_TS_JS) $(COOL_ADMIN_JS)
$(call run_prettier_check,--debug-check $(srcdir)/admin/src)
@awk 'FNR == 1 {print ""} 1' $(COOL_ADMIN_TS_JS) $(patsubst %.js,$(srcdir)/%.js,$(COOL_ADMIN_JS)) > $@

$(INTERMEDIATE_DIR)/cool-src.js: tscompile.done $(call prereq_cool) $(COOL_JS_DST) $(abs_top_srcdir)/scripts/unocommands.py $(srcdir)/cool-src.js.m4
$(INTERMEDIATE_DIR)/cool-src.js: tscompile.done $(call prereq_cool) $(COOL_JS_DST) $(abs_top_srcdir)/scripts/unocommands.py $(abs_top_srcdir)/scripts/unoshortcuts.py $(srcdir)/cool-src.js.m4
@mkdir -p $(dir $@)
$(QUIET_UNOCOMMANDS) $(abs_top_srcdir)/scripts/unocommands.py --check $(abs_top_srcdir)
$(QUIET_UNOSHORTCUTS) $(abs_top_srcdir)/scripts/unoshortcuts.py --check $(abs_top_srcdir)
@printf "Checking for obsolete JS build intermediates in this makefile... "
@if ! test "z$(COOL_ASSERT_INTERSECT)" = "z"; then echo; echo "Error: please remove obsolete files:"; echo "$(COOL_ASSERT_INTERSECT)"; exit 1; else echo "clean"; fi
$(call run_eslint_check,$(srcdir)/src $(srcdir)/js)
Expand Down
40 changes: 21 additions & 19 deletions browser/src/app/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

declare var brandProductFAQURL: string | undefined;
declare var unoShortcutsModifierL10N: any;

interface IDAble {
_leaflet_id: number;
Expand Down Expand Up @@ -237,27 +238,28 @@ class Util {
}

public static replaceCtrlAltInMac(msg: string): string {
if (window.L.Browser.mac) {
var ctrl = /Ctrl/g;
var alt = /Alt/g;
const CustomString = String as any;
if (
CustomString.locale.startsWith('de') ||
CustomString.locale.startsWith('dsb') ||
CustomString.locale.startsWith('hsb')
) {
ctrl = /Strg/g;
if (!window.L.Browser.mac) return msg;

// Find the localized modifier names for the current language
// from the generated unoShortcutsModifierL10N table.
let ctrlName = 'Ctrl';
let altName = 'Alt';
if (typeof unoShortcutsModifierL10N !== 'undefined') {
for (const [lang, replacements] of Object.entries(
unoShortcutsModifierL10N,
)) {
if ((String as any).locale.startsWith(lang)) {
Comment thread
smehrbrodt marked this conversation as resolved.
const r = replacements as Record<string, string>;
if (r['Ctrl']) ctrlName = r['Ctrl'];
if (r['Alt']) altName = r['Alt'];
break;
}
}
if (CustomString.locale.startsWith('lt')) {
ctrl = /Vald/g;
}
if (CustomString.locale.startsWith('sl')) {
ctrl = /Krmilka/gi;
alt = /Izmenjalka/gi;
}
return msg.replace(ctrl, '⌘').replace(alt, '⌥');
}
return msg;

return msg
.replace(new RegExp(ctrlName, 'gi'), '\u2318')
.replace(new RegExp(altName, 'gi'), '\u2325');
}

public static randomString(len: number): string {
Expand Down
2 changes: 2 additions & 0 deletions browser/src/control/Control.JSDialogBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,8 @@ window.L.Control.JSDialogBuilder = window.L.Control.extend({
let tooltip = builder._cleanText(data.tooltip) || builder._cleanText(data.text);
if (data.command && (!tooltip || !tooltip.includes('('))) // Add shortcut to tooltip based on command
tooltip = JSDialog.ShortcutsUtil.getShortcut(tooltip, data.command);
else if (tooltip)
tooltip = JSDialog.ShortcutsUtil.localizeModifiers(tooltip);
div.setAttribute('data-cooltip', tooltip);
enabledTooltip = tooltip;

Expand Down
169 changes: 74 additions & 95 deletions browser/src/control/jsdialog/Util.Shortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,92 +11,69 @@

/*
* Shortcuts for the classic toolbar & the Notebookbar.
*
* UNO command shortcuts are generated from core's Accelerators.xcu by
* scripts/unoshortcuts.py into browser/src/unoshortcuts.js.
*/

declare var JSDialog: any;
declare var unoShortcutsMap: any;
declare var unoShortcutsL10N: any;
declare var unoShortcutsModifierL10N: any;

// Non-UNO IDs that share a shortcut with a UNO command.
const UNO_ALIASES: Record<string, string> = {
save: '.uno:Save',
print: '.uno:Print',
insertcomment: '.uno:InsertAnnotation',
hyperlinkdialog: '.uno:HyperlinkDialog',
inserthyperlink: '.uno:HyperlinkDialog',
};

// Non-UNO IDs with explicit shortcuts not from Accelerators.xcu.
const EXPLICIT_ALIASES: Record<string, string> = {
search: 'Ctrl+F',
'home-search': 'Ctrl+F',
'keyboard-shortcuts': 'Ctrl+Shift+?',
};

class ShortcutsUtil {
private shortcutMap: Map<string, string> = new Map();

// Available Shortcuts
public SAVE = 'Ctrl+S';
public UNDO = 'Ctrl+Z';
public REDO = 'Ctrl+Y';
public PRINT = 'Ctrl+P';
public CUT = 'Ctrl+X';
public COPY = 'Ctrl+C';
public PASTE = 'Ctrl+V';
public PASTE_SPECIAL = 'Ctrl+Shift+Alt+V';
public SELECT_ALL = 'Ctrl+A';
public COMMENT = 'Ctrl+Alt+C';
public FOOTNOTE = 'Ctrl+Alt+F';
public ENDNOTE = 'Ctrl+Alt+D';
public BOLD = 'Ctrl+B';
public ITALIC = 'Ctrl+I';
public UNDERLINE = 'Ctrl+U';
public DOUBLE_UNDERLINE = 'Ctrl+D';
public STRIKETHROUGH = 'Ctrl+Alt+5';
public SUPERSCRIPT = 'Ctrl+Shift+P';
public SUBSCRIPT = 'Ctrl+Shift+B';
public LEFT = 'Ctrl+L';
public CENTERED = 'Ctrl+E';
public RIGHT = 'Ctrl+R';
public JUSTIFIED = 'Ctrl+J';
public KEYBOARD_SHORTCUTS = 'Ctrl+Shift+?';
public HYPERLINK = 'Ctrl+K';
public CLEAR_FORMATTING = 'Ctrl+M';
public INSERT_TABLE = 'Ctrl+F12';
public INSERT_PAGEBREAK = 'Ctrl+Return';
public FIND = 'Ctrl+F';
public FIND_REPLACE = 'Ctrl+H';
public FORMAT_CELL = 'Ctrl+1';

constructor() {
this.shortcutMap.set('.uno:Save', this.SAVE);
this.shortcutMap.set('save', this.SAVE);
this.shortcutMap.set('.uno:Undo', this.UNDO);
this.shortcutMap.set('.uno:Redo', this.REDO);
this.shortcutMap.set('.uno:Print', this.PRINT);
this.shortcutMap.set('print', this.PRINT);
this.shortcutMap.set('.uno:Cut', this.CUT);
this.shortcutMap.set('.uno:Copy', this.COPY);
this.shortcutMap.set('.uno:Paste', this.PASTE);
this.shortcutMap.set('.uno:PasteSpecial', this.PASTE_SPECIAL);
this.shortcutMap.set('.uno:SelectAll', this.SELECT_ALL);
this.shortcutMap.set('.uno:InsertAnnotation', this.COMMENT);
this.shortcutMap.set('insertcomment', this.COMMENT);
this.shortcutMap.set('.uno:InsertFootnote', this.FOOTNOTE);
this.shortcutMap.set('.uno:InsertEndnote', this.ENDNOTE);
this.shortcutMap.set('.uno:Bold', this.BOLD);
this.shortcutMap.set('.uno:Italic', this.ITALIC);
this.shortcutMap.set('.uno:Underline', this.UNDERLINE);
this.shortcutMap.set('.uno:UnderlineDouble', this.DOUBLE_UNDERLINE);
this.shortcutMap.set('.uno:Strikeout', this.STRIKETHROUGH);
this.shortcutMap.set('.uno:SuperScript', this.SUPERSCRIPT);
this.shortcutMap.set('.uno:SubScript', this.SUBSCRIPT);
this.shortcutMap.set('.uno:LeftPara', this.LEFT);
this.shortcutMap.set('.uno:AlignLeft', this.LEFT);
this.shortcutMap.set('.uno:CommonAlignLeft', this.LEFT);
this.shortcutMap.set('.uno:AlignHorizontalCenter', this.CENTERED);
this.shortcutMap.set('.uno:CenterPara', this.CENTERED);
this.shortcutMap.set('.uno:CommonAlignHorizontalCenter', this.CENTERED);
this.shortcutMap.set('.uno:AlignRight', this.RIGHT);
this.shortcutMap.set('.uno:RightPara', this.RIGHT);
this.shortcutMap.set('.uno:CommonAlignRight', this.RIGHT);
this.shortcutMap.set('.uno:AlignBlock', this.JUSTIFIED);
this.shortcutMap.set('.uno:JustifyPara', this.JUSTIFIED);
this.shortcutMap.set('.uno:CommonAlignJustified', this.JUSTIFIED);
this.shortcutMap.set('.uno:KeyboardShortcuts', this.KEYBOARD_SHORTCUTS);
this.shortcutMap.set('keyboard-shortcuts', this.KEYBOARD_SHORTCUTS);
this.shortcutMap.set('hyperlinkdialog', this.HYPERLINK);
this.shortcutMap.set('inserthyperlink', this.HYPERLINK);
this.shortcutMap.set('.uno:ResetAttributes', this.CLEAR_FORMATTING);
this.shortcutMap.set('.uno:InsertTable', this.INSERT_TABLE);
this.shortcutMap.set('.uno:InsertPagebreak', this.INSERT_PAGEBREAK);
this.shortcutMap.set('search', this.FIND);
this.shortcutMap.set('home-search', this.FIND);
this.shortcutMap.set('.uno:SearchDialog', this.FIND_REPLACE);
this.shortcutMap.set('.uno:FormatCellDialog', this.FORMAT_CELL);
// Load shortcuts generated from core's Accelerators.xcu.
if (typeof unoShortcutsMap !== 'undefined') {
for (const [command, shortcut] of Object.entries(unoShortcutsMap)) {
this.shortcutMap.set(command, shortcut as string);
}
}

// Apply per-language shortcut overrides from Accelerators.xcu.
// Core defines different key combos per language
if (typeof unoShortcutsL10N !== 'undefined') {
for (const [lang, overrides] of Object.entries(unoShortcutsL10N)) {
if ((String as any).locale.startsWith(lang)) {
const o = overrides as Record<string, string>;
for (const [command, shortcut] of Object.entries(o)) {
this.shortcutMap.set(command, shortcut);
}
break;
}
}
}

// Aliases: non-UNO IDs that resolve to a UNO command's shortcut.
for (const [alias, unoCmd] of Object.entries(UNO_ALIASES)) {
const shortcut = this.shortcutMap.get(unoCmd);
if (shortcut) {
this.shortcutMap.set(alias, shortcut);
}
}

// Explicit aliases not derived from the XCU.
for (const [alias, shortcut] of Object.entries(EXPLICIT_ALIASES)) {
this.shortcutMap.set(alias, shortcut);
}
}

public hasShortcut(command: string): boolean {
Expand All @@ -109,27 +86,29 @@ class ShortcutsUtil {
* @param {string} shortcut - The shortcut to localize.
* @returns {string} - The localized text with the shortcut.
*/
public localizeModifiers(text: string): string {
if (typeof unoShortcutsModifierL10N !== 'undefined') {
for (const [lang, replacements] of Object.entries(
unoShortcutsModifierL10N,
)) {
if (String.locale.startsWith(lang)) {
for (const [eng, loc] of Object.entries(
replacements as Record<string, string>,
)) {
text = text.replace(eng, loc as string);
}
break;
}
}
}
return text;
}

public getShortcut(text: string, command: string): string {
let shortcut = this.shortcutMap.get(command);
if (!shortcut) return text;

// localize shortcut
if (
String.locale.startsWith('de') ||
String.locale.startsWith('dsb') ||
String.locale.startsWith('hsb')
) {
shortcut = shortcut.replace('Ctrl', 'Strg');
}
if (String.locale.startsWith('lt')) {
shortcut = shortcut.replace('Ctrl', 'Vald');
}
if (String.locale.startsWith('sl')) {
shortcut = shortcut
.replace('Ctrl', 'Krmilka')
.replace('Alt', 'izmenjalka')
.replace('Shift', 'dvigalka');
}
shortcut = this.localizeModifiers(shortcut);

var newText =
_(text).replace('~', '') +
Expand Down
59 changes: 38 additions & 21 deletions browser/src/map/handler/Map.KeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
declare var ThisIsTheMacOSApp: any;
declare var ThisIsTheQtApp: any;
declare var ThisIsTheWindowsApp: any;
declare var unoShortcutsL10NKeyBindings: any;

function isCtrlKey (e: KeyboardEvent) {
if ((window as any).ThisIsTheiOSApp || window.L.Browser.mac)
Expand Down Expand Up @@ -397,35 +398,51 @@ keyboardShortcuts.definitions.set('default', new Array<ShortcutDescriptor>(
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.CTRL, key: '`', preventDefault: false, platform: Platform.MAC }), // Cycle through windows
));

// German shortcuts.
// German shortcuts: only online-specific dispatchAction entries and
// passthrough shortcuts. UNO command bindings are generated from
// Accelerators.xcu (see below).
keyboardShortcuts.definitions.set('de', new Array<ShortcutDescriptor>(
new ShortcutDescriptor({ eventType: 'keydown', key: 'F12', dispatchAction: 'saveas' }),

new ShortcutDescriptor({ docType: 'presentation', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F9', unoAction: '.uno:GridVisible' }),
new ShortcutDescriptor({ docType: 'presentation', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F3', unoAction: '.uno:ChangeCaseRotateCase' }),
new ShortcutDescriptor({ docType: 'presentation', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F5', dispatchAction: 'presentation' }), // Already available without this shortcut.
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.SHIFT | Mod.CTRL, key: 'F', unoAction: '.uno:Bold' }),
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.SHIFT | Mod.CTRL, key: 'K', unoAction: '.uno:Italic' }),
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.SHIFT | Mod.CTRL, key: 'U', unoAction: '.uno:Underline' }),

new ShortcutDescriptor({ docType: 'text', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F3', unoAction: '.uno:ChangeCaseRotateCase' }),
new ShortcutDescriptor({ docType: 'text', eventType: 'keydown', key: 'F5', unoAction: '.uno:GoToPage' }),
new ShortcutDescriptor({ docType: 'text', eventType: 'keydown', modifier: Mod.ALT | Mod.CTRL, key: 's', dispatchAction: 'home-search' }),

new ShortcutDescriptor({ docType: 'spreadsheet', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F3', unoAction: '.uno:FunctionDialog' }),
new ShortcutDescriptor({ docType: 'presentation', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F5', dispatchAction: 'presentation' }),
new ShortcutDescriptor({ docType: 'text', eventType: 'keydown', modifier: Mod.ALT | Mod.CTRL, key: 's', dispatchAction: 'home-search' }),
new ShortcutDescriptor({ docType: 'spreadsheet', eventType: 'keydown', modifier: Mod.SHIFT, key: 'F2', dispatchAction: 'insertcomment' }),
new ShortcutDescriptor({ docType: 'spreadsheet', eventType: 'keydown', key: 'F4', dispatchAction: 'togglerelative' }),
new ShortcutDescriptor({ docType: 'spreadsheet', eventType: 'keydown', key: 'F9', unoAction: '.uno:Calculate' }),
new ShortcutDescriptor({ docType: 'spreadsheet', eventType: 'keydown', key: 'F5', dispatchAction: 'focusonaddressinput' }),
new ShortcutDescriptor({ docType: 'spreadsheet', eventType: 'keydown', modifier: Mod.ALT, key: '0', unoAction: '.uno:FormatCellDialog' }),

// Passthrough some system shortcuts
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.CTRL | Mod.SHIFT, key: '`', preventDefault: false, platform: Platform.MAC }), // Cycle through windows
));

// French shortcuts.
keyboardShortcuts.definitions.set('fr', new Array<ShortcutDescriptor>(
new ShortcutDescriptor({ eventType: 'keydown', modifier: Mod.CTRL, key: 'g', unoAction: '.uno:Bold' }),
));
// Register per-language keyboard shortcuts generated from core's
// Accelerators.xcu by scripts/unoshortcuts.py.
if (typeof unoShortcutsL10NKeyBindings !== 'undefined') {
for (const [lang, bindings] of Object.entries(unoShortcutsL10NKeyBindings)) {
let existing = keyboardShortcuts.definitions.get(lang);
if (!existing) {
existing = new Array<ShortcutDescriptor>();
keyboardShortcuts.definitions.set(lang, existing);
}

// Track key+modifier combos already defined manually so we
// don't create duplicates (which would throw in findShortcut).
const existingKeys = new Set<string>();
for (const d of existing) {
existingKeys.add(d.key + '|' + d.modifier);
}

for (const b of bindings as any[]) {
const k = b.key + '|' + b.modifier;
if (!existingKeys.has(k)) {
existing.push(new ShortcutDescriptor({
eventType: 'keydown',
Comment thread
smehrbrodt marked this conversation as resolved.
key: b.key,
modifier: b.modifier,
unoAction: b.unoAction,
docType: b.docType,
}));
existingKeys.add(k);
}
}
}
}

window.KeyboardShortcuts = keyboardShortcuts;
Loading
Loading