release: 1.0.8

This commit is contained in:
qf3l3k 2026-03-02 17:55:34 +01:00
parent 4718640d6a
commit 4f9055cd1e
7 changed files with 152 additions and 7 deletions

View file

@ -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
View file

@ -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);

View file

@ -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
View file

@ -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",

View file

@ -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": {

View file

@ -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);

View file

@ -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"
}