mirror of
https://github.com/zadam/trilium.git
synced 2025-11-10 13:50:42 +08:00
Merge 1f050255a8 into 4d4cd7d130
This commit is contained in:
commit
1a285af8a1
2 changed files with 192 additions and 9 deletions
|
|
@ -27,6 +27,34 @@ interface TableConfig {
|
||||||
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
|
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
|
||||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
|
const expandedRowsRef = useRef<Set<string>>(new Set());
|
||||||
|
const isDataRefreshingRef = useRef<boolean>(false);
|
||||||
|
|
||||||
|
// Load persisted expansion state on mount
|
||||||
|
useEffect(() => {
|
||||||
|
const storageKey = `trilium-tree-expanded-${note.noteId}`;
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(storageKey);
|
||||||
|
if (stored) {
|
||||||
|
const expandedIds = JSON.parse(stored);
|
||||||
|
expandedRowsRef.current = new Set(expandedIds);
|
||||||
|
console.log('Loaded expansion state from storage:', expandedIds);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to load tree expansion state:', e);
|
||||||
|
}
|
||||||
|
}, [note.noteId]);
|
||||||
|
|
||||||
|
// Save expansion state changes to localStorage
|
||||||
|
const persistExpandedState = useCallback(() => {
|
||||||
|
const storageKey = `trilium-tree-expanded-${note.noteId}`;
|
||||||
|
try {
|
||||||
|
const expandedIds = Array.from(expandedRowsRef.current);
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(expandedIds));
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to save tree expansion state:', e);
|
||||||
|
}
|
||||||
|
}, [note.noteId]);
|
||||||
|
|
||||||
const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget().contentSized());
|
const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget().contentSized());
|
||||||
const contextMenuEvents = useContextMenu(note, parentComponent, tabulatorRef);
|
const contextMenuEvents = useContextMenu(note, parentComponent, tabulatorRef);
|
||||||
|
|
@ -43,7 +71,11 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||||
dataTreeElementColumn: "title",
|
dataTreeElementColumn: "title",
|
||||||
dataTreeChildIndent: 20,
|
dataTreeChildIndent: 20,
|
||||||
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
|
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
|
||||||
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`
|
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`,
|
||||||
|
persistenceMode: "local",
|
||||||
|
persistence: {
|
||||||
|
tree: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [ hasChildren ]);
|
}, [ hasChildren ]);
|
||||||
|
|
||||||
|
|
@ -65,7 +97,74 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||||
footerElement={<TableFooter note={note} />}
|
footerElement={<TableFooter note={note} />}
|
||||||
events={{
|
events={{
|
||||||
...contextMenuEvents,
|
...contextMenuEvents,
|
||||||
...rowEditingEvents
|
...rowEditingEvents,
|
||||||
|
tableBuilt: () => {
|
||||||
|
console.log('Table built - setting up tree event tracking');
|
||||||
|
},
|
||||||
|
// Try all possible expand event names
|
||||||
|
rowTreeExpanded: (row) => {
|
||||||
|
const data = row.getData() as TableData;
|
||||||
|
console.log('Row expanded (rowTreeExpanded):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
|
||||||
|
if (data.branchId && !isDataRefreshingRef.current) {
|
||||||
|
expandedRowsRef.current.add(data.branchId);
|
||||||
|
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
|
||||||
|
persistExpandedState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataTreeRowExpanded: (row) => {
|
||||||
|
const data = row.getData() as TableData;
|
||||||
|
console.log('Row expanded (dataTreeRowExpanded):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
|
||||||
|
if (data.branchId && !isDataRefreshingRef.current) {
|
||||||
|
expandedRowsRef.current.add(data.branchId);
|
||||||
|
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
|
||||||
|
persistExpandedState();
|
||||||
|
}
|
||||||
|
// Call the original context menu handler if it exists
|
||||||
|
if (contextMenuEvents.dataTreeRowExpanded) {
|
||||||
|
contextMenuEvents.dataTreeRowExpanded(row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
treeExpanded: (row) => {
|
||||||
|
const data = row.getData() as TableData;
|
||||||
|
console.log('Row expanded (treeExpanded):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
|
||||||
|
if (data.branchId && !isDataRefreshingRef.current) {
|
||||||
|
expandedRowsRef.current.add(data.branchId);
|
||||||
|
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
|
||||||
|
persistExpandedState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Try all possible collapse event names
|
||||||
|
rowTreeCollapsed: (row) => {
|
||||||
|
const data = row.getData() as TableData;
|
||||||
|
console.log('Row collapsed (rowTreeCollapsed):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
|
||||||
|
if (data.branchId && !isDataRefreshingRef.current) {
|
||||||
|
expandedRowsRef.current.delete(data.branchId);
|
||||||
|
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
|
||||||
|
persistExpandedState();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataTreeRowCollapsed: (row) => {
|
||||||
|
const data = row.getData() as TableData;
|
||||||
|
console.log('Row collapsed (dataTreeRowCollapsed):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
|
||||||
|
if (data.branchId && !isDataRefreshingRef.current) {
|
||||||
|
expandedRowsRef.current.delete(data.branchId);
|
||||||
|
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
|
||||||
|
persistExpandedState();
|
||||||
|
}
|
||||||
|
// Call the original context menu handler if it exists
|
||||||
|
if (contextMenuEvents.dataTreeRowCollapsed) {
|
||||||
|
contextMenuEvents.dataTreeRowCollapsed(row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
treeCollapsed: (row) => {
|
||||||
|
const data = row.getData() as TableData;
|
||||||
|
console.log('Row collapsed (treeCollapsed):', data.branchId, data.title, 'refreshing:', isDataRefreshingRef.current);
|
||||||
|
if (data.branchId && !isDataRefreshingRef.current) {
|
||||||
|
expandedRowsRef.current.delete(data.branchId);
|
||||||
|
console.log('Updated expanded set:', Array.from(expandedRowsRef.current));
|
||||||
|
persistExpandedState();
|
||||||
|
}
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
persistence {...persistenceProps}
|
persistence {...persistenceProps}
|
||||||
layout="fitDataFill"
|
layout="fitDataFill"
|
||||||
|
|
@ -73,7 +172,19 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
|
||||||
movableColumns
|
movableColumns
|
||||||
movableRows={movableRows}
|
movableRows={movableRows}
|
||||||
rowFormatter={rowFormatter}
|
rowFormatter={rowFormatter}
|
||||||
|
preserveTreeState={hasChildren}
|
||||||
|
expandedRowsRef={expandedRowsRef}
|
||||||
|
isDataRefreshingRef={isDataRefreshingRef}
|
||||||
{...dataTreeProps}
|
{...dataTreeProps}
|
||||||
|
dataTreeStartExpanded={(row, level) => {
|
||||||
|
if (expandedRowsRef.current && expandedRowsRef.current.size > 0) {
|
||||||
|
const rowData = row.getData() as TableData;
|
||||||
|
const isExpanded = expandedRowsRef.current.has(rowData.branchId);
|
||||||
|
console.log(`dataTreeStartExpanded called for ${rowData.branchId}: ${isExpanded}`);
|
||||||
|
return isExpanded;
|
||||||
|
}
|
||||||
|
return false; // Default collapsed state
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<TableFooter note={note} />
|
<TableFooter note={note} />
|
||||||
</>
|
</>
|
||||||
|
|
@ -133,6 +244,9 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
|
||||||
const [ movableRows, setMovableRows ] = useState(false);
|
const [ movableRows, setMovableRows ] = useState(false);
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
|
console.log('🔄 TABLE REFRESH TRIGGERED');
|
||||||
|
console.trace('Refresh call stack'); // This will show us what triggered it
|
||||||
|
|
||||||
const info = getAttributeDefinitionInformation(note);
|
const info = getAttributeDefinitionInformation(note);
|
||||||
|
|
||||||
buildRowDefinitions(note, info, includeArchived, maxDepth).then(({ definitions: rowData, hasSubtree: hasChildren, rowNumber }) => {
|
buildRowDefinitions(note, info, includeArchived, maxDepth).then(({ definitions: rowData, hasSubtree: hasChildren, rowNumber }) => {
|
||||||
|
|
@ -150,15 +264,43 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(refresh, [ note, noteIds, maxDepth, movableRows ]);
|
useEffect(() => {
|
||||||
|
console.log('⚡ useEffect refresh triggered by:', { note: note.noteId, noteIds: noteIds.length, maxDepth, movableRows });
|
||||||
|
// Debounce rapid changes to movableRows
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
refresh();
|
||||||
|
}, 50);
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [ note, noteIds.length, maxDepth ]); // Remove movableRows from dependencies
|
||||||
|
|
||||||
|
const refreshTimeoutRef = useRef<number>();
|
||||||
|
|
||||||
|
// Cleanup timeout on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (refreshTimeoutRef.current) {
|
||||||
|
clearTimeout(refreshTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
|
useTriliumEvent("entitiesReloaded", ({ loadResults}) => {
|
||||||
|
console.log('🔄 entitiesReloaded event triggered');
|
||||||
|
console.log('Attributes changed:', loadResults.getAttributeRows().length);
|
||||||
|
console.log('Branches changed:', loadResults.getBranchRows().length);
|
||||||
|
console.log('Notes changed:', loadResults.getNoteIds().length);
|
||||||
|
|
||||||
// React to column changes.
|
// React to column changes.
|
||||||
if (loadResults.getAttributeRows().find(attr =>
|
if (loadResults.getAttributeRows().find(attr =>
|
||||||
attr.type === "label" &&
|
attr.type === "label" &&
|
||||||
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
(attr.name?.startsWith("label:") || attr.name?.startsWith("relation:")) &&
|
||||||
attributes.isAffecting(attr, note))) {
|
attributes.isAffecting(attr, note))) {
|
||||||
refresh();
|
console.log('✅ Refreshing due to column changes');
|
||||||
|
// Clear any pending refresh
|
||||||
|
if (refreshTimeoutRef.current) {
|
||||||
|
clearTimeout(refreshTimeoutRef.current);
|
||||||
|
}
|
||||||
|
refreshTimeoutRef.current = setTimeout(() => refresh(), 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,9 +309,16 @@ function useData(note: FNote, noteIds: string[], viewConfig: TableConfig | undef
|
||||||
|| loadResults.getNoteIds().some(noteId => noteIds.includes(noteId))
|
|| loadResults.getNoteIds().some(noteId => noteIds.includes(noteId))
|
||||||
|| loadResults.getAttributeRows().some(attr => noteIds.includes(attr.noteId!))
|
|| loadResults.getAttributeRows().some(attr => noteIds.includes(attr.noteId!))
|
||||||
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))) {
|
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))) {
|
||||||
refresh();
|
console.log('✅ Refreshing due to row updates');
|
||||||
|
// Clear any pending refresh and debounce
|
||||||
|
if (refreshTimeoutRef.current) {
|
||||||
|
clearTimeout(refreshTimeoutRef.current);
|
||||||
|
}
|
||||||
|
refreshTimeoutRef.current = setTimeout(() => refresh(), 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('❌ No refresh needed for this entitiesReloaded event');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Identify if movable rows.
|
// Identify if movable rows.
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,12 @@ interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"
|
||||||
events?: Partial<EventCallBackMethods>;
|
events?: Partial<EventCallBackMethods>;
|
||||||
index: keyof T;
|
index: keyof T;
|
||||||
footerElement?: string | HTMLElement | JSX.Element;
|
footerElement?: string | HTMLElement | JSX.Element;
|
||||||
|
preserveTreeState?: boolean;
|
||||||
|
expandedRowsRef?: RefObject<Set<string>>;
|
||||||
|
isDataRefreshingRef?: RefObject<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, ...restProps }: TableProps<T>) {
|
export default function Tabulator<T>({ className, columns, data, modules, tabulatorRef: externalTabulatorRef, footerElement, events, index, preserveTreeState, expandedRowsRef, isDataRefreshingRef, ...restProps }: TableProps<T>) {
|
||||||
const parentComponent = useContext(ParentComponent);
|
const parentComponent = useContext(ParentComponent);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const tabulatorRef = useRef<VanillaTabulator>(null);
|
const tabulatorRef = useRef<VanillaTabulator>(null);
|
||||||
|
|
@ -62,8 +65,39 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
|
||||||
}
|
}
|
||||||
}, Object.values(events ?? {}));
|
}, Object.values(events ?? {}));
|
||||||
|
|
||||||
// Change in data.
|
const treeStateTimeoutRef = useRef<number>();
|
||||||
useEffect(() => { tabulatorRef.current?.setData(data) }, [ data ]);
|
|
||||||
|
// Cleanup timeout on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (treeStateTimeoutRef.current) {
|
||||||
|
clearTimeout(treeStateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Change in data - with tree state preservation
|
||||||
|
useEffect(() => {
|
||||||
|
const tabulator = tabulatorRef.current;
|
||||||
|
if (!tabulator || !data) return;
|
||||||
|
|
||||||
|
console.log('Data update triggered, preserveTreeState:', preserveTreeState, 'dataTree option:', tabulator.options?.dataTree);
|
||||||
|
|
||||||
|
if (preserveTreeState && tabulator.options && "dataTree" in tabulator.options && tabulator.options.dataTree && expandedRowsRef) {
|
||||||
|
console.log('Tree state preservation using dataTreeStartExpanded approach');
|
||||||
|
|
||||||
|
// Clear any existing timeout to prevent overlapping updates
|
||||||
|
if (treeStateTimeoutRef.current) {
|
||||||
|
clearTimeout(treeStateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simply update data - expansion state will be handled by dataTreeStartExpanded function
|
||||||
|
tabulator.setData(data);
|
||||||
|
} else {
|
||||||
|
tabulator.setData(data);
|
||||||
|
}
|
||||||
|
}, [ data, preserveTreeState, index ]);
|
||||||
|
|
||||||
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
|
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue