diff --git a/apps/app/src/components/player/menus.tsx b/apps/app/src/components/player/menus.tsx index 1e07a001..95ffa6a1 100644 --- a/apps/app/src/components/player/menus.tsx +++ b/apps/app/src/components/player/menus.tsx @@ -60,11 +60,11 @@ export function MoreOptions() { {...{ [dataLpPassthrough]: true }} onClick={(evt) => { const menu = new Menu(); - const { toggleControls, controls } = store.getState(); + const { toggleControls, controls, hash } = store.getState(); workspace.trigger( "mx-media-menu", menu, - { player, source, toggleControls, controls }, + { player, source, toggleControls, controls, hash }, isEmbed ? "player-menu-embed" : "player-menu-view", ); menu.showAtMouseEvent(evt.nativeEvent); diff --git a/apps/app/src/media-note/note-index/file-info.ts b/apps/app/src/media-note/note-index/file-info.ts index 79530f44..272be807 100644 --- a/apps/app/src/media-note/note-index/file-info.ts +++ b/apps/app/src/media-note/note-index/file-info.ts @@ -1,7 +1,11 @@ -import type { TFile } from "obsidian"; +import type { Vault } from "obsidian"; +import { TFile } from "obsidian"; import type { MediaFileViewType } from "@/media-view/view-type"; -import { isMediaFileViewType } from "@/media-view/view-type"; -import type { MediaType } from "@/patch/media-type"; +import { + isMediaFileViewType, + MEDIA_FILE_VIEW_TYPE, +} from "@/media-view/view-type"; +import { checkMediaType, type MediaType } from "@/patch/media-type"; export interface FileMediaInfo { viewType: MediaFileViewType; @@ -13,3 +17,20 @@ export interface FileMediaInfo { export function isFileMediaInfo(info: unknown): info is FileMediaInfo { return isMediaFileViewType((info as FileMediaInfo).viewType); } + +export function parseFileInfo( + filePath: string, + hash: string, + vault: Vault, +): FileMediaInfo | null { + const file = vault.getAbstractFileByPath(filePath); + if (!file || !(file instanceof TFile)) return null; + const type = checkMediaType(file.extension); + if (!type) return null; + return { + type, + file, + hash, + viewType: MEDIA_FILE_VIEW_TYPE[type], + }; +} diff --git a/apps/app/src/media-view/base.tsx b/apps/app/src/media-view/base.tsx index 244eca79..1478d060 100644 --- a/apps/app/src/media-view/base.tsx +++ b/apps/app/src/media-view/base.tsx @@ -178,12 +178,13 @@ export abstract class MediaRemoteView menuSource: "sidebar-context-menu" | "tab-header" | "more-options", ): void { super.onPaneMenu(menu, menuSource); - const { player, source, toggleControls, controls } = this.store.getState(); + const { player, source, toggleControls, controls, hash } = + this.store.getState(); if (!player || !source) return; this.app.workspace.trigger( "mx-media-menu", menu, - { source, player, toggleControls, controls }, + { source, player, toggleControls, controls, hash }, menuSource, this.leaf, ); diff --git a/apps/app/src/media-view/menu/index.ts b/apps/app/src/media-view/menu/index.ts index 3fdc066d..579d8b0a 100644 --- a/apps/app/src/media-view/menu/index.ts +++ b/apps/app/src/media-view/menu/index.ts @@ -1,7 +1,9 @@ import type { MediaPlayerInstance } from "@vidstack/react"; import type { MediaViewType } from "@/media-note/note-index"; +import { parseFileInfo } from "@/media-note/note-index/file-info"; +import { parseUrl } from "@/media-note/note-index/url-info"; import type MxPlugin from "@/mx-main"; -import "obsidian"; +import { isMediaFileViewType } from "../view-type"; import { muteMenu } from "./mute"; import { pipMenu } from "./pip"; import { speedMenu } from "./speed"; @@ -15,6 +17,7 @@ export interface PlayerContext { viewType: MediaViewType; original: string; }; + hash: string; controls: boolean | undefined; toggleControls: (showCustom: boolean) => void; } @@ -63,8 +66,45 @@ export default function registerMediaMenu(this: MxPlugin) { } else { muteMenu(menu, ctx.player); } + if (source === "player-menu-embed") { + const mediaInfo = isMediaFileViewType(ctx.source.viewType) + ? parseFileInfo(ctx.source.original, ctx.hash, this.app.vault) + : parseUrl(ctx.source.original); + if (mediaInfo) { + menu + .addItem((item) => + item + .setTitle("Open to the right") + .setIcon("separator-vertical") + .setSection("view") + .onClick(() => { + this.leafOpener.openMedia(mediaInfo, "split"); + }), + ) + .addItem((item) => + item + .setTitle("Open in new tab") + .setSection("view") + .setIcon("file-plus") + .onClick(() => { + this.leafOpener.openMedia(mediaInfo, "tab"); + }), + ) + .addItem((item) => + item + .setTitle("Open in new window") + .setSection("view") + .setIcon("maximize") + .onClick(() => { + this.leafOpener.openMedia(mediaInfo, "window"); + }), + ); + } + } webpageMenu(menu, ctx, source); - urlMenu(menu, ctx); + if (source === "player-menu-embed" || source === "more-options") { + urlMenu(menu, ctx); + } }), ); } diff --git a/apps/app/src/media-view/menu/mute.ts b/apps/app/src/media-view/menu/mute.ts index 85579edd..7e0c9c20 100644 --- a/apps/app/src/media-view/menu/mute.ts +++ b/apps/app/src/media-view/menu/mute.ts @@ -6,7 +6,7 @@ export function muteMenu(menu: Menu, player: MediaPlayerInstance) { menu.addItem((item) => item .setSection("mx-player") - .setIcon(muted ? "volume-off" : "volume-up") + .setIcon(muted ? "volume-2" : "volume-x") .setTitle(muted ? "Unmute" : "Mute") .onClick(() => { player.muted = !muted; diff --git a/apps/app/src/media-view/menu/url.ts b/apps/app/src/media-view/menu/url.ts index da427dff..f4fbffbe 100644 --- a/apps/app/src/media-view/menu/url.ts +++ b/apps/app/src/media-view/menu/url.ts @@ -1,17 +1,21 @@ import type { Menu } from "obsidian"; +import { decodeWebpageUrl, isWebpageUrl } from "@/lib/remote-player/encode"; import { isMediaFileViewType } from "../view-type"; import type { PlayerContext } from "."; export function urlMenu(menu: Menu, { source }: PlayerContext) { - if (isMediaFileViewType(source.viewType) || !source.src.startsWith("http")) + const isUrl = source.src.startsWith("http"), + isWebpageEncoded = isWebpageUrl(source.src); + if (isMediaFileViewType(source.viewType) || (!isUrl && !isWebpageEncoded)) return; + const url = isWebpageEncoded ? decodeWebpageUrl(source.src) : source.src; menu.addItem((item) => item .setTitle("Open in browser") .setIcon("globe") .setSection("view") .onClick(() => { - window.open(source.src); + window.open(url); }), ); } diff --git a/apps/app/src/media-view/menu/webpage.ts b/apps/app/src/media-view/menu/webpage.ts index 394101b4..6f3db838 100644 --- a/apps/app/src/media-view/menu/webpage.ts +++ b/apps/app/src/media-view/menu/webpage.ts @@ -14,10 +14,9 @@ export function webpageMenu( ) { if ( !(player.provider instanceof WebiviewMediaProvider) || - source === "player-menu-embed" + (source !== "player-menu-view" && source !== "more-options") ) return; - console.log(source); menu.addItem((item) => { item .setTitle(