Skip to content

Commit

Permalink
Update shortcuts-search extension (#14516)
Browse files Browse the repository at this point in the history
* Update shortcuts-search extension

- Update changelog
- Minor refactoring
- Extract exitWithMessage to separate file
- Remove hostname hook
- Ignore www subdomain
- Improve error handling for web shortcuts
- Move keymap operations to ShortcutsList
- Extract ShortcutsList to separate file
- Extract KeymapDropdown to separate file
- Extract shortcut formatter to separate file
- Extract baseKeySymbolOverride to separate file
- Add basic support for web app shortcuts
- Bump next from 14.1.1 to 14.2.12 in /shortcuts-disco-site
- Add web app hostname for Fastmail
- Use chokidar for shortcut file watching during development. Rewrite combine-apps in Node.js.
- Add a watcher that can prettify and combine JSON files on each source file edit. Make this run in combination with \`next dev\`, which already has its own file watcher.
- Lock down the node version 20, remove unused ts-node
- Bump micromatch from 4.0.5 to 4.0.8 in /shortcuts-disco-site
- Add shortcuts for SQLPro Studio (through SetApp)
- Add shortcuts for Figma
- Add shortcuts for Postman
- Remove newlines in some .json files to align with prettify.ts output
- Fix FreshRSS favorite toggle shortcut
- Extend Firefox shortcuts
- Extend Notion shortcuts
- Add shortcuts for Signal
- Add shortcuts for Spotify
- Update root readme
- Add shortcuts for Discord
- Add shortcuts for FreshRSS
- Remove duplicated shortcut
- Add shortcuts for Numbers
- Add shortcuts for OmniFocus 4
- Add shortcuts for OmniFocus3
- Bump braces from 3.0.2 to 3.0.3 in /shortcuts-raycast-extension
- Bump ws from 8.16.0 to 8.17.1 in /shortcuts-disco-site
- Fix Pixelmator Pro bundle id
- Update README with new prettify script
- Prettify shortcut files
- Add shortcuts prettier script
- Reformat 1Password shortcuts
- Initial Pixelmator Pro shortcuts
- Bump next from 14.1.0 to 14.1.1 in /shortcuts-disco-site
- Update single app title and description metadata
- Add header
- Fix case #60
- Spline
- Update links to GitHub repo
- Add VCS shortcuts section for IntelliJ IDEA
- Update manifest and metadata description
- Add Toggle Full Screen from MacOS
- Fix sitemap for about page
- Add opengraph and twitter images
- Rebrand for Hotkys

* Fix lint

* Add description for each command
  • Loading branch information
solomkinmv authored Sep 19, 2024
1 parent 53b58e2 commit ced6239
Show file tree
Hide file tree
Showing 19 changed files with 334 additions and 172 deletions.
4 changes: 4 additions & 0 deletions extensions/shortcuts-search/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Shortcuts Search Changelog

## [Update] - 2024-09-18

- Add command to show shortcuts for the frontmost web page

## [Update] - 2024-04-26

- Update datasource url to https://hotkys.com
Expand Down
20 changes: 19 additions & 1 deletion extensions/shortcuts-search/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,25 @@ By selecting shortcut extension actually runs the shortcut using AppleScript.

Data is taken from: https://hotkys.com.

Please see contribution guide for adding new shortcuts [here](https://github.com/solomkinmv/shortcuts-disco/blob/main/README.md#shortcuts-contribution).
Please see contribution guide for adding new shortcuts [here](https://github.com/solomkinmv/hotkys/blob/main/README.md#shortcuts-contribution).

## Commands
### List All Shortcuts
Show shortcuts for all available desktop or web applications.

### List Current Shortcuts
Show shortcuts for the frontmost desktop application. Command will exit if no desktop application
is detected or if it is missing in the Hotkys database.

### List Current Web Shortcuts
Show shortcuts for the frontmost web application. Command will exit if no web application
is detected or if it is missing in the Hotkys database.

- Supported browsers: Safari, Chrome, Arc.
- Not supported browsers: Firefox.

### Copy Current App's Bundle ID
Saves current app's bundle id in the clipboard. Useful for contributing new shortcuts.

## Screenshots

Expand Down
14 changes: 7 additions & 7 deletions extensions/shortcuts-search/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions extensions/shortcuts-search/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@
}
]
},
{
"name": "web-shortcuts",
"title": "List Current Web Shortcuts",
"description": "Show shortcuts for the frontmost web page",
"mode": "view",
"preferences": [
{
"name": "delay",
"title": "Delay",
"description": "Specify delay required before running shortcut on the web page",
"required": false,
"default": "0",
"type": "textfield"
}
]
},
{
"name": "current-app",
"title": "Copy Current App's Bundle Id",
Expand Down
4 changes: 2 additions & 2 deletions extensions/shortcuts-search/src/all-shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Action, ActionPanel, List, useNavigation } from "@raycast/api";
import AppShortcuts from "./app-shortcuts";
import useAllShortcuts from "./load/shortcuts-provider";
import { removeHiddenBundleId } from "./model/internal/bundle-id-remover";
import { formatSubtitle } from "./model/internal/subtitle-formatter";
import { getAvatarIcon, useFrecencySorting } from "@raycast/utils";

export default function AllShortcutsCommand() {
Expand All @@ -19,7 +19,7 @@ export default function AllShortcutsCommand() {
key={app.slug}
icon={getAvatarIcon(app.name)}
title={app.name}
subtitle={removeHiddenBundleId(app.bundleId)}
subtitle={formatSubtitle(app)}
actions={
<ActionPanel>
<Action
Expand Down
162 changes: 7 additions & 155 deletions extensions/shortcuts-search/src/app-shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,14 @@
import {
Action,
ActionPanel,
closeMainWindow,
getFrontmostApplication,
getPreferenceValues,
Icon,
List,
PopToRootType,
} from "@raycast/api";
import { showFailureToast, usePromise } from "@raycast/utils";
import { getFrontmostApplication } from "@raycast/api";
import { usePromise } from "@raycast/utils";
import { useEffect, useState } from "react";
import { runShortcuts } from "./engine/shortcut-runner";
import { Application, AtomicShortcut, Keymap, Section, SectionShortcut } from "./model/internal/internal-models";
import { modifierSymbols } from "./model/internal/modifiers";
import { Application } from "./model/internal/internal-models";
import useAllShortcuts from "./load/shortcuts-provider";
import useKeyCodes from "./load/key-codes-provider";

interface Preferences {
delay: string;
}

function KeymapDropdown(props: { keymaps: string[]; onKeymapChange: (newValue: string) => void }) {
const { keymaps, onKeymapChange } = props;
if (keymaps.length == 1) {
return null;
}
return (
<List.Dropdown
tooltip="Select Keymap"
storeValue={true}
onChange={(newValue) => {
onKeymapChange(newValue);
}}
>
<List.Dropdown.Section title="Keymaps">
{keymaps.map((keymap) => (
<List.Dropdown.Item key={keymap} title={keymap} value={keymap} />
))}
</List.Dropdown.Section>
</List.Dropdown>
);
}
import { ShortcutsList } from "./view/shortcuts-list";
import { exitWithMessage } from "./view/exit-action";

export default function AppShortcuts(props?: { app: Application }) {
const [application, setApplication] = useState<Application | undefined>(props?.app);
const [bundleId, setBundleId] = useState(props?.app?.bundleId);
const [keymaps, setKeymaps] = useState<string[]>([]);
const [keymapSections, setKeymapSections] = useState<Section[]>([]);
const [isLoading, setIsLoading] = useState(true);
const keyCodesResponse = useKeyCodes();
const shortcutsProviderResponse = useAllShortcuts({ execute: !props?.app });

useEffect(() => {
Expand All @@ -57,24 +17,12 @@ export default function AppShortcuts(props?: { app: Application }) {
}
const foundApp = shortcutsProviderResponse.data.applications.find((app) => app.bundleId === bundleId);
if (!foundApp) {
// noinspection JSIgnoredPromiseFromCall
closeMainWindow({ clearRootSearch: true, popToRootType: PopToRootType.Immediate });
// noinspection JSIgnoredPromiseFromCall
showFailureToast(undefined, { title: `Shortcuts not available for application ${bundleId}` });
exitWithMessage(`Shortcuts not available for application ${bundleId}`);
return;
}
setApplication(foundApp);
}, [shortcutsProviderResponse.isLoading, bundleId, application]);

useEffect(() => {
if (!application) return;
const foundKeymaps = application?.keymaps.map((k) => k.title) ?? [];
const foundSections = application?.keymaps[0].sections ?? [];
setKeymaps(foundKeymaps);
setKeymapSections(foundSections);
setIsLoading(false);
}, [application]);

usePromise(async () => application?.bundleId ?? (await getFrontmostApplication()).bundleId, [], {
onData: (bundleId) => {
if (!bundleId) return;
Expand All @@ -83,101 +31,5 @@ export default function AppShortcuts(props?: { app: Application }) {
execute: !props?.app,
});

const onKeymapChange = (newValue: string) => {
setKeymapSections(selectKeymap(application?.keymaps ?? [], newValue)?.sections ?? []);
};

async function executeShortcut(bundleId: string | undefined, shortcutSequence: AtomicShortcut[]) {
if (keyCodesResponse.data === undefined) return;
const delay: number = parseFloat(getPreferenceValues<Preferences>().delay);
await closeMainWindow({ popToRootType: PopToRootType.Immediate });
await runShortcuts(bundleId, delay, shortcutSequence, keyCodesResponse.data);
}

return (
<List
isLoading={isLoading}
searchBarPlaceholder="Search for shortcuts"
searchBarAccessory={<KeymapDropdown keymaps={keymaps} onKeymapChange={onKeymapChange} />}
navigationTitle={application?.name}
>
{keymapSections.map((section) => {
return (
<List.Section key={section.title} title={section.title}>
{section.hotkeys.map((shortcut) => {
return (
<List.Item
key={shortcut.title}
title={shortcut.title}
subtitle={generateHotkeyText(shortcut)}
accessories={
shortcut.comment
? [
{
text: shortcut.comment,
icon: Icon.SpeechBubble,
},
]
: undefined
}
keywords={[section.title]}
actions={
shortcut.sequence.length > 0 ? (
<ActionPanel>
<Action
title="Apply"
onAction={() => application && executeShortcut(application.bundleId, shortcut.sequence)}
/>
</ActionPanel>
) : undefined
}
/>
);
})}
</List.Section>
);
})}
</List>
);
return <ShortcutsList application={application} />;
}

function selectKeymap(keymaps: Keymap[], keymapName: string): Keymap | undefined {
return keymaps.find((keymap) => keymap.title === keymapName);
}

function generateHotkeyText(shortcut: SectionShortcut): string {
return shortcut.sequence
.map((atomicShortcut) => {
const modifiersText = atomicShortcut.modifiers.map((modifier) => modifierSymbols.get(modifier)).join("") ?? "";
return modifiersText + overrideSymbolIfPossible(atomicShortcut.base);
})
.join(" ");
}

function overrideSymbolIfPossible(base: string) {
if (baseKeySymbolOverride.has(base)) {
return baseKeySymbolOverride.get(base);
}
return base.toUpperCase();
}

const baseKeySymbolOverride: Map<string, string> = new Map([
["left", "←"],
["right", "→"],
["up", "↑"],
["down", "↓"],
["pageup", "PgUp"],
["pagedown", "PgDown"],
["home", "Home"],
["end", "End"],
["space", "Space"],
["capslock", "⇪"],
["backspace", "⌫"],
["tab", "⇥"],
["esc", "⎋"],
["enter", "↩"],
["cmd", "⌘"],
["ctrl", "⌃"],
["opt", "⌥"],
["shift", "⇧"],
]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { runAppleScript } from "@raycast/utils";

//language=JavaScript
const appleScript = `
const chromium = new Set([
"com.google.Chrome",
"com.google.Chrome.beta",
"com.google.Chrome.canary",
"com.vivaldi.Vivaldi",
"com.brave.Browser",
"com.microsoft.edgemac",
"com.operasoftware.Opera",
"org.chromium.Chromium",
]);
const safari = new Set(["com.apple.Safari", "com.apple.SafariTechPreview"]);
const arc = new Set(["company.thebrowser.Browser"]);
function getFrontmostChromiumLink(bundleId) {
const tab = Application(bundleId).windows[0].activeTab();
return tab.url();
}
function getFrontmostSafariLink(bundleId) {
const tab = Application(bundleId).documents[0];
return tab.url();
}
function getFrontmostArcLink(bundleId) {
const tab = Application(bundleId).windows[0].activeTab;
return tab.url();
}
function getFrontmostApp() {
const apps = Application("System Events")
.applicationProcesses
.where({ frontmost: true });
return apps[0].bundleIdentifier();
}
function getFrontmostLink() {
const app = getFrontmostApp();
if (chromium.has(app)) {
return getFrontmostChromiumLink(app);
} else if (safari.has(app)) {
return getFrontmostSafariLink(app);
} else if (arc.has(app)) {
return getFrontmostArcLink(app);
} else {
return null;
}
}
function run(argv) {
return getFrontmostLink();
}
`;

function extractHostname(url: string): string {
const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?]+)/im);
return match ? match[1] : url;
}

export async function getFrontmostHostname(): Promise<string | null> {
const url = await runAppleScript(appleScript, { language: "JavaScript" });
return url && url !== "null" ? extractHostname(url) : null;
}
3 changes: 1 addition & 2 deletions extensions/shortcuts-search/src/engine/shortcut-runner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { runAppleScript, showFailureToast } from "@raycast/utils";
import { AtomicShortcut } from "../model/internal/internal-models";
import { removeHiddenBundleId } from "../model/internal/bundle-id-remover";
import { KeyCodes } from "../load/key-codes-provider";

// language=JavaScript
Expand Down Expand Up @@ -80,7 +79,7 @@ function generateArguments(
sequence: AtomicShortcut[],
keyCodes: KeyCodes
): string[] {
const args: string[] = [removeHiddenBundleId(bundleId), String(delay), String(sequence.length)];
const args: string[] = [bundleId ?? "", String(delay), String(sequence.length)];
sequence.forEach((atomic) => {
args.push(String(atomic.modifiers.length));
args.push(...atomic.modifiers);
Expand Down
1 change: 1 addition & 0 deletions extensions/shortcuts-search/src/load/input-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class ShortcutsParser {
return {
name: inputApp.name,
bundleId: inputApp.bundleId,
hostname: inputApp.hostname,
slug: inputApp.slug,
keymaps: inputApp.keymaps.map((inputKeymap) => {
return {
Expand Down
2 changes: 1 addition & 1 deletion extensions/shortcuts-search/src/load/shortcuts-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function useAllShortcuts(props?: Properties): UseAllShortcutsResu
},
{
dataParser: (v) => {
return v ? v : emptyShortcuts;
return v || emptyShortcuts;
},
execute: !!keyCodesResult.data,
}
Expand Down
Loading

0 comments on commit ced6239

Please sign in to comment.