diff --git a/README.md b/README.md index 3fb3980..0245a5c 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,14 @@ If you like my plugin, I would appreciate it if you could buy me a cup of coffee [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/iiz00)

## Changelog +- 0.4.0 + - Improvements + - You can now choose whether to open the location of the element or the linked file when you click on a link element. + - The context menu of the link element now allows you to open the linked file in a new tab/new pane/new window. + - You can now choose whether to open the view of the active file at startup or the view that was open when you last closed the app. + - You can now set the size of the pop-out window and whether it should be displayed in the foreground(always on top). + - Fixed + - Fixed an issue where non-markdown files would fail to display on iOS. - 0.3.0 - New functions - Recent/Favorites diff --git a/manifest.json b/manifest.json index 30c574d..d234075 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "multiple-notes-outline", "name": "Multiple Notes Outline", - "version": "0.3.0", + "version": "0.4.0", "minAppVersion": "1.3.0", "description": "Add custom views which show outlines of multiple notes with headings, links, tags and list items.", "author": "iiz", diff --git a/package.json b/package.json index 61c3a01..7508e20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multiple-notes-outline", - "version": "0.3.0", + "version": "0.4.0", "description": "Add custom views which shows outline of multiple notes at once", "main": "main.js", "scripts": { diff --git a/src/constructDOM.ts b/src/constructDOM.ts index 9ca747b..748f24c 100644 --- a/src/constructDOM.ts +++ b/src/constructDOM.ts @@ -11,9 +11,8 @@ export function constructNoteDOM (files:TAbstractFile[], status: FileStatus[], i parentEl:HTMLElement, category:Category, aotEl:HTMLElement, srcFile: TAbstractFile, order:number[]): void { for (let i=0; i - item + item .setTitle("MNO: Stop displaying at the top") .setIcon('pin-off') .onClick(async ()=> { @@ -207,7 +211,7 @@ export function constructNoteDOM (files:TAbstractFile[], status: FileStatus[], i // favoriteに追加/削除 if (this.settings.favorite[noteType].includes(files[si].path)){ menu.addItem((item)=> - item + item .setTitle("MNO: Remove from favorites") .setIcon('bookmark-minus') .onClick(async ()=> { @@ -224,6 +228,7 @@ export function constructNoteDOM (files:TAbstractFile[], status: FileStatus[], i await this.plugin.saveSettings(); })) } + menu.addSeparator(); if (noteType == 'file'){ //新規タブ に開く @@ -252,19 +257,32 @@ export function constructNoteDOM (files:TAbstractFile[], status: FileStatus[], i this.app.workspace.getLeaf('split').openFile(files[si]); }) ); + //新規ウィンドウに開く + menu.addItem((item)=> + item + .setTitle("Open in new window") + .setIcon("scan") + .onClick(async()=> { + if (files[si] != this.activeFile){ + this.holdUpdateOnce = true; + } + // await this.app.workspace.getLeaf('window').openFile(linkTarget); + await this.app.workspace.openPopoutLeaf({size:{width:this.settings.popoutSize.width,height:this.settings.popoutSize.height}}).openFile(files[si]); + if (this.settings.popoutAlwaysOnTop){ + setPopoutAlwaysOnTop(); + } + }) + ); } - - - this.app.workspace.trigger( - "file-menu", - menu, - files[si], - 'link-context-menu' - ); + // this.app.workspace.trigger( + // "file-menu", + // menu, + // files[si], + // 'link-context-menu' + // ); menu.showAtMouseEvent(event); } ); - // もしアウトラインが準備できていなければスキップする if (!status[si].outlineReady){ @@ -306,9 +324,7 @@ export function constructNoteDOM (files:TAbstractFile[], status: FileStatus[], i export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineData[], parentEl:HTMLElement, category: Category):void{ - - // include mode 用の変数を宣言 filter関連コメントアウト // let isIncluded = this.settings.includeBeginning; // let includeModeHeadingLevel: number; @@ -364,21 +380,29 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat } - const outlineEl: HTMLElement = parentEl.createDiv("tree-item nav-file"); const outlineTitle: HTMLElement = outlineEl.createDiv("tree-item-self is-clickable nav-file-title"); setIcon(outlineTitle,'link'); outlineTitle.style.paddingLeft ='0.5em'; outlineTitle.createDiv("tree-item-inner nav-file-title-content").setText(info.frontmatterLinks[j].displayText); - - + //クリック時 outlineTitle.addEventListener( "click", (event: MouseEvent) => { - event.preventDefault(); - this.app.workspace.getLeaf().openFile(file); + if (this.settings.openLinkByClick){ + // openLinkByClick が trueならリンク先を開く + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + this.app.workspace.getLeaf().openFile(linkTarget); + } else { + if (file != this.activeFile){ + this.holdUpdateOnce = true; + } + this.app.workspace.getLeaf().openFile(file); + } }, false ); @@ -431,31 +455,73 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat .setTitle("Open linked file") .setIcon("links-going-out") .onClick(async()=>{ + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } await this.app.workspace.getLeaf().openFile(linkTarget); + if (linkSubpath){ - const view = this.app.workspace.getActiveViewOfType(MarkdownView); const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); - - if (view && subpathPosition) { - view.editor.focus(); - view.editor.setCursor (subpathPosition.start?.line, 0); - view.editor.scrollIntoView( { - from: { - line: subpathPosition.start?.line, - ch:0 - }, - to: { - line: subpathPosition.start?.line, - ch:0 - } - }, true); - } + scrollToElement(subpathPosition.start?.line,0,this.app); } - }) ); menu.addSeparator(); + //リンク先を新規タブに開く + menu.addItem((item)=> + item + .setTitle("Open linked file in new tab") + .setIcon("file-plus") + .onClick(async()=> { + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf('tab').openFile(linkTarget); + if (linkSubpath){ + // linkSubpathがあるときはそこまでスクロール + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + }) + ); + //リンク先を右に開く + menu.addItem((item)=> + item + .setTitle("Open linked file to the right") + .setIcon("separator-vertical") + .onClick(async()=> { + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf('split').openFile(linkTarget); + if (linkSubpath){ + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + }) + ); + //リンク先を新規ウィンドウに開く + menu.addItem((item)=> + item + .setTitle("Open linked file in new window") + .setIcon("scan") + .onClick(async()=> { + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + // await this.app.workspace.getLeaf('window').openFile(linkTarget); + await this.app.workspace.openPopoutLeaf({size:{width:this.settings.popoutSize.width,height:this.settings.popoutSize.height}}).openFile(linkTarget); + if (linkSubpath){ + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + if (this.settings.popoutAlwaysOnTop){ + setPopoutAlwaysOnTop(); + } + }) + ); + menu.addSeparator(); //新規タブに開く menu.addItem((item)=> @@ -601,30 +667,6 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat // links if (element == 'link'){ - // mainは設定次第でリンクは非表示(アウトゴーイングファイル群で代替できるので) - // if (this.settings.hideLinksBetweenRelatedFiles != 'none'){ - // if (category == 'main'){ - // continue; - // } - // if (this.settings.hideLinksBetweenRelatedFiles == 'mainOnly'){ - - // if (category == 'backlink' && app.metadataCache.getFirstLinkpathDest(data[j].link, file.path)?.path == this.targetFiles.main[0].path){ - // continue; - // } - // } else { - // // hideLinksBetweenrelatedFiles == 'all' - // const linktargetpath = app.metadataCache.getFirstLinkpathDest(data[j].link, file.path)?.path; - // if (linktargetpath){ - // for (let category in this.targetFiles){ - // // data[j].linkに一致するファイル名があるかどうかの処理。 - // if (this.targetFiles[category].some( (targetfile) => targetfile.path == linktargetpath)){ - // continue elementloop; - // } - // } - // } - - // } - // } if (this.settings.hideLinksBetweenRelatedFiles == 'mainOnly'){ if (category == 'main'){ continue; @@ -773,7 +815,7 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat if (this.settings.showElements[data[k].typeOfElement]){ //ただし各種の実際には非表示となる条件を満たしていたら打ち切らない // リストの設定による非表示 - if (data[k].typeOfElement == 'listItems' && + if (data[k].typeOfElement == 'listItems' && ( data[k].level >=2 || ((this.settings.allRootItems == false && data[k].level == 1) && (this.settings.allTasks == false || data[k].task === void 0)) || (this.settings.taskOnly && data[k].task === void 0) || @@ -808,40 +850,34 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat outlineTitle.dataset.tooltipPosition = this.settings.tooltipPreviewDirection; outlineTitle.setAttribute('data-tooltip-delay','10'); - // outlineTitle.setAttribute('aria-label-classes','daily-note-preview'); } - + //クリック時 outlineTitle.addEventListener( "click", async(event: MouseEvent) => { - event.preventDefault(); - if (file != this.activeFile){ - this.holdUpdateOnce = true; - } - await this.app.workspace.getLeaf().openFile(file); - const view = this.app.workspace.getActiveViewOfType(MarkdownView); - - if (view) { - view.editor.focus(); - - view.editor.setCursor (data[j].position.start.line, data[j].position.start.col); - view.editor.scrollIntoView( { - from: { - line: data[j].position.start.line, - ch:0 - }, - to: { - line: data[j].position.start.line, - ch:0 - } - }, true); + if (this.settings.openLinkByClick == true && element =='link'){ + // openLinkByClick true かつエレメントがリンクならリンク先を開く + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf().openFile(linkTarget); + if (linkSubpath){ + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + } else { + if (file != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf().openFile(file); + scrollToElement(data[j].position.start.line,data[j].position.start.col,this.app); } }, false ); - //hover preview + //hover preview outlineTitle.addEventListener('mouseover', (event: MouseEvent) => { // リンクエレメントでリンク先が存在するときはそちらをプレビュー if (element == 'link' && linkTarget){ @@ -904,34 +940,73 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat .setTitle("Open linked file") .setIcon("links-going-out") .onClick(async()=>{ + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } await this.app.workspace.getLeaf().openFile(linkTarget); if (linkSubpath){ - const view = this.app.workspace.getActiveViewOfType(MarkdownView); const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); - - if (view && subpathPosition) { - view.editor.focus(); - view.editor.setCursor (subpathPosition.start?.line, 0); - view.editor.scrollIntoView( { - from: { - line: subpathPosition.start?.line, - ch:0 - }, - to: { - line: subpathPosition.start?.line, - ch:0 - } - }, true); - } + scrollToElement(subpathPosition.start?.line,0,this.app); + } + }) + ); + menu.addSeparator(); + //リンク先を新規タブに開く + menu.addItem((item)=> + item + .setTitle("Open linked file in new tab") + .setIcon("file-plus") + .onClick(async()=> { + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf('tab').openFile(linkTarget); + if (linkSubpath){ + // linkSubpathがあるときはそこまでスクロール + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + }) + ); + //リンク先を右に開く + menu.addItem((item)=> + item + .setTitle("Open linked file to the right") + .setIcon("separator-vertical") + .onClick(async()=> { + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf('split').openFile(linkTarget); + if (linkSubpath){ + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + }) + ); + //リンク先を新規ウィンドウに開く + menu.addItem((item)=> + item + .setTitle("Open linked file in new window") + .setIcon("scan") + .onClick(async()=> { + if (linkTarget != this.activeFile){ + this.holdUpdateOnce = true; + } + // await this.app.workspace.getLeaf('window').openFile(linkTarget); + await this.app.workspace.openPopoutLeaf({size:{width:this.settings.popoutSize.width,height:this.settings.popoutSize.height}}).openFile(linkTarget); + if (linkSubpath){ + const subpathPosition = getSubpathPosition(this.app, linkTarget, linkSubpath); + scrollToElement(subpathPosition.start?.line, 0, this.app); + } + if (this.settings.popoutAlwaysOnTop){ + setPopoutAlwaysOnTop(); } - - - }) ); menu.addSeparator(); } - + // タグの場合 if (element =='tag'){ menu.addItem((item)=> item @@ -981,17 +1056,8 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat if (file != this.activeFile){ this.holdUpdateOnce = true; } - await this.app.workspace.getLeaf('window').openFile(file); - // open in new windowはgetLeaf('window').openFile(file)でやるより - // openPopoutLeaf({size:{}}).openFile(file)の方がコンパクト?要調査 - - // console.log(this.app.workspace.floatingSplit) - // await this.app.workspace.openPopoutLeaf({size:{ - // width: 200, height: 200 - // }}).openFile(file); + await this.app.workspace.openPopoutLeaf({size:{width:this.settings.popoutSize.width,height:this.settings.popoutSize.height}}).openFile(file); scrollToElement(data[j].position.start.line, data[j].position.start.col, this.app); - //console.log(this.app.workspace.floatingSplit); - //require("electron").remote.getCurrentWindow().setAlwaysOnTop(true); }) ); @@ -1035,7 +1101,7 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat // main以外の場合、backlink filesの処理 if (category =='main' || this.settings.showBacklinks == false || !info.backlinks){ return; - } + } backlinkfileloop: for (let i = 0; i < info.backlinks?.length; i++){ // targetFilesに含まれていれば除外する @@ -1063,7 +1129,6 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat this.holdUpdateOnce = true; } await this.app.workspace.getLeaf().openFile(info.backlinks[i]); - const view = this.app.workspace.getActiveViewOfType(MarkdownView); }, false ); @@ -1078,6 +1143,56 @@ export function constructOutlineDOM (file:TFile, info:FileInfo, data: OutlineDat linktext: info.backlinks[i].path, }); }); + + // contextmenu + outlineTitle.addEventListener( + "contextmenu", + (event: MouseEvent) => { + const menu = new Menu(); + //リンク先を新規タブに開く + menu.addItem((item)=> + item + .setTitle("Open backlink file in new tab") + .setIcon("file-plus") + .onClick(async()=> { + if (info.backlinks[i] != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf('tab').openFile(info.backlinks[i]); + }) + ); + //リンク先を右に開く + menu.addItem((item)=> + item + .setTitle("Open linked file to the right") + .setIcon("separator-vertical") + .onClick(async()=> { + if (info.backlinks[i] != this.activeFile){ + this.holdUpdateOnce = true; + } + await this.app.workspace.getLeaf('split').openFile(info.backlinks[i]); + + }) + ); + //リンク先を新規ウィンドウに開く + menu.addItem((item)=> + item + .setTitle("Open linked file in new window") + .setIcon("scan") + .onClick(async()=> { + if (info.backlinks[i] != this.activeFile){ + this.holdUpdateOnce = true; + } + // await this.app.workspace.getLeaf('window').openFile(info.backlinks[i]); + await this.app.workspace.openPopoutLeaf({size:{width:this.settings.popoutSize.width,height:this.settings.popoutSize.height}}).openFile(info.backlinks[i]); + + if (this.settings.popoutAlwaysOnTop){ + setPopoutAlwaysOnTop(); + } + }) + ); + menu.showAtMouseEvent(event); + }); } @@ -1103,6 +1218,12 @@ export function scrollToElement(line: number, col: number, app: App): void { } } +function setPopoutAlwaysOnTop(){ + const { remote } = require('electron'); + const activeWindow = remote.BrowserWindow.getFocusedWindow(); + activeWindow.setAlwaysOnTop(true); +} + // ファイル情報を付加表示 function attachFileInfo (targetEl: HTMLElement, status: FileStatus, info: FileInfo, data: OutlineData[],displayFileInfo: string):void { diff --git a/src/drawUI.ts b/src/drawUI.ts index 9efa81e..bba603c 100644 --- a/src/drawUI.ts +++ b/src/drawUI.ts @@ -2,7 +2,7 @@ import { MultipleNotesOutlineView } from "./fileView"; import { MultipleNotesOutlineFolderView } from "./folderView"; import MultipleNotesOutlinePlugin from "./main"; -import { setIcon, TFile, Menu, TFolder } from 'obsidian'; +import { setIcon, TFile, Menu, TFolder, Notice } from 'obsidian'; import { ModalJump, updateFavAndRecent } from "./FavAndRecent"; // 操作アイコン部分を描画 @@ -142,13 +142,11 @@ function uiUpdateFolderView (parentEl:HTMLElement):void{ this.hasMainChanged = true; updateFavAndRecent.call(this, this.targetFolder.path,'folder','recent'); - // 保留 // this.app.workspace.requestSaveLayout(); this.refreshView(true, true); - } } ); diff --git a/src/fileView.ts b/src/fileView.ts index 473670f..b23b709 100644 --- a/src/fileView.ts +++ b/src/fileView.ts @@ -159,7 +159,6 @@ export class MultipleNotesOutlineView extends ItemView { // } else { // await this.initView(); // } - } async onClose(){ @@ -167,30 +166,45 @@ export class MultipleNotesOutlineView extends ItemView { } private async initView() { - await this.bootDelay(); + await this.bootDelay(); checkRelatedFiles(this.app, this.settings); checkFavAndRecentFiles(this.app, this.settings, this.viewType); - + this.collapseAll = this.settings.collapseAllAtStartup; // ノートタイトル背景色の設定 this.theme = getTheme(); setNoteTitleBackgroundColor(this.theme, this.settings); - + // 初期の表示対象ファイルを取得(アクティブファイルまたは最後に表示したファイル) this.activeFile = this.app.workspace.getActiveFile(); if (this.activeFile){ - this.targetFiles.main[0]= this.activeFile; - this.refreshView(true, true); + if (this.settings.openRecentAtStartup.file && this.app.vault.getAbstractFileByPath(this.settings.recent.file?.[0]) instanceof TFile){ + this.targetFiles.main[0] = this.app.vault.getAbstractFileByPath(this.settings.recent.file?.[0]) as TFile; + if (this.settings.pinAfterJump && this.settings.autoupdateFileView){ + this.pinnedMode = true; + } + } else { + this.targetFiles.main[0]= this.activeFile; + } } else { - console.log("Multiple Notes Outline: failed to get active file"); + if (this.app.vault.getAbstractFileByPath(this.settings.recent.file?.[0]) instanceof TFile){ + this.targetFiles.main[0] = this.app.vault.getAbstractFileByPath(this.settings.recent.file?.[0]) as TFile; + if (this.settings.pinAfterJump && this.settings.autoupdateFileView){ + this.pinnedMode = true; + } + } else { + this.targetFiles.main[0] = null; + } } + this.refreshView(true, true); + //自動更新のためのデータ変更、ファイル追加/削除の監視 observe file change/create/delete const debouncerRequestRefresh:Debouncer<[]> = debounce(this.autoRefresh,3000,true); this.flagChanged = false; - this.flagRegetTarget = false; + this.flagRegetTarget = false; this.registerEvent(this.app.workspace.on('file-open', (file) => { if (file instanceof TFile && file !== this.activeFile){ @@ -273,7 +287,6 @@ export class MultipleNotesOutlineView extends ItemView { })); this.registerEvent(this.app.workspace.on('css-change', (e)=>{ - const newTheme = getTheme(); if (newTheme !== this.theme){ this.theme = newTheme; @@ -373,7 +386,7 @@ export class MultipleNotesOutlineView extends ItemView { this.flagSaveSettings = false; } - // リフレッシュセンター + // リフレッシュセンター // flagGetTargetがtrue: 対象ファイルを再取得 // flagGetOutlineがtrue: アウトライン情報を再取得 // その後UI部分とアウトライン部分を描画 @@ -381,7 +394,7 @@ export class MultipleNotesOutlineView extends ItemView { // 描画所要時間を測定 const startTime = performance.now(); - + //dataviewオンオフチェック this.isDataviewEnabled = checkDataview(this.app); @@ -390,9 +403,15 @@ export class MultipleNotesOutlineView extends ItemView { const previousY = containerEl?.scrollTop ? containerEl.scrollTop : 0; + // メインターゲットが取得できていなければUIアイコンのみ描画 + if (!this.targetFiles.main[0]){ + drawUI.call(this); + return; + } + // メインターゲットファイルのfileInfoとoutlineを取得 this.filecount = 0; - if (flagGetTarget){ + if (this.targetFiles.main[0] && flagGetTarget){ this.fileStatus.main = initFileStatus(this.targetFiles.main); this.fileOrder.main = [...Array(this.targetFiles.main.length)].map((_, i) => i); @@ -403,7 +422,7 @@ export class MultipleNotesOutlineView extends ItemView { this.fileStatus.outgoing = initFileStatus(this.targetFiles.outgoing); this.fileOrder.outgoing = [...Array(this.targetFiles.outgoing.length)].map((_, i) => i); - this.targetFiles.backlink = this.fileInfo.main[0].backlinks; + this.targetFiles.backlink = this.fileInfo.main[0]?.backlinks; this.fileStatus.backlink = initFileStatus(this.targetFiles.backlink); this.fileOrder.backlink = [...Array(this.targetFiles.backlink.length)].map((_, i) => i); @@ -426,7 +445,7 @@ export class MultipleNotesOutlineView extends ItemView { const midTime = performance.now(); if (this.settings.showDebugInfo){ - console.log('Multiple Notes Outline: time required to get outlines, file view: ',this.targetFiles.main[0].path, midTime - startTime); + console.log('Multiple Notes Outline: time required to get outlines, file view: ',this.targetFiles.main[0].path, midTime - startTime); } drawUI.call(this); @@ -435,9 +454,9 @@ export class MultipleNotesOutlineView extends ItemView { // 描画所要時間を測定 const endTime = performance.now(); if (this.settings.showDebugInfo){ - console.log('Multiple Notes Outline: time required to draw outlines, file view: ',this.targetFiles.main[0].path, endTime - midTime, previousY); + console.log('Multiple Notes Outline: time required to draw outlines, file view: ',this.targetFiles.main[0].path, endTime - midTime, previousY); - console.log('Multiple Notes Outline: time required to refresh view, file view',this.targetFiles.main[0].path, endTime - startTime); + console.log('Multiple Notes Outline: time required to refresh view, file view',this.targetFiles.main[0].path, endTime - startTime); } } @@ -451,7 +470,7 @@ export class MultipleNotesOutlineView extends ItemView { status[i].isTop =true; } - if ((this.filecount < this.settings.readLimit || status[i].isTop ) && !Object.values(status[i].duplicated).includes(true)){ + if ( (this.filecount < this.settings.readLimit || status[i].isTop ) && !Object.values(status[i].duplicated).includes(true)){ // files.length ==1 の場合、メインターゲットファイルを対象にしている可能性があるため、必ずbacklink filesを取得する。 const info = await getFileInfo(this.app, files[i], this.settings, Boolean( files.length == 1), this.isDataviewEnabled); fileInfo.push(info); @@ -511,7 +530,7 @@ export class MultipleNotesOutlineView extends ItemView { this.constructCategoryDOM('backlink', 'links-coming-in', 'Backlink Files', rootChildrenEl, categoryAOTEl); } - // アウトライン部分の描画実行 + // アウトライン部分の描画実行 this.contentEl.appendChild(containerEl); // スクロール位置を復元 @@ -554,7 +573,7 @@ export class MultipleNotesOutlineView extends ItemView { if (!this.collapseCategory[category]){ constructNoteDOM.call(this, this.targetFiles[category], this.fileStatus[category], this.fileInfo[category], this.outlineData[category], - categoryChildrenEl, category, aotEl, this.targetFiles.main[0],this.fileOrder[category]); + categoryChildrenEl, category, aotEl, this.targetFiles.main[0],this.fileOrder[category]); } else { categoryEl.classList.add('is-collapsed'); categoryCollapseIcon.classList.add('is-collapsed'); diff --git a/src/folderView.ts b/src/folderView.ts index 902e454..663f854 100644 --- a/src/folderView.ts +++ b/src/folderView.ts @@ -1,4 +1,4 @@ -import { setIcon, debounce, Debouncer, Menu, TFolder} from 'obsidian'; +import { setIcon, debounce, Debouncer, Menu, TFolder, Notice} from 'obsidian'; import { ItemView, WorkspaceLeaf, TFile, TAbstractFile} from 'obsidian' @@ -90,20 +90,19 @@ export class MultipleNotesOutlineFolderView extends ItemView { this.plugin = plugin; this.settings = settings; } - + getViewType(): string { return MultipleNotesOutlineFolderViewType; } - + getDisplayText(): string { return 'MNO - folder view'; } - + getIcon(): string { return 'folders'; } - async onOpen(){ await this.initView(); @@ -129,8 +128,8 @@ export class MultipleNotesOutlineFolderView extends ItemView { } private async initView() { - await this.bootDelay(); - + await this.bootDelay(); + checkRelatedFiles(this.app, this.settings); checkFavAndRecentFiles(this.app, this.settings, this.viewType); @@ -139,23 +138,30 @@ export class MultipleNotesOutlineFolderView extends ItemView { // ノートタイトル背景色の設定 this.theme = getTheme(); setNoteTitleBackgroundColor(this.theme, this.settings); - - if (this.targetFolder){ - this.refreshView(true, true); - } else { - this.activeFile = this.app.workspace.getActiveFile(); - if (this.activeFile){ + + // 初期の表示対象フォルダを取得(アクティブファイルのフォルダまたは最後に表示したフォルダ) + this.activeFile = this.app.workspace.getActiveFile(); + if (this.activeFile){ + if (this.settings.openRecentAtStartup.folder && this.app.vault.getAbstractFileByPath(this.settings.recent.folder?.[0]) instanceof TFolder){ + this.targetFolder = this.app.vault.getAbstractFileByPath(this.settings.recent.folder?.[0]) as TFolder; + } else { this.targetFolder = this.activeFile.parent; - this.refreshView(true,true); + } + } else { + if (this.app.vault.getAbstractFileByPath(this.settings.recent.folder?.[0]) instanceof TFolder){ + this.targetFolder = this.app.vault.getAbstractFileByPath(this.settings.recent.folder?.[0]) as TFolder; } else { - console.log("Multiple Notes Outline: failed to get active file"); + this.targetFolder = null; } } + this.refreshView(true,true); + + //自動更新のためのデータ変更、ファイル追加/削除の監視 observe file change/create/delete const debouncerRequestRefresh:Debouncer<[]> = debounce(this.autoRefresh,3000,true); this.flagChanged = false; - this.flagRegetTarget = false; + this.flagRegetTarget = false; //今後アクティブファイルに色づけする場合などは処理を追加 // this.registerEvent(this.app.workspace.on('file-open', (file) => { @@ -281,7 +287,14 @@ export class MultipleNotesOutlineFolderView extends ItemView { const containerEl = document.getElementById('MNOfolderview-listcontainer'); const previousY = containerEl?.scrollTop ? containerEl.scrollTop : 0; + // new Notice('scroll done'); + + //ターゲットフォルダが取得できていなければUIアイコンのみ描画 + if (!this.targetFolder){ + drawUIFolderView.call(this); + return; + } // フォルダに含まれるファイルを取得 this.filecount = 0; if (flagGetTarget){ @@ -293,17 +306,16 @@ export class MultipleNotesOutlineFolderView extends ItemView { const midTime = performance.now(); if (this.settings.showDebugInfo){ - console.log ('Multiple Notes Outline: time required to get outlines, folder view: ',this.targetFolder.path, midTime - startTime); + console.log ('Multiple Notes Outline: time required to get outlines, folder view: ',this.targetFolder.path, midTime - startTime); } - drawUIFolderView.call(this); this.drawOutline(previousY); // 描画所要時間を測定 const endTime = performance.now(); if (this.settings.showDebugInfo){ - console.log ('Multiple Notes Outline: time required to draw outlines, folder view: ',this.targetFolder.path, endTime - midTime); - console.log ('Multiple Notes Outline: time required to refresh view, folder view: ',this.targetFolder.path, endTime - startTime); + console.log ('Multiple Notes Outline: time required to draw outlines, folder view: ',this.targetFolder.path, endTime - midTime); + console.log ('Multiple Notes Outline: time required to refresh view, folder view: ',this.targetFolder.path, endTime - startTime); } } @@ -329,12 +341,11 @@ export class MultipleNotesOutlineFolderView extends ItemView { let fileInfo:FileInfo[] = []; let outlineData: OutlineData[][] = []; for (let i = 0; i < files.length; i++){ - //個別のAlways on Topの判定 if (checkFlag(this.targetFolder, files[i], 'top', this.settings) == true){ status[i].isTop =true; } - + //new Notice(`checkFlagいけた ${i} ${status[i].isFolder}`); if (status[i].isFolder){ // フォルダーなら新たにそのフォルダーを処理 fileInfo.push(undefined); @@ -342,10 +353,10 @@ export class MultipleNotesOutlineFolderView extends ItemView { if (!this.settings.collapseFolder){ await this.processFolder(files[i] as TFolder); status[i].outlineReady = true; - } + } } else { // ファイルなら情報を取得 - if ((this.filecount < this.settings.readLimit || status[i].isTop )){ + if (this.filecount < this.settings.readLimit || status[i].isTop ){ const info = await getFileInfo(this.app, files[i] as TFile, this.settings, false, this.isDataviewEnabled); fileInfo.push(info); @@ -356,7 +367,6 @@ export class MultipleNotesOutlineFolderView extends ItemView { } else { outlineData.push(undefined); } - } else { fileInfo.push(undefined); outlineData.push(undefined); diff --git a/src/getOutline.ts b/src/getOutline.ts index d8c57df..cb6ddce 100644 --- a/src/getOutline.ts +++ b/src/getOutline.ts @@ -27,22 +27,29 @@ export function initFileStatus(files: TAbstractFile[]): FileStatus[] { // 単一ファイルの情報取得 export async function getFileInfo(app: App, file: TFile, settings:MultipleNotesOutlineSettings, forceGetBacklinks: boolean = false, isDataviewEnabled:boolean): Promise { - - const content = await this.app.vault.cachedRead(file); - - const lines = content.split("\n"); - - const backlinkFiles = (settings.showBacklinks || forceGetBacklinks) ? getBacklinkFilesDataview( app, file, isDataviewEnabled): undefined; - - const info:FileInfo = { - lines: lines, - numOfLines: lines.length, - backlinks: backlinkFiles, - frontmatterLinks: undefined + if (file.extension == 'md'){ + const content = await this.app.vault.cachedRead(file); + const lines = content.split("\n"); + const backlinkFiles = (settings.showBacklinks || forceGetBacklinks) ? getBacklinkFilesDataview( app, file, isDataviewEnabled): undefined; + const info:FileInfo = { + lines: lines, + numOfLines: lines.length, + backlinks: backlinkFiles, + frontmatterLinks: undefined + } + return info; + } else { + // .md 以外 + const backlinkFiles = (settings.showBacklinks || forceGetBacklinks) ? getBacklinkFilesDataview( app, file, isDataviewEnabled): undefined; + const info: FileInfo = { + lines: [""], + numOfLines: 0, + backlinks: backlinkFiles, + frontmatterLinks: undefined + } + return info; } - - - return info; + } // 単一ファイルのアウトライン取得 @@ -51,13 +58,17 @@ export async function getOutline (app: App, file: TFile, status:FileStatus, info let data: OutlineData[] = []; const cache = app.metadataCache.getFileCache(file); + // .md以外は空アウトラインを返す + if(file.extension != 'md'){ + return data; + } // cacheはnullの場合がある if (!cache){ return null; } // properties(frontmatter)からリンクを取得 info.frontmatterLinks = cache?.frontmatterLinks; - + // headings,links,tags を抽出 // console.log('check headings',cache.hasOwnProperty("headings") ); diff --git a/src/getTargetFiles.ts b/src/getTargetFiles.ts index 7fd4a91..7ed0c6c 100644 --- a/src/getTargetFiles.ts +++ b/src/getTargetFiles.ts @@ -4,14 +4,14 @@ import { FileInfo, OutlineData } from 'src/main'; export function getOutgoingLinkFiles(app: App, file:TFile, info:FileInfo, cache:OutlineData[]):TFile[] | null { let files:TFile[] =[]; - for (let i=0; i< info.frontmatterLinks?.length; i++){ + for (let i=0; i< info?.frontmatterLinks?.length; i++){ const fileobj = app.metadataCache.getFirstLinkpathDest(info.frontmatterLinks[i].link, file.path); if (fileobj instanceof TFile){ files.push(fileobj); } } - for (let i = 0; i< cache.length; i++ ){ + for (let i = 0; i< cache?.length; i++ ){ if (cache[i].typeOfElement != 'link'){ continue; } diff --git a/src/main.ts b/src/main.ts index 189350e..fca90d2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -157,6 +157,20 @@ export interface MultipleNotesOutlineSettings { numOfRecentFiles: number; pinAfterJump: boolean; // fileViewにおいて履歴/お気に入りを開いたときにピンを付加するかどうか + openRecentAtStartup: { + file: boolean; + folder: boolean; + } + + popoutSize: { + width: number; + height: number; + } + + popoutAlwaysOnTop: boolean; + + openLinkByClick: boolean; + } // 設定項目デフォルト @@ -296,6 +310,20 @@ export const DEFAULT_SETTINGS: MultipleNotesOutlineSettings = { numOfRecentFiles: 30, pinAfterJump: true, + + openRecentAtStartup: { + file: false, + folder: false, + }, + + popoutSize: { + width: 600, + height: 800 + }, + popoutAlwaysOnTop: false, + + openLinkByClick: false, + } diff --git a/src/setting.ts b/src/setting.ts index f436a7d..f10ea19 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -231,6 +231,20 @@ export class MultipleNotesOutlineSettingTab extends PluginSettingTab { }) }); + new Setting(containerEl) + .setName("Open last view at startup") + .setDesc("If enabled, the most recently opened view is opened when File View is launched.") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.openRecentAtStartup.file) + .onChange(async (value) => { + this.plugin.settings.openRecentAtStartup.file = value; + this.display(); + await this.plugin.saveSettings(); + this.callRefreshView(false); + }) + }); + // 各カテゴリの表示/非表示 new Setting(containerEl) .setName("Show the main target file section") @@ -353,6 +367,20 @@ export class MultipleNotesOutlineSettingTab extends PluginSettingTab { }) }); + new Setting(containerEl) + .setName("Open last view at startup") + .setDesc("If enabled, the most recently opened view is opened when Folder View is launched.") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.openRecentAtStartup.folder) + .onChange(async (value) => { + this.plugin.settings.openRecentAtStartup.folder = value; + this.display(); + await this.plugin.saveSettings(); + this.callRefreshView(false); + }) + }); + // 各カテゴリの表示/非表示 new Setting(containerEl) .setName("Collapse subfolder") @@ -399,6 +427,24 @@ export class MultipleNotesOutlineSettingTab extends PluginSettingTab { cls:"setting-item-description", }); } + //リンク + this.containerEl.createEl("h4", { + text: "Links", + cls:"setting-category", + }); + new Setting(containerEl) + .setName("Open link by clicking link element") + .setDesc("If enabled, clicking on a link element opens the linked file instead of opening the element's position.") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.openLinkByClick) + .onChange(async (value) => { + this.plugin.settings.openLinkByClick = value; + this.display(); + await this.plugin.saveSettings(); + }) + }); + // プレビュー this.containerEl.createEl("h4", { @@ -453,6 +499,64 @@ export class MultipleNotesOutlineSettingTab extends PluginSettingTab { }) }); } + // Popout Window + this.containerEl.createEl("h4", { + text: "Popout window", + cls: 'setting-category' + }); + this.containerEl.createEl("p", { + text: "Popout window size", + cls: 'setting-category' + }); + new Setting(containerEl) + .setName("Width") + .setDesc("default & min = 600") + .addText((text) => { + text.inputEl.setAttr('type','number'); + text + .setPlaceholder(String(DEFAULT_SETTINGS.popoutSize.width)) + .setValue(String(this.plugin.settings.popoutSize.width)) + + text.inputEl.onblur = async (e: FocusEvent) => { + let parsed = parseInt((e.target as HTMLInputElement).value,10); + if (parsed <= 600){ + parsed = DEFAULT_SETTINGS.popoutSize.width; + } + this.plugin.settings.popoutSize.width = parsed; + await this.plugin.saveSettings(); + } + }); + + new Setting(containerEl) + .setName("Height") + .setDesc("default = 800 min = 600") + .addText((text) => { + text.inputEl.setAttr('type','number'); + text + .setPlaceholder(String(DEFAULT_SETTINGS.popoutSize.height)) + .setValue(String(this.plugin.settings.popoutSize.height)) + + text.inputEl.onblur = async (e: FocusEvent) => { + let parsed = parseInt((e.target as HTMLInputElement).value,10); + if (parsed <= 600){ + parsed = DEFAULT_SETTINGS.popoutSize.height; + } + this.plugin.settings.popoutSize.height = parsed; + await this.plugin.saveSettings(); + } + }); + + new Setting(containerEl) + .setName("Set popout window always on top") + .addToggle((toggle) => { + toggle + .setValue(this.plugin.settings.popoutAlwaysOnTop) + .onChange(async (value) => { + this.plugin.settings.popoutAlwaysOnTop = value; + this.display(); + await this.plugin.saveSettings(); + }) + }); // Always on Top this.containerEl.createEl("h4", { @@ -852,10 +956,10 @@ export class MultipleNotesOutlineSettingTab extends PluginSettingTab { .addOption("custom","custom") .setValue(this.plugin.settings.icon.heading) .onChange(async (value) => { - this.plugin.settings.icon.heading = value; - this.display(); - await this.plugin.saveSettings(); - this.callRefreshView(false); + this.plugin.settings.icon.heading = value; + this.display(); + await this.plugin.saveSettings(); + this.callRefreshView(false); }) });