diff --git a/vite/src/pipedal/FilePropertyDialog.tsx b/vite/src/pipedal/FilePropertyDialog.tsx index cda47c8..8ecb77e 100644 --- a/vite/src/pipedal/FilePropertyDialog.tsx +++ b/vite/src/pipedal/FilePropertyDialog.tsx @@ -1129,6 +1129,28 @@ export default withStyles( } + handleT3kLinkClick(href: string): boolean { + if (!href.startsWith("https://tone3000.com/")) { + return false; + } + return false; + // STUB: Waiting for a way to do a popup flow starting with a tone or user home page. + // let uri = new URL(href); + // if (uri.pathname.split("/").filter(segment => segment.length > 0).length === 1) { + // // It's a username. Launch a select flow with username as a filter. + // let username = uri.pathname.split("/").filter(segment => segment.length > 0)[0]; + // this.model.showTone3000DownloadPopup( + // Tone3000DownloadType.Nam, + // this.state.currentDirectory, + // { + // username: username + // } + // ); + // } + + return true; + } + render() { const isTracksDirectory = this.isTracksDirectory(); const isToobNamModelFile = this.props.fileProperty.patchProperty === ToobNamModelFileUrl; @@ -1803,6 +1825,9 @@ export default withStyles( this.setState({ textFileName: undefined })} + onT3kLinkClick={(href) => { + return this.handleT3kLinkClick(href); } + } /> )} diff --git a/vite/src/pipedal/PiPedalModel.tsx b/vite/src/pipedal/PiPedalModel.tsx index bc2868f..d75a631 100644 --- a/vite/src/pipedal/PiPedalModel.tsx +++ b/vite/src/pipedal/PiPedalModel.tsx @@ -773,7 +773,10 @@ export class PiPedalModel //implements PiPedalModel showTone3000DownloadPopup( downloadType: Tone3000DownloadType, - downloadPath: string + downloadPath: string, + options?: { + userName?: string; + } ): void { if (this.tone3000DownloadHandler === null) { this.tone3000DownloadHandler = new Tone3000DownloadHandler(this); @@ -781,7 +784,7 @@ export class PiPedalModel //implements PiPedalModel this.tone3000DownloadHandler.launchTone3000Popup( downloadType, downloadPath, - ); + options); }; @@ -1375,6 +1378,7 @@ export class PiPedalModel //implements PiPedalModel } this.debug = !!data.debug; let { socket_server_port, socket_server_address, max_upload_size } = data; + if ((!socket_server_address) || socket_server_address === "*") { socket_server_address = window.location.hostname; } diff --git a/vite/src/pipedal/TextInfo_RewriteT3kUrls.ts b/vite/src/pipedal/TextInfo_RewriteT3kUrls.ts new file mode 100644 index 0000000..029ae57 --- /dev/null +++ b/vite/src/pipedal/TextInfo_RewriteT3kUrls.ts @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2026 Robin E. R. Davies + * All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import type { Root } from 'hast'; + +// Rewrite tags with "t3klink?id=xxx" hrefs to call the T3kLinkHandler handler instead of navigating to a new page. + +// // Hook in the rewriter as follows: +// +// {this.state.textFileContent} +// + +type HastNode = { + type: string; + tagName?: string; + properties?: Record; + children?: HastNode[]; +}; + +export type T3kLinkClickHandler = (href: string) => boolean; + +export interface RewriteT3kUrlsOptions { + prefixUrl?: string; + onT3kLinkClick?: T3kLinkClickHandler; +} + +// Default callback. Supply `onT3kLinkClick` in options to handle links. +export function T3kLinkHandler(href: string): boolean { + void href; + return false; +} + + +function getHref(properties: Record): string | null { + const href = properties.href; + return typeof href === 'string' ? href : null; +} + +function rewriteNode(node: HastNode, prefixUrl: string, onT3kLinkClick: T3kLinkClickHandler): void { + if (node.type === 'element' && node.tagName === 'a') { + const properties = node.properties ?? {}; + const href = getHref(properties); + if (href && href.startsWith(prefixUrl)) { + properties.target = '_blank'; + properties.rel = 'noopener noreferrer'; + properties.onClick = (event: { preventDefault?: () => void; stopPropagation?: () => void; }) => { + const handled = onT3kLinkClick(href); + if (handled) { + event.preventDefault?.(); + event.stopPropagation?.(); + } + }; + node.properties = properties; + } + } + + if (node.children) { + for (const child of node.children) { + rewriteNode(child, prefixUrl, onT3kLinkClick); + } + } +} + + +export function rewriteT3kUrls(options?: RewriteT3kUrlsOptions) { + const prefixUrl = options?.prefixUrl; + const onT3kLinkClick = options?.onT3kLinkClick ?? ((href: string) => T3kLinkHandler(href)); + return (tree: Root) => { + if (!prefixUrl) { + return; + } + rewriteNode(tree as unknown as HastNode, prefixUrl, onT3kLinkClick); + }; +} +