chore(integration): snapshot local main worktree
This commit is contained in:
1 parent
48a9189939
commit
33fb31fb28
116 files changed
+8108
-3026
No files matched your search
@@ -0,0 +1,140 @@
|
||||
import { invokeIpc } from './api-client';
|
||||
|
||||
const MAX_LINK_LENGTH = 4_096;
|
||||
const LOCAL_WEB_FILE_PATTERN = /\.(?:html?|xhtml)$/i;
|
||||
|
||||
function isBoundedSingleLine(value: string): boolean {
|
||||
return value.length > 0
|
||||
&& value.length <= MAX_LINK_LENGTH
|
||||
&& !/[\0\r\n]/.test(value);
|
||||
}
|
||||
|
||||
export function safeConversationExternalUrl(value: string | undefined): string | null {
|
||||
const candidate = value?.trim() ?? '';
|
||||
if (!isBoundedSingleLine(candidate)) return null;
|
||||
|
||||
try {
|
||||
const url = new URL(candidate);
|
||||
if ((url.protocol !== 'https:' && url.protocol !== 'http:')
|
||||
|| url.username
|
||||
|| url.password) {
|
||||
return null;
|
||||
}
|
||||
return url.toString();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function revealableConversationPath(value: string): string | null {
|
||||
const candidate = value.trim();
|
||||
if (!isBoundedSingleLine(candidate)) return null;
|
||||
|
||||
const homeRelative = candidate === '~' || /^~[\\/]/.test(candidate);
|
||||
const posixAbsolute = candidate.startsWith('/');
|
||||
const windowsAbsolute = /^[A-Za-z]:[\\/]/.test(candidate);
|
||||
const windowsNetwork = /^\\\\[^\\]+\\[^\\]+/.test(candidate);
|
||||
return homeRelative || posixAbsolute || windowsAbsolute || windowsNetwork
|
||||
? candidate
|
||||
: null;
|
||||
}
|
||||
|
||||
export function openableConversationLocalWebPath(value: string): string | null {
|
||||
const path = revealableConversationPath(value);
|
||||
return path && LOCAL_WEB_FILE_PATTERN.test(path) ? path : null;
|
||||
}
|
||||
|
||||
export function previewableConversationRelativePath(value: string): string | null {
|
||||
const candidate = value.trim();
|
||||
if (!isBoundedSingleLine(candidate)
|
||||
|| revealableConversationPath(candidate)
|
||||
|| /^[A-Za-z][A-Za-z0-9+.-]*:/.test(candidate)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = candidate.replaceAll('\\', '/').replace(/^\.\//, '');
|
||||
const segments = normalized.split('/');
|
||||
if (!normalized
|
||||
|| normalized.startsWith('/')
|
||||
|| segments.some((segment) => !segment || segment === '.' || segment === '..')
|
||||
|| !LOCAL_WEB_FILE_PATTERN.test(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function comparableLocalPath(value: string): string {
|
||||
return value.replaceAll('\\', '/').replace(/\/$/, '');
|
||||
}
|
||||
|
||||
export function resolveConversationMarkdownWebPath(
|
||||
value: string,
|
||||
markdown: string,
|
||||
): string | null {
|
||||
const relativePath = previewableConversationRelativePath(value);
|
||||
if (!relativePath) return null;
|
||||
|
||||
const matches = new Set<string>();
|
||||
for (const match of markdown.matchAll(/`([^`\r\n]+)`/g)) {
|
||||
const absolutePath = revealableConversationPath(match[1] ?? '');
|
||||
if (!absolutePath || !LOCAL_WEB_FILE_PATTERN.test(absolutePath)) continue;
|
||||
const comparable = comparableLocalPath(absolutePath);
|
||||
if (comparable.endsWith(`/${relativePath}`)) matches.add(absolutePath);
|
||||
}
|
||||
return matches.size === 1 ? [...matches][0] ?? null : null;
|
||||
}
|
||||
|
||||
function resolveHomeRelativePath(value: string, home: string): string {
|
||||
if (value === '~') return home;
|
||||
const separator = home.includes('\\') ? '\\' : '/';
|
||||
const suffix = value.slice(2).replace(/[\\/]/g, separator);
|
||||
return `${home.replace(/[\\/]$/, '')}${separator}${suffix}`;
|
||||
}
|
||||
|
||||
export async function openConversationExternalUrl(value: string): Promise<void> {
|
||||
const url = safeConversationExternalUrl(value);
|
||||
if (!url) throw new Error('Unsupported external URL');
|
||||
await invokeIpc('shell:openExternal', url);
|
||||
}
|
||||
|
||||
export async function revealConversationPath(value: string): Promise<void> {
|
||||
const path = revealableConversationPath(value);
|
||||
if (!path) throw new Error('Unsupported local path');
|
||||
|
||||
let resolvedPath = path;
|
||||
if (path === '~' || /^~[\\/]/.test(path)) {
|
||||
const home = await invokeIpc<string>('app:getPath', 'home');
|
||||
resolvedPath = resolveHomeRelativePath(path, home);
|
||||
}
|
||||
await invokeIpc('shell:showItemInFolder', resolvedPath);
|
||||
}
|
||||
|
||||
export async function openConversationLocalWebFile(value: string): Promise<void> {
|
||||
const path = openableConversationLocalWebPath(value);
|
||||
if (!path) throw new Error('Unsupported local web file');
|
||||
|
||||
let resolvedPath = path;
|
||||
if (path === '~' || /^~[\\/]/.test(path)) {
|
||||
const home = await invokeIpc<string>('app:getPath', 'home');
|
||||
resolvedPath = resolveHomeRelativePath(path, home);
|
||||
}
|
||||
const error = await invokeIpc<string>('shell:openPath', resolvedPath);
|
||||
if (error) throw new Error('Local web file could not be opened');
|
||||
}
|
||||
|
||||
export async function showConversationLinkContextMenu(
|
||||
request:
|
||||
| { kind: 'external'; target: string }
|
||||
| { kind: 'local-web'; target: string },
|
||||
): Promise<void> {
|
||||
if (request.kind === 'external') {
|
||||
const target = safeConversationExternalUrl(request.target);
|
||||
if (!target) throw new Error('Unsupported external URL');
|
||||
await invokeIpc('shell:showLinkContextMenu', { kind: request.kind, target });
|
||||
return;
|
||||
}
|
||||
|
||||
const target = openableConversationLocalWebPath(request.target);
|
||||
if (!target) throw new Error('Unsupported local web file');
|
||||
await invokeIpc('shell:showLinkContextMenu', { kind: request.kind, target });
|
||||
}
|
||||
Reference in new issue
Block a user