mirror of
https://github.com/devon22/obsidian-gridexplorer.git
synced 2026-07-22 12:00:25 +00:00
3.1.4
This commit is contained in:
parent
fd374426f9
commit
47037a90ce
7 changed files with 126 additions and 100 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gridexplorer",
|
||||
"name": "GridExplorer",
|
||||
"version": "3.1.3",
|
||||
"version": "3.1.4",
|
||||
"minAppVersion": "1.1.0",
|
||||
"description": "Browse note files in a grid view.",
|
||||
"author": "Devon22",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "gridexplorer",
|
||||
"version": "3.1.3",
|
||||
"version": "3.1.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gridexplorer",
|
||||
"version": "3.1.3",
|
||||
"version": "3.1.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@esbuild/linux-x64": "0.17.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gridexplorer",
|
||||
"version": "3.1.3",
|
||||
"version": "3.1.4",
|
||||
"description": "Browse note files in a grid view.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
128
src/GridView.ts
128
src/GridView.ts
|
|
@ -67,6 +67,7 @@ export class GridView extends ItemView {
|
|||
isShowingNote: boolean = false; // 是否正在顯示筆記
|
||||
noteViewContainer: HTMLElement | null = null; // 筆記檢視容器
|
||||
eventCleanupFunctions: (() => void)[] = []; // 存儲事件清理函數
|
||||
targetFocusPath: string | null = null; // 指定下次渲染要對焦的檔案路徑
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: GridExplorerPlugin) {
|
||||
super(leaf);
|
||||
|
|
@ -1063,45 +1064,102 @@ export class GridView extends ItemView {
|
|||
const state: DividerState = { lastDateString: lastDateString, pinDividerAdded: pinDividerAdded, blankDividerAdded: blankDividerAdded };
|
||||
const paramsBase: FileRenderParams = { container, observer, files, dateDividerMode, sortType, shouldShowDateDividers, state };
|
||||
const selfRef = this;
|
||||
|
||||
if (Platform.isIosApp) {
|
||||
// iOS 專用:以 time-slice 方式分批,避免阻塞點擊事件
|
||||
const TIME_BUDGET_MS = 6; // 每幀最多執行 6ms
|
||||
const processChunk = (start: number) => {
|
||||
if (currentToken !== this.renderToken) return;
|
||||
const batchSize = 50;
|
||||
let currentIndex = 0;
|
||||
|
||||
// 建立哨兵元素,用於觸發後續載入
|
||||
const sentinel = container.createDiv('ge-load-more-sentinel');
|
||||
sentinel.style.height = '1px';
|
||||
sentinel.style.width = '100%';
|
||||
sentinel.style.flexShrink = '0';
|
||||
|
||||
let isLoading = false;
|
||||
|
||||
const loadMore = (entries?: IntersectionObserverEntry[]) => {
|
||||
if (currentToken !== selfRef.renderToken) return;
|
||||
|
||||
if (entries && entries[0] && !entries[0].isIntersecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLoading) return;
|
||||
isLoading = true;
|
||||
|
||||
// 暫時移除哨兵
|
||||
sentinel.remove();
|
||||
|
||||
let targetMaxEnd = currentIndex + batchSize;
|
||||
if (selfRef.targetFocusPath) {
|
||||
const tIndex = files.findIndex(f => f.path === selfRef.targetFocusPath);
|
||||
if (tIndex >= currentIndex) {
|
||||
targetMaxEnd = Math.max(targetMaxEnd, tIndex + 1);
|
||||
}
|
||||
}
|
||||
const end = Math.min(targetMaxEnd, files.length);
|
||||
const TIME_BUDGET_MS = Platform.isIosApp ? 6 : 16;
|
||||
|
||||
const renderChunk = () => {
|
||||
if (currentToken !== selfRef.renderToken) return;
|
||||
|
||||
const startTime = performance.now();
|
||||
let i = start;
|
||||
for (; i < files.length; i++) {
|
||||
selfRef.processFile(files[i], paramsBase);
|
||||
if (performance.now() - startTime > TIME_BUDGET_MS) {
|
||||
break; // 超過時間預算,讓出主執行緒
|
||||
}
|
||||
while (currentIndex < end && (performance.now() - startTime) < TIME_BUDGET_MS) {
|
||||
selfRef.processFile(files[currentIndex], paramsBase);
|
||||
currentIndex++;
|
||||
}
|
||||
if (i < files.length) {
|
||||
requestAnimationFrame(() => processChunk(i)); // 下一幀繼續
|
||||
|
||||
if (currentIndex < end) {
|
||||
requestAnimationFrame(renderChunk);
|
||||
} else {
|
||||
// 這一批次處理完畢
|
||||
if (selfRef.targetFocusPath) {
|
||||
// 尋找剛剛載入的目標項目並捲動到該位置
|
||||
const gridItem = Array.from(container.querySelectorAll('.ge-grid-item')).find(
|
||||
item => (item as HTMLElement).dataset.filePath === selfRef.targetFocusPath
|
||||
) as HTMLElement;
|
||||
|
||||
if (gridItem) {
|
||||
gridItem.scrollIntoView({ block: 'nearest' });
|
||||
const itemIndex = selfRef.gridItems.indexOf(gridItem);
|
||||
if (itemIndex >= 0) {
|
||||
selfRef.selectItem(itemIndex);
|
||||
}
|
||||
selfRef.targetFocusPath = null; // 處理完清除目標
|
||||
}
|
||||
}
|
||||
|
||||
if (currentIndex < files.length) {
|
||||
// 還有剩餘檔案,將哨兵加回底部
|
||||
container.appendChild(sentinel);
|
||||
|
||||
// 主動判斷是否仍在可視範圍內(解決捲動跳轉後 IntersectionObserver 未觸發的問題)
|
||||
setTimeout(() => {
|
||||
if (currentToken !== selfRef.renderToken) return;
|
||||
// 由於不一定是在 root container,也可以透過 getBoundingClientRect 簡單測算
|
||||
const rect = sentinel.getBoundingClientRect();
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
if (rect.top - containerRect.bottom < 400) {
|
||||
loadMore();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
isLoading = false;
|
||||
}
|
||||
};
|
||||
processChunk(0);
|
||||
} else {
|
||||
// 其他平台維持原本固定 batchSize 的邏輯
|
||||
const batchSize = 50;
|
||||
const processBatch = (start: number) => {
|
||||
if (currentToken !== this.renderToken) return;
|
||||
const end = Math.min(start + batchSize, files.length);
|
||||
for (let i = start; i < end; i++) {
|
||||
selfRef.processFile(files[i], paramsBase);
|
||||
}
|
||||
if (end < files.length) {
|
||||
const cb = () => processBatch(end);
|
||||
if (typeof (window as any).requestIdleCallback === 'function') {
|
||||
(window as any).requestIdleCallback(cb);
|
||||
} else {
|
||||
setTimeout(cb, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
processBatch(0);
|
||||
}
|
||||
|
||||
renderChunk();
|
||||
};
|
||||
|
||||
const sentinelObserver = new IntersectionObserver(loadMore, {
|
||||
root: container,
|
||||
rootMargin: '400px', // 提早 400px 載入
|
||||
threshold: 0
|
||||
});
|
||||
|
||||
sentinelObserver.observe(sentinel);
|
||||
|
||||
// 初始載入
|
||||
loadMore();
|
||||
}
|
||||
|
||||
if (this.plugin.statusBarItem) {
|
||||
|
|
|
|||
48
src/main.ts
48
src/main.ts
|
|
@ -629,30 +629,8 @@ export default class GridExplorerPlugin extends Plugin {
|
|||
async openNoteInRecentFiles(file: TFile) {
|
||||
const view = await this.activateView();
|
||||
if (view instanceof GridView) {
|
||||
view.targetFocusPath = file instanceof TFile ? file.path : null;
|
||||
await view.setSource('recent-files');
|
||||
// 如果是文件,等待視圖渲染完成後捲動到該文件位置
|
||||
if (file instanceof TFile) {
|
||||
// 等待下一個事件循環以確保視圖已完全渲染
|
||||
requestAnimationFrame(() => {
|
||||
const gridContainer = view.containerEl.querySelector('.ge-grid-container') as HTMLElement;
|
||||
if (!gridContainer) return;
|
||||
|
||||
// 找到對應的網格項目
|
||||
const gridItem = Array.from(gridContainer.querySelectorAll('.ge-grid-item')).find(
|
||||
item => (item as HTMLElement).dataset.filePath === file.path
|
||||
) as HTMLElement;
|
||||
|
||||
if (gridItem) {
|
||||
// 捲動到該項目的位置
|
||||
gridItem.scrollIntoView({ block: 'nearest' });
|
||||
// 選中該項目
|
||||
const itemIndex = view.gridItems.indexOf(gridItem);
|
||||
if (itemIndex >= 0) {
|
||||
view.selectItem(itemIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -662,30 +640,8 @@ export default class GridExplorerPlugin extends Plugin {
|
|||
const folderPath = file ? (file instanceof TFile ? file.parent?.path : file.path) : "/";
|
||||
const view = await this.activateView();
|
||||
if (view instanceof GridView) {
|
||||
view.targetFocusPath = file instanceof TFile ? file.path : null;
|
||||
await view.setSource('folder', folderPath);
|
||||
// 如果是文件,等待視圖渲染完成後捲動到該文件位置
|
||||
if (file instanceof TFile) {
|
||||
// 等待下一個事件循環以確保視圖已完全渲染
|
||||
requestAnimationFrame(() => {
|
||||
const gridContainer = view.containerEl.querySelector('.ge-grid-container') as HTMLElement;
|
||||
if (!gridContainer) return;
|
||||
|
||||
// 找到對應的網格項目
|
||||
const gridItem = Array.from(gridContainer.querySelectorAll('.ge-grid-item')).find(
|
||||
item => (item as HTMLElement).dataset.filePath === file.path
|
||||
) as HTMLElement;
|
||||
|
||||
if (gridItem) {
|
||||
// 捲動到該項目的位置
|
||||
gridItem.scrollIntoView({ block: 'nearest' });
|
||||
// 選中該項目
|
||||
const itemIndex = view.gridItems.indexOf(gridItem);
|
||||
if (itemIndex >= 0) {
|
||||
view.selectItem(itemIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
40
styles.css
40
styles.css
|
|
@ -40,14 +40,20 @@
|
|||
height: var(--grid-item-height);
|
||||
}
|
||||
|
||||
.ge-grid-item:hover {
|
||||
transform: translateY(-2px);
|
||||
background-color: var(--text-selection) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
@media (hover: hover) {
|
||||
.ge-grid-item:hover {
|
||||
transform: translateY(-2px);
|
||||
background-color: var(--text-selection) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.ge-grid-item:hover p {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
.ge-grid-item:hover p {
|
||||
color: var(--text-muted);
|
||||
.ge-grid-item {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* 右上角懸浮開啟筆記按鈕 Hover Open-in-Grid Button */
|
||||
|
|
@ -287,8 +293,10 @@
|
|||
}
|
||||
|
||||
/* 鼠標懸停效果:整張卡片微抬升 */
|
||||
.ge-vertical-card .ge-grid-item:hover {
|
||||
transform: translateY(-2px);
|
||||
@media (hover: hover) {
|
||||
.ge-vertical-card .ge-grid-item:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 僅圖片/影片的直向卡片 Media Card */
|
||||
|
|
@ -391,10 +399,12 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
.ge-grid-item.ge-folder-item:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
@media (hover: hover) {
|
||||
.ge-grid-item.ge-folder-item:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 直向卡片模式下的資料夾項目補正 */
|
||||
|
|
@ -525,8 +535,10 @@
|
|||
background-color: var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.ge-grid-item.ge-note-hidden:hover {
|
||||
opacity: 0.7;
|
||||
@media (hover: hover) {
|
||||
.ge-grid-item.ge-note-hidden:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.ge-grid-item.ge-note-hidden .ge-title {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"3.1.3": "1.1.0"
|
||||
"3.1.4": "1.1.0"
|
||||
}
|
||||
Loading…
Reference in a new issue