feat: refresh files and notes on file events
This commit is contained in:
parent
914c8a7204
commit
7b249fcf48
|
|
@ -335,6 +335,13 @@
|
||||||
return full.indexOf(workspaceRoot + '/') === 0 ? full.slice(workspaceRoot.length + 1) : full;
|
return full.indexOf(workspaceRoot + '/') === 0 ? full.slice(workspaceRoot.length + 1) : full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWorkspaceEvent(event) {
|
||||||
|
var payload = (event && event.payload) || {};
|
||||||
|
var path = cleanPath(payload.path || '');
|
||||||
|
if (!path) return true;
|
||||||
|
return !workspaceRoot || path === workspaceRoot || path.indexOf(workspaceRoot + '/') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
var toolbar = el('div', { className: 'files-toolbar' });
|
var toolbar = el('div', { className: 'files-toolbar' });
|
||||||
var breadcrumb = el('div', { className: 'files-breadcrumb' });
|
var breadcrumb = el('div', { className: 'files-breadcrumb' });
|
||||||
var backBtn = iconButton('back', 'Back', 'back', goBack);
|
var backBtn = iconButton('back', 'Back', 'back', goBack);
|
||||||
|
|
@ -1412,8 +1419,22 @@
|
||||||
loadContributionActions();
|
loadContributionActions();
|
||||||
loadEntries();
|
loadEntries();
|
||||||
|
|
||||||
|
var fileChangedUnsubscribe = null;
|
||||||
|
if (api.events && typeof api.events.subscribe === 'function') {
|
||||||
|
api.events.subscribe('file.changed', function (event) {
|
||||||
|
if (disposed || !isWorkspaceEvent(event)) return;
|
||||||
|
if (showingTrash) loadTrashEntries();
|
||||||
|
else loadEntries();
|
||||||
|
}).then(function (unsubscribe) {
|
||||||
|
fileChangedUnsubscribe = unsubscribe;
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('[files] file.changed subscription:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
containerEl.__filesCleanup = function () {
|
containerEl.__filesCleanup = function () {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
if (typeof fileChangedUnsubscribe === 'function') fileChangedUnsubscribe();
|
||||||
document.removeEventListener('click', onDocClick);
|
document.removeEventListener('click', onDocClick);
|
||||||
document.removeEventListener('keydown', onDocKeydown);
|
document.removeEventListener('keydown', onDocKeydown);
|
||||||
window.removeEventListener('mousedown', handleMouseHistory, true);
|
window.removeEventListener('mousedown', handleMouseHistory, true);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"files.write",
|
"files.write",
|
||||||
"files.delete",
|
"files.delete",
|
||||||
"files.openExternal",
|
"files.openExternal",
|
||||||
|
"events.subscribe",
|
||||||
"workbench.open",
|
"workbench.open",
|
||||||
"ui.register"
|
"ui.register"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,14 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNotesEvent(event) {
|
||||||
|
var payload = (event && event.payload) || {};
|
||||||
|
var changedPath = cleanPath(payload.path || '');
|
||||||
|
if (!changedPath) return true;
|
||||||
|
var parent = notesParent();
|
||||||
|
return changedPath === parent || changedPath.indexOf(parent + '/') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
function actionInitial(action) {
|
function actionInitial(action) {
|
||||||
var label = String(action && action.label || '').trim();
|
var label = String(action && action.label || '').trim();
|
||||||
return label ? label.charAt(0).toUpperCase() : '+';
|
return label ? label.charAt(0).toUpperCase() : '+';
|
||||||
|
|
@ -640,8 +648,21 @@
|
||||||
loadContributionActions();
|
loadContributionActions();
|
||||||
loadNotes();
|
loadNotes();
|
||||||
|
|
||||||
|
var fileChangedUnsubscribe = null;
|
||||||
|
if (api.events && typeof api.events.subscribe === 'function') {
|
||||||
|
api.events.subscribe('file.changed', function (event) {
|
||||||
|
if (disposed || !isNotesEvent(event)) return;
|
||||||
|
loadNotes();
|
||||||
|
}).then(function (unsubscribe) {
|
||||||
|
fileChangedUnsubscribe = unsubscribe;
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.error('[notes] file.changed subscription:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
containerEl.__notesCleanup = function () {
|
containerEl.__notesCleanup = function () {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
if (typeof fileChangedUnsubscribe === 'function') fileChangedUnsubscribe();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"files.read",
|
"files.read",
|
||||||
"files.write",
|
"files.write",
|
||||||
"files.delete",
|
"files.delete",
|
||||||
|
"events.subscribe",
|
||||||
"workbench.open",
|
"workbench.open",
|
||||||
"ui.register"
|
"ui.register"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,9 @@ function loadFilesComponent(document) {
|
||||||
function makeApi() {
|
function makeApi() {
|
||||||
const externalCalls = [];
|
const externalCalls = [];
|
||||||
const contributionCalls = [];
|
const contributionCalls = [];
|
||||||
|
const eventHandlers = {};
|
||||||
let restored = false;
|
let restored = false;
|
||||||
|
let externalVisible = false;
|
||||||
let trashEntries = [{
|
let trashEntries = [{
|
||||||
originalPath: 'Docs/deleted.md',
|
originalPath: 'Docs/deleted.md',
|
||||||
trashPath: '.verstak/trash/files/mock/deleted.md',
|
trashPath: '.verstak/trash/files/mock/deleted.md',
|
||||||
|
|
@ -210,6 +212,13 @@ function makeApi() {
|
||||||
return {
|
return {
|
||||||
externalCalls,
|
externalCalls,
|
||||||
contributionCalls,
|
contributionCalls,
|
||||||
|
emitFileChanged(payload) {
|
||||||
|
(eventHandlers['file.changed'] || []).forEach((handler) => handler({
|
||||||
|
name: 'file.changed',
|
||||||
|
payload,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}));
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
list: async () => {
|
list: async () => {
|
||||||
const entries = [{
|
const entries = [{
|
||||||
|
|
@ -230,6 +239,16 @@ function makeApi() {
|
||||||
modifiedAt: '2026-06-27T01:03:00Z',
|
modifiedAt: '2026-06-27T01:03:00Z',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (externalVisible) {
|
||||||
|
entries.push({
|
||||||
|
name: 'external.md',
|
||||||
|
relativePath: 'Docs/external.md',
|
||||||
|
type: 'file',
|
||||||
|
extension: 'md',
|
||||||
|
size: 9,
|
||||||
|
modifiedAt: '2026-06-27T01:04:00Z',
|
||||||
|
});
|
||||||
|
}
|
||||||
return entries;
|
return entries;
|
||||||
},
|
},
|
||||||
metadata: async () => { throw new Error('not-found'); },
|
metadata: async () => { throw new Error('not-found'); },
|
||||||
|
|
@ -279,6 +298,18 @@ function makeApi() {
|
||||||
return { status: 'handled' };
|
return { status: 'handled' };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
events: {
|
||||||
|
subscribe: async (eventName, handler) => {
|
||||||
|
eventHandlers[eventName] = eventHandlers[eventName] || [];
|
||||||
|
eventHandlers[eventName].push(handler);
|
||||||
|
return () => {
|
||||||
|
eventHandlers[eventName] = (eventHandlers[eventName] || []).filter((candidate) => candidate !== handler);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showExternalFile() {
|
||||||
|
externalVisible = true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,6 +382,13 @@ async function flush() {
|
||||||
if (!restoredRow) {
|
if (!restoredRow) {
|
||||||
throw new Error(`restored file row not rendered after restore: ${container.textContent}`);
|
throw new Error(`restored file row not rendered after restore: ${container.textContent}`);
|
||||||
}
|
}
|
||||||
|
api.showExternalFile();
|
||||||
|
api.emitFileChanged({ path: 'Docs/external.md', operation: 'external.create', type: 'file' });
|
||||||
|
await flush();
|
||||||
|
const externalRow = walk(container, (node) => node.getAttribute && node.getAttribute('data-file-path') === 'Docs/external.md');
|
||||||
|
if (!externalRow) {
|
||||||
|
throw new Error(`external file row not rendered after file.changed: ${container.textContent}`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('files frontend smoke passed');
|
console.log('files frontend smoke passed');
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
|
|
|
||||||
|
|
@ -137,10 +137,18 @@ function makeApi(options = {}) {
|
||||||
const entries = new Map();
|
const entries = new Map();
|
||||||
const opened = [];
|
const opened = [];
|
||||||
const contributionCalls = [];
|
const contributionCalls = [];
|
||||||
|
const eventHandlers = {};
|
||||||
return {
|
return {
|
||||||
entries,
|
entries,
|
||||||
opened,
|
opened,
|
||||||
contributionCalls,
|
contributionCalls,
|
||||||
|
emitFileChanged(payload) {
|
||||||
|
(eventHandlers['file.changed'] || []).forEach((handler) => handler({
|
||||||
|
name: 'file.changed',
|
||||||
|
payload,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}));
|
||||||
|
},
|
||||||
files: {
|
files: {
|
||||||
list: async (relativeDir) => {
|
list: async (relativeDir) => {
|
||||||
const prefix = relativeDir ? `${relativeDir}/` : '';
|
const prefix = relativeDir ? `${relativeDir}/` : '';
|
||||||
|
|
@ -212,6 +220,15 @@ function makeApi(options = {}) {
|
||||||
return { status: 'handled' };
|
return { status: 'handled' };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
events: {
|
||||||
|
subscribe: async (eventName, handler) => {
|
||||||
|
eventHandlers[eventName] = eventHandlers[eventName] || [];
|
||||||
|
eventHandlers[eventName].push(handler);
|
||||||
|
return () => {
|
||||||
|
eventHandlers[eventName] = (eventHandlers[eventName] || []).filter((candidate) => candidate !== handler);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,6 +309,13 @@ async function mountNotes(api) {
|
||||||
sortSelect.value = 'title-asc';
|
sortSelect.value = 'title-asc';
|
||||||
sortSelect.dispatchEvent('change');
|
sortSelect.dispatchEvent('change');
|
||||||
|
|
||||||
|
createApi.entries.set('Project/Notes/Third_Note.md', { type: 'file', content: '# Third Note\n' });
|
||||||
|
createApi.emitFileChanged({ path: 'Project/Notes/Third_Note.md', operation: 'external.create', type: 'file' });
|
||||||
|
await flush();
|
||||||
|
if (!container.textContent.includes('Third Note')) {
|
||||||
|
throw new Error(`notes list did not refresh after file.changed: ${container.textContent}`);
|
||||||
|
}
|
||||||
|
|
||||||
const renameButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-note-action') === 'rename');
|
const renameButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-note-action') === 'rename');
|
||||||
if (!renameButton) throw new Error('rename note button not found');
|
if (!renameButton) throw new Error('rename note button not found');
|
||||||
renameButton.click();
|
renameButton.click();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue