mirror of
https://github.com/qf3l3k/obsidian-data-fetcher.git
synced 2026-07-22 05:43:10 +00:00
Merge branch 'feature-1.1.5-hardening'
This commit is contained in:
commit
7442d1d615
8 changed files with 121 additions and 11 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -2,6 +2,16 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [1.1.5] - 2026-05-02
|
||||
|
||||
### Added
|
||||
- Added endpoint list filtering in settings by alias, type, or URL.
|
||||
- Added an export confirmation dialog with an explicit `Include headers` option for endpoint JSON exports.
|
||||
|
||||
### Changed
|
||||
- Endpoint exports now omit headers by default to avoid accidentally sharing API keys or tokens.
|
||||
- Frontmatter output is no longer applied from cached render results; it still writes after fresh fetches and manual refreshes.
|
||||
|
||||
## [1.1.4] - 2026-03-21
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Supported endpoint types:
|
|||
- Endpoint alias configuration in plugin settings
|
||||
- Compact endpoint list with modal editor (v1.1.1)
|
||||
- Endpoint import/export for moving aliases between devices (v1.1.2)
|
||||
- Endpoint filtering in settings and safer header-free exports by default (v1.1.5)
|
||||
- Cache with configurable expiration
|
||||
- Cache browser modal (list, preview, delete individual entries)
|
||||
- Optional ribbon icon shortcut for cache browser
|
||||
|
|
@ -184,6 +185,7 @@ Available options:
|
|||
- Cache duration (minutes)
|
||||
- Endpoint aliases (compact list with Name/Type/URL + actions)
|
||||
- Endpoint import/export (JSON)
|
||||
- Endpoint filter by alias, type, or URL
|
||||
- Per-alias headers
|
||||
- Cache clearing
|
||||
- Cache browser shortcut
|
||||
|
|
@ -212,6 +214,7 @@ Available options:
|
|||
|
||||
Use command `Open cache browser` (or settings button) to:
|
||||
- list cache entries with size/date
|
||||
- filter entries by alias, key, type, or URL
|
||||
- preview cached payloads
|
||||
- delete individual entries
|
||||
- clear all cache
|
||||
|
|
|
|||
103
main.ts
103
main.ts
|
|
@ -21,7 +21,6 @@ export default class DataFetcherPlugin extends Plugin {
|
|||
const cachedResult = await this.cacheManager.getFromCache(query);
|
||||
|
||||
if (cachedResult) {
|
||||
await this.applyOutputTargetSafely(query, cachedResult, ctx);
|
||||
this.renderResult(cachedResult, el, query, ctx);
|
||||
} else {
|
||||
el.createEl('div', { text: 'Fetching data...', cls: 'data-fetcher-loading' });
|
||||
|
|
@ -710,6 +709,7 @@ export default class DataFetcherPlugin extends Plugin {
|
|||
|
||||
class DataFetcherSettingTab extends PluginSettingTab {
|
||||
plugin: DataFetcherPlugin;
|
||||
private endpointFilter = '';
|
||||
|
||||
constructor(app: App, plugin: DataFetcherPlugin) {
|
||||
super(app, plugin);
|
||||
|
|
@ -788,7 +788,9 @@ class DataFetcherSettingTab extends PluginSettingTab {
|
|||
.addButton(button => button
|
||||
.setButtonText('Export endpoints')
|
||||
.onClick(() => {
|
||||
this.exportEndpoints();
|
||||
new EndpointExportModal(this.app, this.plugin.settings.endpoints, includeHeaders => {
|
||||
this.exportEndpoints(includeHeaders);
|
||||
}).open();
|
||||
}))
|
||||
.addButton(button => button
|
||||
.setButtonText('Import endpoints')
|
||||
|
|
@ -801,6 +803,19 @@ class DataFetcherSettingTab extends PluginSettingTab {
|
|||
.setName('Endpoint aliases')
|
||||
.setHeading();
|
||||
|
||||
const filterEl = containerEl.createEl('input', {
|
||||
cls: 'data-fetcher-endpoint-filter',
|
||||
attr: {
|
||||
type: 'text',
|
||||
placeholder: 'Filter endpoints by alias, type, or URL...'
|
||||
}
|
||||
});
|
||||
filterEl.value = this.endpointFilter;
|
||||
filterEl.addEventListener('input', () => {
|
||||
this.endpointFilter = filterEl.value;
|
||||
this.renderEndpointTable(containerEl);
|
||||
});
|
||||
|
||||
this.renderEndpointTable(containerEl);
|
||||
|
||||
new Setting(containerEl)
|
||||
|
|
@ -856,6 +871,7 @@ class DataFetcherSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
private renderEndpointTable(containerEl: HTMLElement): void {
|
||||
containerEl.querySelector('.data-fetcher-endpoint-table')?.remove();
|
||||
const table = containerEl.createEl('div', { cls: 'data-fetcher-endpoint-table' });
|
||||
const header = table.createEl('div', { cls: 'data-fetcher-endpoint-row data-fetcher-endpoint-row-header' });
|
||||
header.createEl('div', { text: 'Name', cls: 'data-fetcher-endpoint-col data-fetcher-endpoint-col-alias' });
|
||||
|
|
@ -870,7 +886,28 @@ class DataFetcherSettingTab extends PluginSettingTab {
|
|||
return;
|
||||
}
|
||||
|
||||
this.plugin.settings.endpoints.forEach((endpoint, index) => {
|
||||
const normalizedFilter = this.endpointFilter.trim().toLowerCase();
|
||||
const visibleEndpoints = this.plugin.settings.endpoints
|
||||
.map((endpoint, index) => ({ endpoint, index }))
|
||||
.filter(({ endpoint }) => {
|
||||
if (!normalizedFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [
|
||||
endpoint.alias || '',
|
||||
endpoint.type || '',
|
||||
endpoint.url || ''
|
||||
].join(' ').toLowerCase().includes(normalizedFilter);
|
||||
});
|
||||
|
||||
if (visibleEndpoints.length === 0) {
|
||||
const empty = table.createEl('div', { cls: 'data-fetcher-endpoint-empty' });
|
||||
empty.setText('No endpoints match current filter.');
|
||||
return;
|
||||
}
|
||||
|
||||
visibleEndpoints.forEach(({ endpoint, index }) => {
|
||||
const row = table.createEl('div', { cls: 'data-fetcher-endpoint-row' });
|
||||
row.createEl('div', {
|
||||
text: endpoint.alias?.trim() || '(unnamed)',
|
||||
|
|
@ -928,11 +965,19 @@ class DataFetcherSettingTab extends PluginSettingTab {
|
|||
});
|
||||
}
|
||||
|
||||
private exportEndpoints(): void {
|
||||
private endpointForExport(endpoint: EndpointConfig, includeHeaders: boolean): EndpointConfig {
|
||||
return {
|
||||
...endpoint,
|
||||
headers: includeHeaders ? { ...(endpoint.headers || {}) } : {}
|
||||
};
|
||||
}
|
||||
|
||||
private exportEndpoints(includeHeaders: boolean): void {
|
||||
const payload = {
|
||||
version: 1,
|
||||
exportedAt: new Date().toISOString(),
|
||||
endpoints: this.plugin.settings.endpoints
|
||||
includesHeaders: includeHeaders,
|
||||
endpoints: this.plugin.settings.endpoints.map(endpoint => this.endpointForExport(endpoint, includeHeaders))
|
||||
};
|
||||
const json = JSON.stringify(payload, null, 2);
|
||||
const blob = new Blob([json], { type: 'application/json' });
|
||||
|
|
@ -945,7 +990,8 @@ class DataFetcherSettingTab extends PluginSettingTab {
|
|||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
URL.revokeObjectURL(url);
|
||||
new Notice(`Exported ${this.plugin.settings.endpoints.length} endpoint(s)`);
|
||||
const headerMode = includeHeaders ? 'with headers' : 'without headers';
|
||||
new Notice(`Exported ${this.plugin.settings.endpoints.length} endpoint(s) ${headerMode}`);
|
||||
}
|
||||
|
||||
private readFileAsText(file: File): Promise<string> {
|
||||
|
|
@ -1169,6 +1215,51 @@ class EndpointImportModeModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
class EndpointExportModal extends Modal {
|
||||
private endpoints: EndpointConfig[];
|
||||
private onSubmit: (includeHeaders: boolean) => void;
|
||||
private includeHeaders = false;
|
||||
|
||||
constructor(app: App, endpoints: EndpointConfig[], onSubmit: (includeHeaders: boolean) => void) {
|
||||
super(app);
|
||||
this.endpoints = endpoints;
|
||||
this.onSubmit = onSubmit;
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Export endpoints')
|
||||
.setHeading();
|
||||
|
||||
contentEl.createEl('p', {
|
||||
text: `Export ${this.endpoints.length} endpoint(s) to JSON. Headers may contain API keys or tokens.`
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName('Include headers')
|
||||
.setDesc('Disabled by default to avoid exporting secrets such as Authorization headers.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.includeHeaders)
|
||||
.onChange(value => {
|
||||
this.includeHeaders = value;
|
||||
}));
|
||||
|
||||
const actions = contentEl.createEl('div', { cls: 'data-fetcher-endpoint-editor-actions' });
|
||||
actions.createEl('button', { text: 'Cancel' }).addEventListener('click', () => this.close());
|
||||
actions.createEl('button', { text: 'Export', cls: 'mod-cta' }).addEventListener('click', () => {
|
||||
this.onSubmit(this.includeHeaders);
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class EndpointEditorModal extends Modal {
|
||||
private endpoint: EndpointConfig;
|
||||
private existingAliases: Set<string>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "data-fetcher",
|
||||
"name": "Data Fetcher",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"minAppVersion": "1.8.3",
|
||||
"description": "Fetch data from multiple sources (REST APIs, RPC, gRPC, GraphQL) and insert results into notes",
|
||||
"author": "qf3l3k",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-api-fetcher",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-api-fetcher",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-api-fetcher",
|
||||
"version": "1.1.4",
|
||||
"version": "1.1.5",
|
||||
"description": "Fetch data from multiple sources (REST APIs, RPC, gRPC, GraphQL) and insert results into notes",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,11 @@
|
|||
font-size: var(--font-ui-smaller);
|
||||
}
|
||||
|
||||
.data-fetcher-endpoint-filter {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.data-fetcher-endpoint-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(84px, 1fr) 72px minmax(140px, 2fr) 64px minmax(150px, 1.2fr);
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@
|
|||
"1.1.1": "1.8.3",
|
||||
"1.1.2": "1.8.3",
|
||||
"1.1.3": "1.8.3",
|
||||
"1.1.4": "1.8.3"
|
||||
"1.1.4": "1.8.3",
|
||||
"1.1.5": "1.8.3"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue