mirror of
https://github.com/gapmiss/substack-clipper.git
synced 2026-07-22 07:48:24 +00:00
feat: improve history view with search icon/clear button, Notice toasts, and disabled state during re-download
This commit is contained in:
parent
1c6cfefbbf
commit
0129a2aca9
2 changed files with 141 additions and 32 deletions
|
|
@ -9,7 +9,7 @@ export class HistoryView extends ItemView {
|
|||
private plugin: SubstackClipperPlugin;
|
||||
private filterText = '';
|
||||
private listEl: HTMLElement;
|
||||
private statusEl: HTMLElement;
|
||||
private redownloadBtn: HTMLButtonElement;
|
||||
private selectedEntries: Set<number> = new Set();
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: SubstackClipperPlugin) {
|
||||
|
|
@ -33,31 +33,49 @@ export class HistoryView extends ItemView {
|
|||
const { contentEl } = this;
|
||||
contentEl.addClass('substack-clipper-history-view');
|
||||
|
||||
const searchInput = contentEl.createEl('input', {
|
||||
const searchContainer = contentEl.createDiv({ cls: 'substack-clipper-history-search-container' });
|
||||
const searchIconEl = searchContainer.createSpan({ cls: 'substack-clipper-history-search-icon' });
|
||||
setIcon(searchIconEl, 'search');
|
||||
const searchInput = searchContainer.createEl('input', {
|
||||
type: 'text',
|
||||
placeholder: 'Filter by title or author...',
|
||||
cls: 'substack-clipper-history-search',
|
||||
attr: {
|
||||
placeholder: 'Filter by title or author...',
|
||||
'aria-label': 'Filter history entries',
|
||||
},
|
||||
});
|
||||
const clearBtn = searchContainer.createEl('button', {
|
||||
cls: 'substack-clipper-history-search-clear',
|
||||
attr: {
|
||||
'aria-label': 'Clear filter',
|
||||
'data-tooltip-position': 'top',
|
||||
},
|
||||
});
|
||||
setIcon(clearBtn, 'x');
|
||||
clearBtn.addEventListener('click', () => {
|
||||
searchInput.value = '';
|
||||
this.filterText = '';
|
||||
clearBtn.removeClass('is-visible');
|
||||
this.renderList();
|
||||
searchInput.focus();
|
||||
});
|
||||
searchInput.setAttribute('aria-label', 'Filter history entries');
|
||||
searchInput.addEventListener('input', () => {
|
||||
this.filterText = searchInput.value.toLowerCase();
|
||||
clearBtn.toggleClass('is-visible', searchInput.value.length > 0);
|
||||
this.renderList();
|
||||
});
|
||||
|
||||
this.listEl = contentEl.createDiv({ cls: 'substack-clipper-history-list' });
|
||||
|
||||
this.statusEl = contentEl.createDiv({ cls: 'substack-clipper-history-status' });
|
||||
this.statusEl.setAttribute('role', 'status');
|
||||
this.statusEl.setAttribute('aria-live', 'polite');
|
||||
|
||||
const footer = contentEl.createDiv({ cls: 'substack-clipper-history-footer' });
|
||||
const btn = footer.createEl('button', {
|
||||
this.redownloadBtn = footer.createEl('button', {
|
||||
text: 'Re-download comments',
|
||||
cls: 'mod-cta',
|
||||
attr: { 'aria-label': 'Re-download comments for selected entries' },
|
||||
});
|
||||
btn.setAttribute('aria-label', 'Re-download comments for selected entries');
|
||||
btn.addEventListener('click', () => {
|
||||
void this.redownloadComments(btn);
|
||||
this.redownloadBtn.disabled = true;
|
||||
this.redownloadBtn.addEventListener('click', () => {
|
||||
void this.redownloadComments();
|
||||
});
|
||||
|
||||
this.renderList();
|
||||
|
|
@ -80,9 +98,14 @@ export class HistoryView extends ItemView {
|
|||
);
|
||||
}
|
||||
|
||||
private syncRedownloadBtn(): void {
|
||||
this.redownloadBtn.disabled = this.selectedEntries.size === 0;
|
||||
}
|
||||
|
||||
private renderList(): void {
|
||||
this.listEl.empty();
|
||||
this.selectedEntries.clear();
|
||||
this.syncRedownloadBtn();
|
||||
|
||||
const entries = this.getFilteredEntries();
|
||||
|
||||
|
|
@ -124,6 +147,7 @@ export class HistoryView extends ItemView {
|
|||
const checked = rowCheckboxes.filter(r => r.checkbox.checked).length;
|
||||
selectAllCb.checked = checked === total;
|
||||
selectAllCb.indeterminate = checked > 0 && checked < total;
|
||||
this.syncRedownloadBtn();
|
||||
});
|
||||
|
||||
const info = row.createDiv({ cls: 'substack-clipper-history-info' });
|
||||
|
|
@ -191,17 +215,27 @@ export class HistoryView extends ItemView {
|
|||
this.selectedEntries.delete(idx);
|
||||
}
|
||||
}
|
||||
this.syncRedownloadBtn();
|
||||
});
|
||||
}
|
||||
|
||||
private async redownloadComments(btn: HTMLButtonElement): Promise<void> {
|
||||
private setViewDisabled(disabled: boolean): void {
|
||||
this.contentEl.toggleClass('is-disabled', disabled);
|
||||
const inputs = Array.from(this.contentEl.querySelectorAll<HTMLInputElement | HTMLButtonElement>('input, button'));
|
||||
for (const el of inputs) {
|
||||
el.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
private async redownloadComments(): Promise<void> {
|
||||
if (this.selectedEntries.size === 0) {
|
||||
new Notice('No entries selected.');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
this.statusEl.setText('Downloading...');
|
||||
this.setViewDisabled(true);
|
||||
const notice = new Notice('Re-downloading comments...', 0);
|
||||
notice.containerEl.addClass('is-loading');
|
||||
|
||||
const history = this.plugin.settings.history;
|
||||
const indices = [...this.selectedEntries];
|
||||
|
|
@ -233,18 +267,15 @@ export class HistoryView extends ItemView {
|
|||
}
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
notice.hide();
|
||||
this.setViewDisabled(false);
|
||||
this.renderList();
|
||||
btn.disabled = false;
|
||||
|
||||
if (failed === 0) {
|
||||
const msg = `Re-downloaded comments for ${String(succeeded)} posts`;
|
||||
this.statusEl.setText(msg);
|
||||
new Notice(msg);
|
||||
new Notice(`Re-downloaded comments for ${String(succeeded)} posts`);
|
||||
} else {
|
||||
const total = succeeded + failed;
|
||||
const msg = `Re-downloaded ${String(succeeded)} of ${String(total)} (${String(failed)} failed)`;
|
||||
this.statusEl.setText(msg);
|
||||
new Notice(msg);
|
||||
new Notice(`Re-downloaded ${String(succeeded)} of ${String(total)} (${String(failed)} failed)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
98
styles.css
98
styles.css
|
|
@ -19,16 +19,96 @@
|
|||
gap: var(--size-4-2);
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-4-1);
|
||||
min-height: 32px;
|
||||
padding: 0 var(--size-4-2);
|
||||
background: var(--background-secondary);
|
||||
border: var(--input-border-width) solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-container:focus-within {
|
||||
box-shadow: inset 0 0 0 1px var(--background-modifier-border-focus);
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-faint);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.substack-clipper-history-search {
|
||||
width: 100%;
|
||||
padding: var(--size-4-2);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
background: var(--background-primary);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: var(--size-4-1) 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-medium);
|
||||
}
|
||||
|
||||
.substack-clipper-history-search:hover,
|
||||
.substack-clipper-history-search:focus,
|
||||
.substack-clipper-history-search:focus-visible {
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.substack-clipper-history-view .substack-clipper-history-search-container input[type="text"] {
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.substack-clipper-history-search::placeholder {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-clear {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--text-faint);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-clear:hover {
|
||||
color: var(--text-muted);
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-clear.is-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.substack-clipper-history-search-clear svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.substack-clipper-history-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
|
@ -128,11 +208,9 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.substack-clipper-history-status {
|
||||
padding: var(--size-4-2) 0;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-ui-small);
|
||||
min-height: var(--size-4-4);
|
||||
.substack-clipper-history-view.is-disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.substack-clipper-history-footer {
|
||||
|
|
|
|||
Loading…
Reference in a new issue