Skip to content

Commit

Permalink
feat(app): url schema to open url
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenlx committed Feb 4, 2024
1 parent 4265aab commit 8f1a80c
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 96 deletions.
7 changes: 0 additions & 7 deletions apps/app/src/media-note/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { PlaybackSpeedPrompt } from "@/media-view/menu/prompt";
import { speedOptions } from "@/media-view/menu/speed";
import type { MediaView } from "@/media-view/view-type";
import type MxPlugin from "@/mx-main";
import { MediaSwitcherModal } from "@/switcher";
import { isMediaLeaf } from "./leaf-open";
import type { MediaInfo } from "./note-index";
import { saveScreenshot } from "./timestamp/screenshot";
Expand Down Expand Up @@ -142,12 +141,6 @@ function speed(): Controls[] {
}

export function registerNoteCommands(plugin: MxPlugin) {
plugin.addCommand({
id: "open-media-url",
name: "Open media from URL",
icon: "link",
callback: () => new MediaSwitcherModal(plugin).open(),
});
addMediaViewCommand(
{
id: "take-timestamp",
Expand Down
2 changes: 2 additions & 0 deletions apps/app/src/mx-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { MediaFileExtensions } from "./patch/media-type";
import injectMediaView from "./patch/view";
import { createSettingsStore } from "./settings";
import { MxSettingTabs } from "./settings/tab";
import { initSwitcher } from "./switcher";
import { BilibiliRequestHacker } from "./web/bili-req";
import { modifySession } from "./web/session";
import "./login/modal";
Expand All @@ -46,6 +47,7 @@ export default class MxPlugin extends Plugin {
this.registerMediaMenu();
this.handleMediaNote();
await this.modifySession();
initSwitcher(this);
}

async loadSettings() {
Expand Down
100 changes: 11 additions & 89 deletions apps/app/src/switcher/index.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,13 @@
import { Keymap, Platform, SuggestModal } from "obsidian";
import { toURL } from "@/lib/url";
import { parseUrl, type UrlMediaInfo } from "@/media-note/note-index/url-info";
import type MxPlugin from "@/mx-main";

const avId = /^av(?<id>\d+)$/i;
/**
* @see https://github.com/SocialSisterYi/bilibili-API-collect/tree/master?tab=readme-ov-file
*/
const bvId = /^BV1(?<id>[1-9A-HJ-NP-Za-km-z]{9})$/;

const youtubeId = /^[\w-]{11}$/;

export class MediaSwitcherModal extends SuggestModal<UrlMediaInfo> {
constructor(public plugin: MxPlugin) {
super(plugin.app);
this.setPlaceholder("Enter URL or media ID");
this.setInstructions([
{ command: "↑↓", purpose: "to navigate" },
{ command: "↵", purpose: "to open url" },
{
command: Platform.isMacOS ? "⌘ ↵" : "ctrl ↵",
purpose: "to open in new tab",
},
{
command: Platform.isMacOS ? "⌘ ⌥ ↵" : "ctrl alt ↵",
purpose: "to open to the right",
},
{ command: "esc", purpose: "to dismiss" },
]);
// this.scope.register(["Mod"], "Enter", (e) => {
// this.selectSuggestion(null as any, e);
// return false;
// });
// this.scope.register(["Mod", "Alt"], "Enter", (e) => {
// this.selectSuggestion(null as any, e);
// return false;
// });
this.scope.register(null as any, "Enter", (e) => {
// @ts-ignore
this.chooser.useSelectedItem(e);
return false;
});
}

getSuggestions(query: string): UrlMediaInfo[] {
const url = toURL(query);
const guess: URL[] = [];
if (!url) {
let match;
if ((match = query.match(avId))) {
guess.push(
new URL(`https://www.bilibili.com/video/av${match.groups!.id}`),
);
}
if ((match = query.match(bvId))) {
guess.push(new URL(`https://www.bilibili.com/video/${query}`));
}
if ((match = query.match(youtubeId))) {
guess.push(new URL(`https://www.youtube.com/watch?v=${query}`));
}
if (!match) {
const fixProtocol = toURL(`https://${query}`);
if (fixProtocol) {
guess.push(fixProtocol);
}
}
}
const guessInfo = guess
.map((u) => parseUrl(u.href))
.filter((x): x is UrlMediaInfo => !!x);
const urlInfo = parseUrl(url?.href);
if (urlInfo) {
return [urlInfo, ...guessInfo];
}
return guessInfo;
}

renderSuggestion(value: UrlMediaInfo, el: HTMLElement) {
el.setText(value.original);
}
onChooseSuggestion(item: UrlMediaInfo, evt: MouseEvent | KeyboardEvent) {
if (Keymap.isModifier(evt, "Mod") && Keymap.isModifier(evt, "Alt")) {
this.plugin.leafOpener.openMedia(item, "split", "vertical");
} else if (Keymap.isModifier(evt, "Mod")) {
this.plugin.leafOpener.openMedia(item, "tab");
} else {
this.plugin.leafOpener.openMedia(item);
}
}
import { MediaSwitcherModal } from "./modal";
import { registerProtocol } from "./protocol";

export function initSwitcher(plugin: MxPlugin) {
plugin.addCommand({
id: "open-media-url",
name: "Open media from URL",
icon: "link",
callback: () => new MediaSwitcherModal(plugin).open(),
});
registerProtocol(plugin);
}
91 changes: 91 additions & 0 deletions apps/app/src/switcher/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Keymap, Platform, SuggestModal } from "obsidian";
import { toURL } from "@/lib/url";
import { parseUrl, type UrlMediaInfo } from "@/media-note/note-index/url-info";
import type MxPlugin from "@/mx-main";

const avId = /^av(?<id>\d+)$/i;
/**
* @see https://github.com/SocialSisterYi/bilibili-API-collect/tree/master?tab=readme-ov-file
*/
const bvId = /^BV1(?<id>[1-9A-HJ-NP-Za-km-z]{9})$/;

const youtubeId = /^[\w-]{11}$/;

export class MediaSwitcherModal extends SuggestModal<UrlMediaInfo> {
constructor(public plugin: MxPlugin) {
super(plugin.app);
this.setPlaceholder("Enter URL or media ID");
this.setInstructions([
{ command: "↑↓", purpose: "to navigate" },
{ command: "↵", purpose: "to open url" },
{
command: Platform.isMacOS ? "⌘ ↵" : "ctrl ↵",
purpose: "to open in new tab",
},
{
command: Platform.isMacOS ? "⌘ ⌥ ↵" : "ctrl alt ↵",
purpose: "to open to the right",
},
{ command: "esc", purpose: "to dismiss" },
]);
// this.scope.register(["Mod"], "Enter", (e) => {
// this.selectSuggestion(null as any, e);
// return false;
// });
// this.scope.register(["Mod", "Alt"], "Enter", (e) => {
// this.selectSuggestion(null as any, e);
// return false;
// });
this.scope.register(null as any, "Enter", (e) => {
// @ts-ignore
this.chooser.useSelectedItem(e);
return false;
});
}

getSuggestions(query: string): UrlMediaInfo[] {
const url = toURL(query);
const guess: URL[] = [];
if (!url) {
let match;
if ((match = query.match(avId))) {
guess.push(
new URL(`https://www.bilibili.com/video/av${match.groups!.id}`),
);
}
if ((match = query.match(bvId))) {
guess.push(new URL(`https://www.bilibili.com/video/${query}`));
}
if ((match = query.match(youtubeId))) {
guess.push(new URL(`https://www.youtube.com/watch?v=${query}`));
}
if (!match) {
const fixProtocol = toURL(`https://${query}`);
if (fixProtocol) {
guess.push(fixProtocol);
}
}
}
const guessInfo = guess
.map((u) => parseUrl(u.href))
.filter((x): x is UrlMediaInfo => !!x);
const urlInfo = parseUrl(url?.href);
if (urlInfo) {
return [urlInfo, ...guessInfo];
}
return guessInfo;
}

renderSuggestion(value: UrlMediaInfo, el: HTMLElement) {
el.setText(value.original);
}
onChooseSuggestion(item: UrlMediaInfo, evt: MouseEvent | KeyboardEvent) {
if (Keymap.isModifier(evt, "Mod") && Keymap.isModifier(evt, "Alt")) {
this.plugin.leafOpener.openMedia(item, "split", "vertical");
} else if (Keymap.isModifier(evt, "Mod")) {
this.plugin.leafOpener.openMedia(item, "tab");
} else {
this.plugin.leafOpener.openMedia(item);
}
}
}
64 changes: 64 additions & 0 deletions apps/app/src/switcher/protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { around } from "monkey-around";
import type { ObsidianProtocolData } from "obsidian";
import { Notice } from "obsidian";
import { toURL } from "@/lib/url";
import { parseUrl } from "@/media-note/note-index/url-info";
import type MxPlugin from "@/mx-main";

declare global {
// eslint-disable-next-line no-var
var OBS_ACT: ((params: ObsidianProtocolData) => void) | undefined;
}

const ACTION = "mx-open";
export function registerProtocol(plugin: MxPlugin) {
if (window.OBS_ACT) {
plugin.register(
around(window as { OBS_ACT: (params: ObsidianProtocolData) => void }, {
OBS_ACT: (next) =>
function OBS_ACT(params: ObsidianProtocolData) {
if (params.action.startsWith(ACTION + "/")) {
handlePathnameProtocol(params);
return;
}
// @ts-ignore
// eslint-disable-next-line prefer-rest-params
return next.apply(this, arguments);
},
}),
);
}

plugin.registerObsidianProtocolHandler("mx-open", (params) => {
const url = toURL(params.url);
if (!url) {
new Notice("Invalid URL: " + params.url);
return;
}
handleUrl(url);
});

function handlePathnameProtocol(params: ObsidianProtocolData) {
// remove "mx-open/"
const base = decodeURI(params.action.substring(ACTION.length + 1));
const url = toURL(base);
const search = new URLSearchParams(params);
search.delete("action");

if (!url) {
new Notice("Invalid URL: " + base + "?" + search.toString());
return;
}
url.search = search.toString();
handleUrl(url);
}
function handleUrl(url: URL) {
const urlInfo = parseUrl(url.toString());
if (!urlInfo) {
new Notice("Not yet supported: " + url.href);
return;
}
plugin.leafOpener.openMedia(urlInfo);
}
}

0 comments on commit 8f1a80c

Please sign in to comment.