mirror of
https://github.com/qf3l3k/obsidian-data-fetcher.git
synced 2026-07-22 05:43:10 +00:00
release: 1.0.8
This commit is contained in:
parent
4718640d6a
commit
4f9055cd1e
7 changed files with 152 additions and 7 deletions
|
|
@ -4,11 +4,14 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.8] - 2026-03-02
|
||||
|
||||
### Added
|
||||
- Issue #6: added optional `path` selector and `format: table` rendering for array-of-object JSON responses.
|
||||
|
||||
### Changed
|
||||
- Copy and Save actions now use transformed output (selected path / table view).
|
||||
- Improved table readability by truncating long cell content in UI with full value on hover.
|
||||
|
||||
## [1.0.7] - 2026-03-02
|
||||
|
||||
|
|
|
|||
136
main.ts
136
main.ts
|
|
@ -184,6 +184,116 @@ export default class DataFetcherPlugin extends Plugin {
|
|||
return { headers, rows };
|
||||
}
|
||||
|
||||
private tryResolveTableInput(data: any): any {
|
||||
if (Array.isArray(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Common GraphQL envelope: { data: ... }
|
||||
if ('data' in data && data.data && typeof data.data === 'object') {
|
||||
const unwrapped = this.tryResolveTableInput(data.data);
|
||||
if (Array.isArray(unwrapped)) {
|
||||
return unwrapped;
|
||||
}
|
||||
}
|
||||
|
||||
// If object has exactly one property, keep drilling into it.
|
||||
const keys = Object.keys(data);
|
||||
if (keys.length === 1) {
|
||||
const singleValue = data[keys[0]];
|
||||
const drilled = this.tryResolveTableInput(singleValue);
|
||||
if (Array.isArray(drilled)) {
|
||||
return drilled;
|
||||
}
|
||||
}
|
||||
|
||||
// GraphQL connections: { edges: [...] }
|
||||
if (Array.isArray((data as any).edges)) {
|
||||
return (data as any).edges;
|
||||
}
|
||||
|
||||
// Generic object containing any array field.
|
||||
for (const key of keys) {
|
||||
if (Array.isArray((data as any)[key])) {
|
||||
return (data as any)[key];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private findFirstArrayOfObjects(data: any, depth: number = 0): Record<string, any>[] | null {
|
||||
if (depth > 8 || data === null || data === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
if (data.length > 0 && data.every(item =>
|
||||
item &&
|
||||
typeof item === 'object' &&
|
||||
!Array.isArray(item)
|
||||
)) {
|
||||
return data as Record<string, any>[];
|
||||
}
|
||||
|
||||
for (const item of data) {
|
||||
const nested = this.findFirstArrayOfObjects(item, depth + 1);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof data !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const objectData = data as Record<string, any>;
|
||||
const priorityKeys = ['edges', 'nodes', 'items', 'results', 'data'];
|
||||
|
||||
for (const key of priorityKeys) {
|
||||
if (key in objectData) {
|
||||
const nested = this.findFirstArrayOfObjects(objectData[key], depth + 1);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.values(objectData)) {
|
||||
const nested = this.findFirstArrayOfObjects(value, depth + 1);
|
||||
if (nested) {
|
||||
return nested;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private normalizeTableRows(rows: Record<string, any>[]): Record<string, any>[] {
|
||||
// Common GraphQL edge shape: [{ node: {...} }]
|
||||
const canUnwrapNode = rows.length > 0 && rows.every(row =>
|
||||
row &&
|
||||
typeof row === 'object' &&
|
||||
!Array.isArray(row) &&
|
||||
Object.keys(row).length === 1 &&
|
||||
row.node &&
|
||||
typeof row.node === 'object' &&
|
||||
!Array.isArray(row.node)
|
||||
);
|
||||
|
||||
if (canUnwrapNode) {
|
||||
return rows.map(row => row.node as Record<string, any>);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private tableCellValue(value: any): string {
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
|
|
@ -194,6 +304,15 @@ export default class DataFetcherPlugin extends Plugin {
|
|||
return String(value);
|
||||
}
|
||||
|
||||
private tableCellDisplayValue(value: any): string {
|
||||
const raw = this.tableCellValue(value).replace(/\s+/g, ' ').trim();
|
||||
const maxLength = 120;
|
||||
if (raw.length <= maxLength) {
|
||||
return raw;
|
||||
}
|
||||
return `${raw.substring(0, maxLength - 3)}...`;
|
||||
}
|
||||
|
||||
private toMarkdownTable(headers: string[], rows: Record<string, any>[]): string {
|
||||
const headerLine = `| ${headers.join(' | ')} |`;
|
||||
const dividerLine = `| ${headers.map(() => '---').join(' | ')} |`;
|
||||
|
|
@ -311,7 +430,14 @@ export default class DataFetcherPlugin extends Plugin {
|
|||
outputText = 'No data returned';
|
||||
content.setText(outputText);
|
||||
} else if (format === 'table') {
|
||||
const tableData = this.buildTableData(selectedData);
|
||||
const tableInput = this.tryResolveTableInput(selectedData);
|
||||
const resolvedTableInput = this.buildTableData(tableInput)
|
||||
? tableInput
|
||||
: (this.findFirstArrayOfObjects(tableInput) || tableInput);
|
||||
const initialTable = this.buildTableData(resolvedTableInput);
|
||||
const tableData = initialTable
|
||||
? this.buildTableData(this.normalizeTableRows(initialTable.rows))
|
||||
: null;
|
||||
if (tableData) {
|
||||
const tableEl = content.createEl('table', { cls: 'data-fetcher-table' });
|
||||
const thead = tableEl.createEl('thead');
|
||||
|
|
@ -323,7 +449,13 @@ export default class DataFetcherPlugin extends Plugin {
|
|||
for (const row of tableData.rows) {
|
||||
const tr = tbody.createEl('tr');
|
||||
for (const headerName of tableData.headers) {
|
||||
tr.createEl('td', { text: this.tableCellValue(row[headerName]) });
|
||||
const fullValue = this.tableCellValue(row[headerName]);
|
||||
const cell = tr.createEl('td');
|
||||
cell.createEl('span', {
|
||||
text: this.tableCellDisplayValue(row[headerName]),
|
||||
cls: 'data-fetcher-table-cell',
|
||||
attr: { title: fullValue }
|
||||
});
|
||||
}
|
||||
}
|
||||
outputText = this.toMarkdownTable(tableData.headers, tableData.rows);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "data-fetcher",
|
||||
"name": "Data Fetcher",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"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.0.7",
|
||||
"version": "1.0.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-api-fetcher",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-api-fetcher",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.8",
|
||||
"description": "Fetch data from multiple sources (REST APIs, RPC, gRPC, GraphQL) and insert results into notes",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,15 @@
|
|||
background-color: var(--background-secondary);
|
||||
}
|
||||
|
||||
.data-fetcher-table-cell {
|
||||
display: inline-block;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.data-fetcher-format-note {
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-muted);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"1.0.5": "1.8.3",
|
||||
"1.0.6": "1.8.3",
|
||||
"1.0.7": "1.8.3"
|
||||
"1.0.7": "1.8.3",
|
||||
"1.0.8": "1.8.3"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue