This commit is contained in:
+5
-1
@@ -202,7 +202,11 @@ function setupIpc() {
|
|||||||
reindex(id);
|
reindex(id);
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
h('library:trash:purge', () => store.purgeTrash());
|
h('library:trash:purge', ({ names } = {}) => {
|
||||||
|
if (names && names.length) store.purgeTrashItems(names);
|
||||||
|
else store.purgeTrash();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
h('folders:create', ({ name, parentId }) => store.createFolder(name, parentId || null));
|
h('folders:create', ({ name, parentId }) => store.createFolder(name, parentId || null));
|
||||||
h('folders:rename', ({ folderId, name }) => store.renameFolder(folderId, name));
|
h('folders:rename', ({ folderId, name }) => store.renameFolder(folderId, name));
|
||||||
h('folders:delete', ({ folderId }) => store.deleteFolder(folderId));
|
h('folders:delete', ({ folderId }) => store.deleteFolder(folderId));
|
||||||
|
|||||||
+72
-7
@@ -23,6 +23,7 @@ class StepForgeApp {
|
|||||||
info: null,
|
info: null,
|
||||||
selectMode: false,
|
selectMode: false,
|
||||||
selectedGuides: new Set(),
|
selectedGuides: new Set(),
|
||||||
|
selectedTrash: new Set(),
|
||||||
};
|
};
|
||||||
this.editorMeta = null;
|
this.editorMeta = null;
|
||||||
this.libraryRenderToken = 0;
|
this.libraryRenderToken = 0;
|
||||||
@@ -351,12 +352,13 @@ class StepForgeApp {
|
|||||||
clearNode(this.libraryHost);
|
clearNode(this.libraryHost);
|
||||||
const q = this.state.query.trim();
|
const q = this.state.query.trim();
|
||||||
const folderLabel = this.filterLabel();
|
const folderLabel = this.filterLabel();
|
||||||
// Selecting guides only makes sense for the plain guide grid — drop out
|
// Selecting only makes sense for the guide grid and the trash — drop out
|
||||||
// of select mode for search results and the trash.
|
// of select mode for search results.
|
||||||
const canSelect = !q && this.state.folderFilter !== 'trash';
|
const canSelect = !q;
|
||||||
if (!canSelect && this.state.selectMode) {
|
if (!canSelect && this.state.selectMode) {
|
||||||
this.state.selectMode = false;
|
this.state.selectMode = false;
|
||||||
this.state.selectedGuides = new Set();
|
this.state.selectedGuides = new Set();
|
||||||
|
this.state.selectedTrash = new Set();
|
||||||
}
|
}
|
||||||
const body = el('div.library', {},
|
const body = el('div.library', {},
|
||||||
el('aside.lib-side', {},
|
el('aside.lib-side', {},
|
||||||
@@ -406,10 +408,18 @@ class StepForgeApp {
|
|||||||
this.renderTopbar();
|
this.renderTopbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFolderFilter(folderFilter) {
|
||||||
|
this.state.folderFilter = folderFilter;
|
||||||
|
this.state.selectMode = false;
|
||||||
|
this.state.selectedGuides = new Set();
|
||||||
|
this.state.selectedTrash = new Set();
|
||||||
|
this.renderLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
libraryNavItem(id, label, count) {
|
libraryNavItem(id, label, count) {
|
||||||
const props = {
|
const props = {
|
||||||
className: `nav-item${this.state.folderFilter === id ? ' active' : ''}`,
|
className: `nav-item${this.state.folderFilter === id ? ' active' : ''}`,
|
||||||
onClick: () => { this.state.folderFilter = id; this.renderLibrary(); },
|
onClick: () => this.setFolderFilter(id),
|
||||||
};
|
};
|
||||||
if (!['all', 'favorites', 'trash'].includes(id)) {
|
if (!['all', 'favorites', 'trash'].includes(id)) {
|
||||||
props.onContextMenu = (e) => this.folderContextMenu(e, id);
|
props.onContextMenu = (e) => this.folderContextMenu(e, id);
|
||||||
@@ -430,7 +440,7 @@ class StepForgeApp {
|
|||||||
out.push(el('div.nav-item', {
|
out.push(el('div.nav-item', {
|
||||||
className: `nav-item${this.state.folderFilter === folder.id ? ' active' : ''}`,
|
className: `nav-item${this.state.folderFilter === folder.id ? ' active' : ''}`,
|
||||||
style: { paddingLeft: `${8 + depth * 12}px` },
|
style: { paddingLeft: `${8 + depth * 12}px` },
|
||||||
onClick: () => { this.state.folderFilter = folder.id; this.renderLibrary(); },
|
onClick: () => this.setFolderFilter(folder.id),
|
||||||
onContextMenu: (e) => this.folderContextMenu(e, folder.id),
|
onContextMenu: (e) => this.folderContextMenu(e, folder.id),
|
||||||
},
|
},
|
||||||
el('span', {}, folder.name),
|
el('span', {}, folder.name),
|
||||||
@@ -514,9 +524,17 @@ class StepForgeApp {
|
|||||||
this.domLibraryResults.append(el('div.empty-state', {}, el('div.big', {}, 'Trash'), 'Nothing deleted yet.'));
|
this.domLibraryResults.append(el('div.empty-state', {}, el('div.big', {}, 'Trash'), 'Nothing deleted yet.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const items = this.state.trash.map((name) => el('div.guide-card', {
|
const selectMode = this.state.selectMode;
|
||||||
|
const items = this.state.trash.map((name) => {
|
||||||
|
const selected = this.state.selectedTrash.has(name);
|
||||||
|
return el('div.guide-card', {
|
||||||
|
className: `guide-card${selected ? ' selected' : ''}`,
|
||||||
|
onClick: () => {
|
||||||
|
if (selectMode) this.toggleTrashSelection(name);
|
||||||
|
},
|
||||||
onContextMenu: (e) => {
|
onContextMenu: (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (selectMode) return;
|
||||||
contextMenu(e.clientX, e.clientY, [
|
contextMenu(e.clientX, e.clientY, [
|
||||||
{ label: 'Restore', action: () => this.restoreTrashItem(name) },
|
{ label: 'Restore', action: () => this.restoreTrashItem(name) },
|
||||||
{ label: 'Empty trash', danger: true, action: () => this.purgeTrashItem() },
|
{ label: 'Empty trash', danger: true, action: () => this.purgeTrashItem() },
|
||||||
@@ -524,7 +542,8 @@ class StepForgeApp {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
el('h4', {}, name),
|
el('h4', {}, name),
|
||||||
el('div.meta', {}, 'Deleted guide archive')));
|
el('div.meta', {}, 'Deleted guide archive'));
|
||||||
|
});
|
||||||
this.domLibraryResults.append(el('div.guide-grid', {}, ...items));
|
this.domLibraryResults.append(el('div.guide-grid', {}, ...items));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -604,6 +623,7 @@ class StepForgeApp {
|
|||||||
toggleSelectMode() {
|
toggleSelectMode() {
|
||||||
this.state.selectMode = !this.state.selectMode;
|
this.state.selectMode = !this.state.selectMode;
|
||||||
this.state.selectedGuides = new Set();
|
this.state.selectedGuides = new Set();
|
||||||
|
this.state.selectedTrash = new Set();
|
||||||
this.renderLibrary();
|
this.renderLibrary();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,10 +647,55 @@ class StepForgeApp {
|
|||||||
this.renderBulkBar();
|
this.renderBulkBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleTrashSelection(name) {
|
||||||
|
if (this.state.selectedTrash.has(name)) this.state.selectedTrash.delete(name);
|
||||||
|
else this.state.selectedTrash.add(name);
|
||||||
|
this.renderTrashView();
|
||||||
|
this.renderBulkBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAllTrash() {
|
||||||
|
this.state.selectedTrash = new Set(this.state.trash);
|
||||||
|
this.renderTrashView();
|
||||||
|
this.renderBulkBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTrashSelection() {
|
||||||
|
this.state.selectedTrash = new Set();
|
||||||
|
this.renderTrashView();
|
||||||
|
this.renderBulkBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkPurgeTrash() {
|
||||||
|
const names = [...this.state.selectedTrash];
|
||||||
|
if (!names.length) return;
|
||||||
|
const ok = await confirmDialog(`Permanently delete ${names.length} item${names.length === 1 ? '' : 's'}? This cannot be undone.`, { danger: true, okLabel: 'Delete forever' });
|
||||||
|
if (!ok) return;
|
||||||
|
await api.library.trashPurge({ names });
|
||||||
|
this.state.selectedTrash = new Set();
|
||||||
|
await this.refreshLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
renderBulkBar() {
|
renderBulkBar() {
|
||||||
if (!this.domBulkBar) return;
|
if (!this.domBulkBar) return;
|
||||||
clearNode(this.domBulkBar);
|
clearNode(this.domBulkBar);
|
||||||
if (!this.state.selectMode) return;
|
if (!this.state.selectMode) return;
|
||||||
|
if (this.state.folderFilter === 'trash') {
|
||||||
|
const n = this.state.selectedTrash.size;
|
||||||
|
const allSelected = this.state.trash.length > 0 && n === this.state.trash.length;
|
||||||
|
this.domBulkBar.append(
|
||||||
|
el('div.bulk-bar', {},
|
||||||
|
el('span', {}, n ? `${n} selected` : 'Select items to delete forever'),
|
||||||
|
el('span.spacer', {}),
|
||||||
|
el('button', {
|
||||||
|
type: 'button',
|
||||||
|
onClick: () => (allSelected ? this.clearTrashSelection() : this.selectAllTrash()),
|
||||||
|
}, allSelected ? 'Clear selection' : 'Select all'),
|
||||||
|
el('button.danger', { type: 'button', disabled: !n, onClick: () => this.bulkPurgeTrash() }, 'Delete forever'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const guides = this.state.library.guides.filter((guide) => this.scopeGuide(guide));
|
const guides = this.state.library.guides.filter((guide) => this.scopeGuide(guide));
|
||||||
const n = this.state.selectedGuides.size;
|
const n = this.state.selectedGuides.size;
|
||||||
const allSelected = guides.length > 0 && n === guides.length;
|
const allSelected = guides.length > 0 && n === guides.length;
|
||||||
|
|||||||
@@ -134,6 +134,12 @@ class GuideStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
purgeTrashItems(names) {
|
||||||
|
for (const name of names) {
|
||||||
|
fs.rmSync(path.join(this.trashDir, path.basename(name)), { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
duplicateGuide(guideId, { title } = {}) {
|
duplicateGuide(guideId, { title } = {}) {
|
||||||
const src = this.getGuide(guideId);
|
const src = this.getGuide(guideId);
|
||||||
const steps = this.listSteps(guideId);
|
const steps = this.listSteps(guideId);
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ Initial release.
|
|||||||
recording" option to resume capturing more steps.
|
recording" option to resume capturing more steps.
|
||||||
- Editor step list: a "Select" toggle enables multi-select (checkboxes)
|
- Editor step list: a "Select" toggle enables multi-select (checkboxes)
|
||||||
with a "Select all" / "Delete" bar for removing several steps at once.
|
with a "Select all" / "Delete" bar for removing several steps at once.
|
||||||
|
- The library's "Select" toggle and bulk action bar now also work in the
|
||||||
|
Trash, with a "Delete forever" action that permanently removes the
|
||||||
|
selected items.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user