mirror of
https://github.com/devon22/obsidian-gridexplorer.git
synced 2026-07-22 05:38:16 +00:00
311 lines
12 KiB
TypeScript
311 lines
12 KiB
TypeScript
import { App, TFile, Notice } from 'obsidian';
|
||
import { showNameInputModal } from '../modal/folderRenameModal';
|
||
import { t } from '../translations';
|
||
|
||
interface ShortcutFrontmatter {
|
||
type?: 'mode' | 'folder' | 'file' | 'search' | 'uri';
|
||
redirect?: string;
|
||
searchCurrentLocationOnly?: boolean;
|
||
searchFilesNameOnly?: boolean;
|
||
searchMediaFiles?: boolean;
|
||
}
|
||
|
||
/**
|
||
* 創建新筆記的共用函數
|
||
* @param app Obsidian App 實例
|
||
* @param folderPath 目標資料夾路徑
|
||
*/
|
||
export async function createNewNote(app: App, folderPath: string) {
|
||
let newFileName = `${t('untitled')}.md`;
|
||
let newFilePath = !folderPath || folderPath === '/' ? newFileName : `${folderPath}/${newFileName}`;
|
||
|
||
// 檢查檔案是否已存在,如果存在則遞增編號
|
||
let counter = 1;
|
||
while (app.vault.getAbstractFileByPath(newFilePath)) {
|
||
newFileName = `${t('untitled')} ${counter}.md`;
|
||
newFilePath = !folderPath || folderPath === '/' ? newFileName : `${folderPath}/${newFileName}`;
|
||
counter++;
|
||
}
|
||
|
||
try {
|
||
// 建立新筆記
|
||
const newFile = await app.vault.create(newFilePath, '');
|
||
// 開啟新筆記
|
||
await app.workspace.getLeaf().openFile(newFile);
|
||
} catch (error) {
|
||
console.error('An error occurred while creating a new note:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 創建新資料夾的共用函數
|
||
* @param app Obsidian App 實例
|
||
* @param folderPath 目標資料夾路徑
|
||
* @param onSuccess 成功後的回調函數
|
||
*/
|
||
export async function createNewFolder(app: App, folderPath: string, onSuccess?: () => void) {
|
||
// 預設名稱
|
||
const defaultName = `${t('untitled')}`;
|
||
|
||
// 先彈出名稱輸入框
|
||
showNameInputModal(app, {
|
||
title: t('new_folder'),
|
||
description: t('enter_new_folder_name'),
|
||
defaultValue: defaultName,
|
||
onSubmit: (inputName: string) => {
|
||
void (async () => {
|
||
let baseName = (inputName && inputName.trim().length > 0) ? inputName.trim() : defaultName;
|
||
let newFolderName = baseName;
|
||
let newFolderPath = !folderPath || folderPath === '/' ? newFolderName : `${folderPath}/${newFolderName}`;
|
||
|
||
// 檢查資料夾是否已存在,如果存在則遞增編號
|
||
let counter = 1;
|
||
while (app.vault.getAbstractFileByPath(newFolderPath)) {
|
||
newFolderName = `${baseName} ${counter}`;
|
||
newFolderPath = !folderPath || folderPath === '/' ? newFolderName : `${folderPath}/${newFolderName}`;
|
||
counter++;
|
||
}
|
||
|
||
try {
|
||
// 建立新資料夾
|
||
await app.vault.createFolder(newFolderPath);
|
||
// 執行成功回調
|
||
if (onSuccess) {
|
||
onSuccess();
|
||
}
|
||
} catch (error) {
|
||
console.error('An error occurred while creating a new folder:', error);
|
||
}
|
||
})();
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 創建新畫布的共用函數
|
||
* @param app Obsidian App 實例
|
||
* @param folderPath 目標資料夾路徑
|
||
*/
|
||
export async function createNewCanvas(app: App, folderPath: string) {
|
||
let newFileName = `${t('untitled')}.canvas`;
|
||
let newFilePath = !folderPath || folderPath === '/' ? newFileName : `${folderPath}/${newFileName}`;
|
||
|
||
// 檢查檔案是否已存在,如果存在則遞增編號
|
||
let counter = 1;
|
||
while (app.vault.getAbstractFileByPath(newFilePath)) {
|
||
newFileName = `${t('untitled')} ${counter}.canvas`;
|
||
newFilePath = !folderPath || folderPath === '/' ? newFileName : `${folderPath}/${newFileName}`;
|
||
counter++;
|
||
}
|
||
|
||
try {
|
||
// 建立新畫布
|
||
const newFile = await app.vault.create(newFilePath, '');
|
||
// 開啟新畫布
|
||
await app.workspace.getLeaf().openFile(newFile);
|
||
} catch (error) {
|
||
console.error('An error occurred while creating a new canvas:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 創建新 base 檔案的共用函數
|
||
* @param app Obsidian App 實例
|
||
* @param folderPath 目標資料夾路徑
|
||
*/
|
||
export async function createNewBase(app: App, folderPath: string) {
|
||
let newFileName = `${t('untitled')}.base`;
|
||
let newFilePath = !folderPath || folderPath === '/' ? newFileName : `${folderPath}/${newFileName}`;
|
||
|
||
// 檢查檔案是否已存在,如果存在則遞增編號
|
||
let counter = 1;
|
||
while (app.vault.getAbstractFileByPath(newFilePath)) {
|
||
newFileName = `${t('untitled')} ${counter}.base`;
|
||
newFilePath = !folderPath || folderPath === '/' ? newFileName : `${folderPath}/${newFileName}`;
|
||
counter++;
|
||
}
|
||
|
||
try {
|
||
// 建立新 base 檔案
|
||
const newFile = await app.vault.create(newFilePath, '');
|
||
// 開啟新檔案
|
||
await app.workspace.getLeaf().openFile(newFile);
|
||
} catch (error) {
|
||
console.error('An error occurred while creating a new base:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 創建捷徑檔案的共用函數
|
||
* @param app Obsidian App 實例
|
||
* @param folderPath 目標資料夾路徑
|
||
* @param option 捷徑選項
|
||
*/
|
||
export async function createShortcut(
|
||
app: App,
|
||
folderPath: string,
|
||
option: {
|
||
type: 'mode' | 'folder' | 'file' | 'search' | 'uri';
|
||
value: string;
|
||
display: string;
|
||
searchOptions?: {
|
||
searchCurrentLocationOnly: boolean;
|
||
searchFilesNameOnly: boolean;
|
||
searchMediaFiles: boolean;
|
||
};
|
||
}
|
||
) {
|
||
try {
|
||
// 生成不重複的檔案名稱
|
||
let counter = 0;
|
||
let shortcutName: string;
|
||
|
||
// 對於 URI 類型,使用特殊的檔名生成邏輯
|
||
if (option.type === 'uri') {
|
||
shortcutName = generateFilenameFromUri(option.value);
|
||
} else {
|
||
shortcutName = `${option.display}`;
|
||
}
|
||
|
||
let newName = `${shortcutName}.md`;
|
||
let newPath = !folderPath || folderPath === '/' ? newName : `${folderPath}/${newName}`;
|
||
while (app.vault.getAbstractFileByPath(newPath)) {
|
||
counter++;
|
||
const baseName = option.type === 'uri' ? generateFilenameFromUri(option.value) : option.display;
|
||
shortcutName = `${baseName} ${counter}`;
|
||
newName = `${shortcutName}.md`;
|
||
newPath = !folderPath || folderPath === '/' ? newName : `${folderPath}/${newName}`;
|
||
}
|
||
|
||
// 創建新檔案
|
||
const newFile = await app.vault.create(newPath, '');
|
||
|
||
// 使用 processFrontMatter 來更新 frontmatter
|
||
await app.fileManager.processFrontMatter(newFile, (frontmatter: ShortcutFrontmatter) => {
|
||
if (option.type === 'mode') {
|
||
frontmatter.type = 'mode';
|
||
frontmatter.redirect = option.value;
|
||
} else if (option.type === 'folder') {
|
||
frontmatter.type = 'folder';
|
||
frontmatter.redirect = option.value;
|
||
} else if (option.type === 'file') {
|
||
const targetFile = app.vault.getAbstractFileByPath(option.value);
|
||
if (!(targetFile instanceof TFile)) {
|
||
return;
|
||
}
|
||
|
||
const link = app.fileManager.generateMarkdownLink(targetFile, "");
|
||
frontmatter.type = "file";
|
||
frontmatter.redirect = link;
|
||
} else if (option.type === 'search') {
|
||
frontmatter.type = 'search';
|
||
frontmatter.redirect = option.value;
|
||
// 添加搜尋選項到 frontmatter
|
||
if (option.searchOptions) {
|
||
frontmatter.searchCurrentLocationOnly = option.searchOptions.searchCurrentLocationOnly;
|
||
frontmatter.searchFilesNameOnly = option.searchOptions.searchFilesNameOnly;
|
||
frontmatter.searchMediaFiles = option.searchOptions.searchMediaFiles;
|
||
}
|
||
} else if (option.type === 'uri') {
|
||
frontmatter.type = 'uri';
|
||
frontmatter.redirect = option.value;
|
||
}
|
||
});
|
||
|
||
new Notice(`${t('shortcut_created')}: ${shortcutName}`);
|
||
|
||
} catch (error) {
|
||
console.error('Create shortcut error', error);
|
||
new Notice(t('failed_to_create_shortcut'));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 從 URI 生成檔案名稱
|
||
* @param uri URI 字串
|
||
* @returns 檔案名稱
|
||
*/
|
||
function generateFilenameFromUri(uri: string): string {
|
||
try {
|
||
// 處理 obsidian:// 協議
|
||
if (uri.startsWith('obsidian://')) {
|
||
const match = uri.match(/obsidian:\/\/([^?]+)/);
|
||
let vaultName = '';
|
||
|
||
// 嘗試提取 vault 參數
|
||
const vaultMatch = uri.match(/[?&]vault=([^&]+)/);
|
||
if (vaultMatch) {
|
||
vaultName = decodeURIComponent(vaultMatch[1]);
|
||
// 清理 vault 名稱,移除不適合檔名的字符
|
||
vaultName = vaultName.replace(/[<>:"/\\|?*]/g, '_');
|
||
}
|
||
|
||
if (match) {
|
||
const action = match[1];
|
||
const vaultSuffix = vaultName ? ` (${vaultName})` : '';
|
||
|
||
// 根據不同的 obsidian 動作生成檔名
|
||
switch (action) {
|
||
case 'open':
|
||
return `🌐 Obsidian Open${vaultSuffix}`;
|
||
case 'new':
|
||
return `🌐 Obsidian New${vaultSuffix}`;
|
||
case 'search':
|
||
return `🌐 Obsidian Search${vaultSuffix}`;
|
||
case 'hook-get-address':
|
||
return `🌐 Obsidian Hook${vaultSuffix}`;
|
||
default:
|
||
return `🌐 Obsidian ${action}${vaultSuffix}`;
|
||
}
|
||
}
|
||
return vaultName ? `🌐 Obsidian Link (${vaultName})` : '🌐 Obsidian Link';
|
||
}
|
||
|
||
// 處理 file:// 協議
|
||
if (uri.startsWith('file://')) {
|
||
const filename = uri.split('/').pop() || 'Local File';
|
||
return `🌐 ${filename}`;
|
||
}
|
||
|
||
// 處理 http/https 協議
|
||
if (uri.startsWith('http://') || uri.startsWith('https://')) {
|
||
const url = new URL(uri);
|
||
let domain = url.hostname;
|
||
|
||
// 移除 www. 前綴
|
||
if (domain.startsWith('www.')) {
|
||
domain = domain.substring(4);
|
||
}
|
||
|
||
// 如果有路徑,嘗試提取有意義的部分
|
||
if (url.pathname && url.pathname !== '/') {
|
||
const pathParts = url.pathname.split('/').filter(part => part.length > 0);
|
||
if (pathParts.length > 0) {
|
||
const lastPart = pathParts[pathParts.length - 1];
|
||
// 如果最後一部分看起來像檔名或有意義的標識符
|
||
if (lastPart.length < 50 && !lastPart.includes('?')) {
|
||
return `🌐 ${domain} - ${lastPart}`;
|
||
}
|
||
}
|
||
}
|
||
|
||
return `🌐 ${domain}`;
|
||
}
|
||
|
||
// 其他協議的處理
|
||
const protocolMatch = uri.match(/^([^:]+):/);
|
||
if (protocolMatch) {
|
||
const protocol = protocolMatch[1].toUpperCase();
|
||
return `🌐 ${protocol} Link`;
|
||
}
|
||
|
||
// 如果不是標準 URI,直接使用前 30 個字符
|
||
const cleanUri = uri.replace(/[<>:"/\\|?*]/g, '_').substring(0, 30);
|
||
return `🌐 ${cleanUri}`;
|
||
|
||
} catch {
|
||
// 如果解析失敗,使用安全的預設名稱
|
||
const cleanUri = uri.replace(/[<>:"/\\|?*]/g, '_').substring(0, 30);
|
||
return `🌐 ${cleanUri}`;
|
||
}
|
||
}
|