From 18720c71964ed163b2825192393839a4d7d53976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Thu, 5 May 2022 00:55:14 +0200 Subject: [PATCH 01/10] pruebas testeando --- .eslintrc.js | 10 +++++++++- docs/changelog.md | 5 ++++- package.json | 1 + {__mocks__ => src/__mocks__}/obsidian.ts | 0 src/__tests__/lab.test.ts | 11 +++++++++++ src/mock/labTest.ts | 3 +++ 6 files changed, 28 insertions(+), 2 deletions(-) rename {__mocks__ => src/__mocks__}/obsidian.ts (100%) create mode 100644 src/__tests__/lab.test.ts create mode 100644 src/mock/labTest.ts diff --git a/.eslintrc.js b/.eslintrc.js index be82aa4e..4623c211 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,5 +10,13 @@ module.exports = { sourceType: "module", }, plugins: ["@typescript-eslint"], - rules: {}, + rules: { + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-expect-error": false, + "ts-ignore": false + } + ], + }, }; \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index 1d665369..53420e5c 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,14 +1,17 @@ ## 0.1.2 +*Published on 2022/05/04* ### Improved - Now DnD of file column and persist order are supported. [ISSUE#18](https://github.com/RafaelGB/obsidian-db-folder/issues/18) ### No longer broken - Change select cell type no crash the view anymore. This is a but introduced in 0.1.1. ## 0.1.1 +*Published on 2022/05/03* ### Improved - The width of columns are adjusted when a column is added of removed ### No longer broken - Now if you update a cell and then use global filter, the value is updated correctly [ISSUE#23](https://github.com/RafaelGB/obsidian-db-folder/issues/23) ## 0.1.0 +*Published on 2022/05/02* ### Shiny new things - New button to download a CSV file with the current data (supports filtering!). Temporally this feature is inside menu bar. We are working on move it into the actual file options of Obsidian [ISSUE#15](https://github.com/RafaelGB/obsidian-db-folder/issues/15) ### Improved @@ -23,7 +26,7 @@ - Add prefix to the className of components, so interference with other plugins is less probable. [ISSUE#19](https://github.com/RafaelGB/obsidian-bd-folder/issues/19) - When column folder is activated and a file is moved, now link is updated correctly ## 0.0.7 -*Published on 04/27/2022* +*Published on 2022/04/27* ### Shiny new things - New local property `group_folder_column` to specify a select column type column. This column will be used to group the notes into subfolders with the same cell value. The subfolder will be created if it does not exist. [ISSUE#11](https://github.com/RafaelGB/obsidian-bd-folder/issues/11) diff --git a/package.json b/package.json index 0989cbe1..205b8931 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@typescript-eslint/parser": "5.20.0", "eslint": "8.14.0", "jest": "27.5.1", + "jest-mock-extended": "2.0.5", "obsidian": "0.14.6", "obsidian-dataview": "0.5.17", "rollup": "2.70.2", diff --git a/__mocks__/obsidian.ts b/src/__mocks__/obsidian.ts similarity index 100% rename from __mocks__/obsidian.ts rename to src/__mocks__/obsidian.ts diff --git a/src/__tests__/lab.test.ts b/src/__tests__/lab.test.ts new file mode 100644 index 00000000..8cd17abe --- /dev/null +++ b/src/__tests__/lab.test.ts @@ -0,0 +1,11 @@ +import { MockProxy, mock, mockDeep, DeepMockProxy } from 'jest-mock-extended'; +import { labTest1 } from 'mock/labTest'; +import { App } from 'obsidian'; +/* tslint:disable */ +test('test', () => { + // @ts-ignore + const mockObj: DeepMockProxy = mockDeep(); + global.app = mockObj; + mockObj.vault.getFiles.mockReturnValue([]); + expect(labTest1()).toEqual([]); +}); \ No newline at end of file diff --git a/src/mock/labTest.ts b/src/mock/labTest.ts new file mode 100644 index 00000000..df8d2eff --- /dev/null +++ b/src/mock/labTest.ts @@ -0,0 +1,3 @@ +export function labTest1() { + return app.vault.getFiles() +} \ No newline at end of file From f3a8fcc6a384eab1c3e4dc26dcb7a571225ee6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Thu, 5 May 2022 17:29:23 +0200 Subject: [PATCH 02/10] mock obsidian interface working! --- .eslintrc.js | 1 - {src/__mocks__ => __mocks__}/obsidian.ts | 0 jest.config.js | 2 +- package.json | 17 ++++--- src/__tests__/IO/columns.test.ts | 23 +++++++++ src/__tests__/lab.test.ts | 62 ++++++++++++++++++++++-- src/mock/mockUtils.tsx | 41 ++++++++++++++++ 7 files changed, 131 insertions(+), 15 deletions(-) rename {src/__mocks__ => __mocks__}/obsidian.ts (100%) create mode 100644 src/__tests__/IO/columns.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index 4623c211..be971be8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,6 @@ module.exports = { "@typescript-eslint/ban-ts-comment": [ "error", { - "ts-expect-error": false, "ts-ignore": false } ], diff --git a/src/__mocks__/obsidian.ts b/__mocks__/obsidian.ts similarity index 100% rename from src/__mocks__/obsidian.ts rename to __mocks__/obsidian.ts diff --git a/jest.config.js b/jest.config.js index 35e949ca..72cdc0cd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,6 @@ module.exports = { transform: { '^.+\\.ts$': 'ts-jest', }, - moduleFileExtensions: ['js', 'ts'], + moduleFileExtensions: ['js', 'ts', 'tsx'], testEnvironment: 'jsdom', }; \ No newline at end of file diff --git a/package.json b/package.json index 205b8931..e197a682 100644 --- a/package.json +++ b/package.json @@ -20,25 +20,26 @@ "devDependencies": { "@rollup/plugin-commonjs": "22.0.0", "@rollup/plugin-json": "4.1.0", - "@rollup/plugin-node-resolve": "13.2.1", + "@rollup/plugin-node-resolve": "13.3.0", "@rollup/plugin-typescript": "8.3.2", - "@types/jest": "27.4.1", + "@types/jest": "27.5.0", + "@types/faker": "6.6.9", "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "12.1.2", - "@types/node": "17.0.25", + "@types/node": "17.0.31", "@types/react": "17.0.43", "@types/react-dom": "17.0.14", - "@types/react-table": "7.7.10", + "@types/react-table": "7.7.11", "@types/react-window": "1.8.5", "@types/react-beautiful-dnd": "13.1.2", "@types/react-csv": "1.1.2", - "@typescript-eslint/eslint-plugin": "5.20.0", - "@typescript-eslint/parser": "5.20.0", + "@typescript-eslint/eslint-plugin": "5.22.0", + "@typescript-eslint/parser": "5.22.0", "eslint": "8.14.0", "jest": "27.5.1", "jest-mock-extended": "2.0.5", "obsidian": "0.14.6", - "obsidian-dataview": "0.5.17", + "obsidian-dataview": "0.5.18", "rollup": "2.70.2", "svelte-check": "2.7.0", "svelte-jester": "2.3.2", @@ -46,7 +47,7 @@ "ts-jest": "27.1.4", "tslib": "2.4.0", "typescript": "4.6.4", - "@faker-js/faker": "6.2.0" + "@faker-js/faker": "6.3.1" }, "dependencies": { "@material-ui/core": "4.12.4", diff --git a/src/__tests__/IO/columns.test.ts b/src/__tests__/IO/columns.test.ts new file mode 100644 index 00000000..49402b9f --- /dev/null +++ b/src/__tests__/IO/columns.test.ts @@ -0,0 +1,23 @@ +import { DatabaseColumn } from "cdm/DatabaseModel"; +import { obtainMetadataColumns } from "components/Columns"; +import { DataTypes, MetadataColumns } from "helpers/Constants"; + +test('Obtain metadata columns test', async () => { + const result = await obtainMetadataColumns(generateYamlColumns(5)); + expect(result).toHaveProperty(MetadataColumns.FILE); +}); + +const generateYamlColumns = (length: number): Record => { + const yamlColumns: Record = {}; + yamlColumns['column1'] = { + input: DataTypes.TEXT, + accessor: "uniqueKey", + label: "custom label", + key: "uniqueKey", + position: 1, + isMetadata: false, + skipPersist: true, + csvCandidate: false + } + return yamlColumns; +} \ No newline at end of file diff --git a/src/__tests__/lab.test.ts b/src/__tests__/lab.test.ts index 8cd17abe..eb889c7c 100644 --- a/src/__tests__/lab.test.ts +++ b/src/__tests__/lab.test.ts @@ -1,11 +1,63 @@ import { MockProxy, mock, mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { labTest1 } from 'mock/labTest'; -import { App } from 'obsidian'; -/* tslint:disable */ +import { App, TFile } from 'obsidian'; test('test', () => { + const tfileArrayMock: MockProxy = mock(); + tfileArrayMock // @ts-ignore const mockObj: DeepMockProxy = mockDeep(); global.app = mockObj; - mockObj.vault.getFiles.mockReturnValue([]); - expect(labTest1()).toEqual([]); -}); \ No newline at end of file + mockObj.vault.getFiles.mockReturnValue(fakeGetFilesResponse); + expect(labTest1()).toEqual(fakeGetFilesResponse); +}); + +const fakeGetFilesResponse: TFile[] = [ + { + "name": "file1", + "path": "path1", + "parent": { + "name": "folder1", + "children": [ + { + "name": "file2", + "path": "path2", + "vault": null, + "parent": null, + }, + ], + "isRoot": () => true, + "vault": null, + "path": "path1", + "parent": null + }, + "basename": "file1", + "vault": null, + "extension": "", + "stat": null + }, + { + "name": "file2", + "path": "path2", + "parent": { + "name": "folder1", + "children": [ + { + "name": "file2", + "path": "path2", + "vault": null, + "parent": null, + }, + ], + "isRoot": () => true, + "vault": null, + "path": "path1", + "parent": null + }, + "basename": "file2", + "vault": null, + "extension": "", + "stat": null + }, +]; + + diff --git a/src/mock/mockUtils.tsx b/src/mock/mockUtils.tsx index 9b4b6b63..02469d84 100644 --- a/src/mock/mockUtils.tsx +++ b/src/mock/mockUtils.tsx @@ -1,3 +1,4 @@ +// ts import faker from "@faker-js/faker"; import { MarkdownRenderer } from "obsidian"; import React, { useRef, useLayoutEffect } from "react"; @@ -5,6 +6,13 @@ import { randomColor } from "helpers/Colors"; import { DataTypes } from "helpers/Constants"; import { TableDataType, TableColumn, RowDataType } from "cdm/FolderModel"; import { LOGGER } from "services/Logger"; +import { DatabaseColumn } from "cdm/DatabaseModel"; + +/** + * Generate a random initialState table with the given number of rows. + * @param count number of rows to generate + * @returns + */ export function makeData(count: number): TableDataType { const data: Array = []; const options = []; @@ -66,3 +74,36 @@ export function makeData(count: number): TableDataType { view: null, }; } + +export const generateYamlColumns = ( + count: number +): Record => { + const yamlColumns: Record = {}; + for (let i = 0; i < count; i++) { + const columnKey: string = faker.unique(faker.name.firstName); + yamlColumns[columnKey] = { + input: getRandomEnumValue(DataTypes), + accessor: columnKey, + label: `${columnKey} label`, + key: columnKey, + position: 1, + isMetadata: false, + skipPersist: true, + csvCandidate: false, + }; + } + return yamlColumns; +}; + +function getRandomEnumValue(anEnum: T): T[keyof T] { + //save enums inside array + const enumValues = Object.keys(anEnum) as Array; + + //Generate a random index (max is array length) + const randomIndex = Math.floor(Math.random() * enumValues.length); + // get the random enum value + + const randomEnumKey = enumValues[randomIndex]; + return anEnum[randomEnumKey]; + // if you want to have the key than return randomEnumKey +} From 15d8a47b99deb402c64522c9bd517c5993cf0783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Thu, 5 May 2022 19:10:21 +0200 Subject: [PATCH 03/10] test columns --- src/__tests__/IO/columns.test.ts | 41 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/__tests__/IO/columns.test.ts b/src/__tests__/IO/columns.test.ts index 49402b9f..be5baba2 100644 --- a/src/__tests__/IO/columns.test.ts +++ b/src/__tests__/IO/columns.test.ts @@ -1,23 +1,18 @@ -import { DatabaseColumn } from "cdm/DatabaseModel"; -import { obtainMetadataColumns } from "components/Columns"; -import { DataTypes, MetadataColumns } from "helpers/Constants"; - -test('Obtain metadata columns test', async () => { - const result = await obtainMetadataColumns(generateYamlColumns(5)); - expect(result).toHaveProperty(MetadataColumns.FILE); -}); - -const generateYamlColumns = (length: number): Record => { - const yamlColumns: Record = {}; - yamlColumns['column1'] = { - input: DataTypes.TEXT, - accessor: "uniqueKey", - label: "custom label", - key: "uniqueKey", - position: 1, - isMetadata: false, - skipPersist: true, - csvCandidate: false - } - return yamlColumns; -} \ No newline at end of file +import { obtainMetadataColumns, obtainColumnsFromFolder } from "components/Columns"; +import { MetadataColumns } from "helpers/Constants"; +import { generateYamlColumns } from "mock/mockUtils"; +describe("Columns", () => { + /** Metadata columns */ + test('obtainMetadataColumns()', async () => { + // Check if mandatory columns exists + const columnsRecord = await obtainMetadataColumns(generateYamlColumns(5)); + expect(columnsRecord).toHaveProperty(MetadataColumns.FILE); + expect(columnsRecord).toHaveProperty(MetadataColumns.ADD_COLUMN); + }); + /** Table columns */ + test('obtainColumnsFromFolder()', async () => { + const columnsRecord = await obtainMetadataColumns(generateYamlColumns(5)); + const tableColumns = await obtainColumnsFromFolder(columnsRecord); + expect(tableColumns.length >= 5).toBeTruthy(); + }); +}); \ No newline at end of file From 42e2d3107d0af62c042a97b8f68787bad2681f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Thu, 5 May 2022 19:19:41 +0200 Subject: [PATCH 04/10] now that app is mocked, render works! but mockedTableData must be improved --- jest.config.js | 4 ++-- package.json | 2 +- src/__tests__/reactTable.test.tsx | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/jest.config.js b/jest.config.js index 72cdc0cd..b1bef195 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,10 +1,10 @@ module.exports = { verbose: true, preset: 'ts-jest', + testEnvironment: 'jsdom', moduleDirectories: ["node_modules", "src"], transform: { '^.+\\.ts$': 'ts-jest', }, - moduleFileExtensions: ['js', 'ts', 'tsx'], - testEnvironment: 'jsdom', + moduleFileExtensions: ['js', 'ts', 'tsx'] }; \ No newline at end of file diff --git a/package.json b/package.json index e197a682..395ba413 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "rollup --config rollup.config.js -w", "build": "rollup --config rollup.config.js", - "test": "jest --env=node --colors --coverage test", + "test": "jest --colors --coverage test", "pretest": "eslint --ignore-path .gitignore src/**/*.ts" }, "keywords": [ diff --git a/src/__tests__/reactTable.test.tsx b/src/__tests__/reactTable.test.tsx index 943ee3a5..0e0bbcad 100644 --- a/src/__tests__/reactTable.test.tsx +++ b/src/__tests__/reactTable.test.tsx @@ -1,15 +1,17 @@ import { render, screen } from "@testing-library/react"; -import obsidian from "obsidian"; import { Database } from "components/index/Database"; import { TableDataType } from "cdm/FolderModel"; import { makeData } from "mock/mockUtils"; import React from "react"; -// jest.mock("obsidian"); - -test("Render without crashing", () => { - // const mockedTableData: TableDataType = makeData(10); - // render(); - // const titleLabel = screen.getByText(" title"); - // expect(titleLabel).toBeInTheDocument(); +/** + * @jest-environment node || jsdom + */ +describe("React-table", () => { + test("Render without crashing", () => { + const mockedTableData: TableDataType = makeData(10); + render(); + const titleLabel = screen.getByText(" title"); + expect(titleLabel).toBeInTheDocument(); + }); }); From 95b18eb0e2675de1ec4909cb5db44d636b4cc86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Thu, 5 May 2022 21:52:30 +0200 Subject: [PATCH 05/10] cleaning --- package.json | 5 +-- src/__tests__/reactTable.test.tsx | 6 ++-- src/cdm/FolderModel.ts | 2 +- src/mock/mockUtils.tsx | 57 +++++++++---------------------- versions.json | 2 -- 5 files changed, 22 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index 395ba413..be8d1935 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,7 @@ "jest-mock-extended": "2.0.5", "obsidian": "0.14.6", "obsidian-dataview": "0.5.18", - "rollup": "2.70.2", - "svelte-check": "2.7.0", - "svelte-jester": "2.3.2", - "svelte-preprocess": "4.10.6", + "rollup": "2.72.0", "ts-jest": "27.1.4", "tslib": "2.4.0", "typescript": "4.6.4", diff --git a/src/__tests__/reactTable.test.tsx b/src/__tests__/reactTable.test.tsx index 0e0bbcad..7fa7bc45 100644 --- a/src/__tests__/reactTable.test.tsx +++ b/src/__tests__/reactTable.test.tsx @@ -5,11 +5,11 @@ import { makeData } from "mock/mockUtils"; import React from "react"; /** - * @jest-environment node || jsdom + * @jest-environment jsdom */ describe("React-table", () => { - test("Render without crashing", () => { - const mockedTableData: TableDataType = makeData(10); + test("Render without crashing", async () => { + const mockedTableData: TableDataType = await makeData(10); render(); const titleLabel = screen.getByText(" title"); expect(titleLabel).toBeInTheDocument(); diff --git a/src/cdm/FolderModel.ts b/src/cdm/FolderModel.ts index 458e42a0..606383ad 100644 --- a/src/cdm/FolderModel.ts +++ b/src/cdm/FolderModel.ts @@ -64,7 +64,7 @@ export type TableDataType = { data: Array, skipReset: boolean, view: DatabaseView, - stateManager?: StateManager, + stateManager: StateManager, dispatch?: Dispatch } export interface DatabaseHeaderProps { diff --git a/src/mock/mockUtils.tsx b/src/mock/mockUtils.tsx index 02469d84..39b4fa4a 100644 --- a/src/mock/mockUtils.tsx +++ b/src/mock/mockUtils.tsx @@ -7,13 +7,14 @@ import { DataTypes } from "helpers/Constants"; import { TableDataType, TableColumn, RowDataType } from "cdm/FolderModel"; import { LOGGER } from "services/Logger"; import { DatabaseColumn } from "cdm/DatabaseModel"; +import { obtainColumnsFromFolder } from "components/Columns"; /** * Generate a random initialState table with the given number of rows. * @param count number of rows to generate * @returns */ -export function makeData(count: number): TableDataType { +export async function makeData(count: number): Promise { const data: Array = []; const options = []; const note: any = null; @@ -27,54 +28,25 @@ export function makeData(count: number): TableDataType { data.push(row); } - const columns: TableColumn[] = [ - { - id: "title", - label: "File Name", - key: "title", - accessor: "title", - position: 0, - minWidth: 100, - dataType: DataTypes.TEXT, - options: options, - csvCandidate: true, - Cell: ({ cell }: any) => { - const { value } = cell; - const containerRef = useRef(); - LOGGER.info("containerRef: " + containerRef); - useLayoutEffect(() => { - MarkdownRenderer.renderMarkdown( - "[[readme]]", - containerRef.current, - "readme.md", - null - ); - }); - return ; - }, - }, - { - id: "status", - label: "Status", - key: "Status", - accessor: "Status", - position: 1, - minWidth: 100, - dataType: DataTypes.TEXT, - options: options, - csvCandidate: true, - }, - ]; + const columns: TableColumn[] = await obtainColumnsFromFolder( + generateYamlColumns(5) + ); return { columns: columns, shadowColumns: [], data: data, skipReset: false, view: null, + stateManager: null, }; } +/** + * Generate random columns + * @param count number of columns to generate + * @returns + */ export const generateYamlColumns = ( count: number ): Record => { @@ -95,7 +67,12 @@ export const generateYamlColumns = ( return yamlColumns; }; -function getRandomEnumValue(anEnum: T): T[keyof T] { +/** + * Given a Enum, return a random valueß + * @param anEnum + * @returns + */ +export function getRandomEnumValue(anEnum: T): T[keyof T] { //save enums inside array const enumValues = Object.keys(anEnum) as Array; diff --git a/versions.json b/versions.json index ba147850..f3ee419d 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,2 @@ { - "1.0.1": "0.9.12", - "1.0.0": "0.9.7" } From c75401600954d5e28dbe90f5506b84ee061c4d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Thu, 5 May 2022 23:37:41 +0200 Subject: [PATCH 06/10] refactor mocks --- __mocks__/StateManager.ts | 0 src/DatabaseView.tsx | 2 +- src/StateManager.ts | 2 +- src/__tests__/IO/columns.test.ts | 2 +- src/__tests__/lab.test.ts | 14 ++++----- src/__tests__/reactTable.test.tsx | 29 ++++++++++++++++++- src/cdm/FolderModel.ts | 2 +- src/components/Table.tsx | 2 +- src/main.ts | 2 +- src/mock/mockDataviewUtils.ts | 12 ++++++++ src/mock/mockObsidianUtils.ts | 0 .../{mockUtils.tsx => mockTableUtils.tsx} | 2 -- 12 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 __mocks__/StateManager.ts create mode 100644 src/mock/mockDataviewUtils.ts create mode 100644 src/mock/mockObsidianUtils.ts rename src/mock/{mockUtils.tsx => mockTableUtils.tsx} (96%) diff --git a/__mocks__/StateManager.ts b/__mocks__/StateManager.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/DatabaseView.tsx b/src/DatabaseView.tsx index 9c6298ba..69a4026e 100644 --- a/src/DatabaseView.tsx +++ b/src/DatabaseView.tsx @@ -23,7 +23,7 @@ import ReactDOM from "react-dom"; import DatabaseInfo from "services/DatabaseInfo"; import { LOGGER } from "services/Logger"; import { SettingsModal } from "Settings"; -import { StateManager } from "StateManager"; +import StateManager from "StateManager"; export const databaseIcon = "blocks"; export class DatabaseView extends TextFileView implements HoverParent { diff --git a/src/StateManager.ts b/src/StateManager.ts index 83719c25..4dd1fd02 100644 --- a/src/StateManager.ts +++ b/src/StateManager.ts @@ -2,7 +2,7 @@ import { DatabaseView } from "DatabaseView"; import { App, TFile } from 'obsidian'; import { LOGGER } from "services/Logger"; import { DatabaseSettings } from 'Settings'; -export class StateManager { +export default class StateManager { private onEmpty: () => void; private getGlobalSettings: () => DatabaseSettings; private viewSet: Set = new Set(); diff --git a/src/__tests__/IO/columns.test.ts b/src/__tests__/IO/columns.test.ts index be5baba2..4a52120d 100644 --- a/src/__tests__/IO/columns.test.ts +++ b/src/__tests__/IO/columns.test.ts @@ -1,6 +1,6 @@ import { obtainMetadataColumns, obtainColumnsFromFolder } from "components/Columns"; import { MetadataColumns } from "helpers/Constants"; -import { generateYamlColumns } from "mock/mockUtils"; +import { generateYamlColumns } from "mock/mockTableUtils"; describe("Columns", () => { /** Metadata columns */ test('obtainMetadataColumns()', async () => { diff --git a/src/__tests__/lab.test.ts b/src/__tests__/lab.test.ts index eb889c7c..bc08796e 100644 --- a/src/__tests__/lab.test.ts +++ b/src/__tests__/lab.test.ts @@ -2,13 +2,13 @@ import { MockProxy, mock, mockDeep, DeepMockProxy } from 'jest-mock-extended'; import { labTest1 } from 'mock/labTest'; import { App, TFile } from 'obsidian'; test('test', () => { - const tfileArrayMock: MockProxy = mock(); - tfileArrayMock - // @ts-ignore - const mockObj: DeepMockProxy = mockDeep(); - global.app = mockObj; - mockObj.vault.getFiles.mockReturnValue(fakeGetFilesResponse); - expect(labTest1()).toEqual(fakeGetFilesResponse); + // const tfileArrayMock: MockProxy = mock(); + // tfileArrayMock + // // @ts-ignore + // const mockObj: DeepMockProxy = mockDeep(); + // global.app = mockObj; + // mockObj.vault.getFiles.mockReturnValue(fakeGetFilesResponse); + // expect(labTest1()).toEqual(fakeGetFilesResponse); }); const fakeGetFilesResponse: TFile[] = [ diff --git a/src/__tests__/reactTable.test.tsx b/src/__tests__/reactTable.test.tsx index 7fa7bc45..0e770b49 100644 --- a/src/__tests__/reactTable.test.tsx +++ b/src/__tests__/reactTable.test.tsx @@ -1,17 +1,44 @@ import { render, screen } from "@testing-library/react"; import { Database } from "components/index/Database"; import { TableDataType } from "cdm/FolderModel"; -import { makeData } from "mock/mockUtils"; +import { makeData } from "mock/mockTableUtils"; import React from "react"; +import { + DeepMockProxy, + mockDeep, + mock, + MockProxy, + mockReset, +} from "jest-mock-extended"; +import { App } from "obsidian"; +import StateManager from "StateManager"; /** * @jest-environment jsdom */ describe("React-table", () => { + // @ts-ignore + const mockGlobalApp: DeepMockProxy = mockDeep(); + /** + * Run this before each test. + */ + beforeAll(() => { + mockReset(mockGlobalApp); + global.app = mockGlobalApp; + }); + test("Render without crashing", async () => { const mockedTableData: TableDataType = await makeData(10); + //mockedTableData.stateManager = StateManager(); render(); const titleLabel = screen.getByText(" title"); expect(titleLabel).toBeInTheDocument(); }); + + /** + * Run this after each test. + */ + afterAll(() => { + global.app = undefined; + }); }); diff --git a/src/cdm/FolderModel.ts b/src/cdm/FolderModel.ts index 606383ad..026fe5c0 100644 --- a/src/cdm/FolderModel.ts +++ b/src/cdm/FolderModel.ts @@ -1,6 +1,6 @@ import { DatabaseView } from "DatabaseView"; import { Dispatch } from "react"; -import { StateManager } from "StateManager"; +import StateManager from "StateManager"; import { RowType } from "cdm/RowTypeModel"; import { RowSelectOption } from "cdm/RowSelectModel"; import NoteInfo from "services/NoteInfo"; diff --git a/src/components/Table.tsx b/src/components/Table.tsx index e06f5ace..373b0077 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -11,7 +11,7 @@ import { import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import { TableDataType, RowDataType, TableColumn } from "cdm/FolderModel"; import { DatabaseView } from "DatabaseView"; -import { StateManager } from "StateManager"; +import StateManager from "StateManager"; import { getNormalizedPath } from "helpers/VaultManagement"; import { ActionTypes, DatabaseCore } from "helpers/Constants"; import PlusIcon from "components/img/Plus"; diff --git a/src/main.ts b/src/main.ts index b6996d5c..64edfd10 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,7 @@ import { DBFolderAPI } from 'api/plugin-api'; -import { StateManager } from 'StateManager'; +import StateManager from 'StateManager'; import { around } from 'monkey-around'; import { LOGGER } from 'services/Logger'; import { DatabaseCore, DatabaseFrontmatterOptions } from 'helpers/Constants'; diff --git a/src/mock/mockDataviewUtils.ts b/src/mock/mockDataviewUtils.ts new file mode 100644 index 00000000..6631339c --- /dev/null +++ b/src/mock/mockDataviewUtils.ts @@ -0,0 +1,12 @@ +import { DatabaseView } from "DatabaseView"; +import StateManager from "StateManager"; + +// TODO +export const generateStateManager = (): StateManager => { + const initialView: DatabaseView = new DatabaseView( + app.workspace.activeLeaf, + null + ); + const stateManager = new StateManager(app, initialView, null, null, null); + return stateManager; +}; \ No newline at end of file diff --git a/src/mock/mockObsidianUtils.ts b/src/mock/mockObsidianUtils.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/mock/mockUtils.tsx b/src/mock/mockTableUtils.tsx similarity index 96% rename from src/mock/mockUtils.tsx rename to src/mock/mockTableUtils.tsx index 39b4fa4a..151f48fa 100644 --- a/src/mock/mockUtils.tsx +++ b/src/mock/mockTableUtils.tsx @@ -1,11 +1,9 @@ // ts import faker from "@faker-js/faker"; -import { MarkdownRenderer } from "obsidian"; import React, { useRef, useLayoutEffect } from "react"; import { randomColor } from "helpers/Colors"; import { DataTypes } from "helpers/Constants"; import { TableDataType, TableColumn, RowDataType } from "cdm/FolderModel"; -import { LOGGER } from "services/Logger"; import { DatabaseColumn } from "cdm/DatabaseModel"; import { obtainColumnsFromFolder } from "components/Columns"; From 1e76ddbadeba69c09f49f46058e39c4e9d264f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Fri, 6 May 2022 10:21:39 +0200 Subject: [PATCH 07/10] inline file edit working if you specify that is inline --- src/cdm/DatabaseModel.ts | 1 + src/cdm/FolderModel.ts | 1 + src/components/Columns.tsx | 1 + src/components/reducers/DatabaseDispatch.tsx | 17 ++++++++++------- src/helpers/Constants.ts | 1 + src/helpers/VaultManagement.ts | 17 +++++++++++++++++ 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/cdm/DatabaseModel.ts b/src/cdm/DatabaseModel.ts index 059f65ac..32896881 100644 --- a/src/cdm/DatabaseModel.ts +++ b/src/cdm/DatabaseModel.ts @@ -11,6 +11,7 @@ export type DatabaseColumn = { isMetadata?: boolean, skipPersist?: boolean, csvCandidate?: boolean, + isInline?: boolean, [key: string]: RowType } diff --git a/src/cdm/FolderModel.ts b/src/cdm/FolderModel.ts index 026fe5c0..8f714090 100644 --- a/src/cdm/FolderModel.ts +++ b/src/cdm/FolderModel.ts @@ -49,6 +49,7 @@ export type TableColumn = { getResizerProps?: any, isMetadata?: boolean, skipPersist?: boolean, + isInline?: boolean, csvCandidate: boolean } diff --git a/src/components/Columns.tsx b/src/components/Columns.tsx index 0d1ca043..89f1062f 100644 --- a/src/components/Columns.tsx +++ b/src/components/Columns.tsx @@ -62,6 +62,7 @@ async function columnOptions( isMetadata: column.isMetadata ?? false, skipPersist: column.skipPersist ?? false, csvCandidate: column.csvCandidate ?? true, + isInline: column.isInline ?? false, }; /** * return plain text diff --git a/src/components/reducers/DatabaseDispatch.tsx b/src/components/reducers/DatabaseDispatch.tsx index c75303c6..ae5fa97f 100644 --- a/src/components/reducers/DatabaseDispatch.tsx +++ b/src/components/reducers/DatabaseDispatch.tsx @@ -343,6 +343,7 @@ export function databaseReducer(state: TableDataType, action: ActionType) { ], }; case ActionTypes.UPDATE_OPTION_CELL: // save on disk + // check if this column is configured as a group folder if (dbconfig.group_folder_column === action.key) { moveFile(`${state.view.file.parent.path}/${action.value}`, action); action.row[ @@ -375,18 +376,20 @@ export function databaseReducer(state: TableDataType, action: ActionType) { }, }); } - // Else go UPDATE_CELL + // otherwise go UPDATE_CELL case ActionTypes.UPDATE_CELL: - // save on disk + // Obtain current column index + const update_cell_index = state.columns.findIndex( + (column) => column.id === action.columnId + ); + // Save on disk updateRowFile( action.file, action.key, action.value, - UpdateRowOptions.COLUMN_VALUE - ); - // Update original cell value - const update_cell_index = state.columns.findIndex( - (column) => column.id === action.columnId + state.columns[update_cell_index].isInline + ? UpdateRowOptions.INLINE_VALUE + : UpdateRowOptions.COLUMN_VALUE ); const update_option_cell_column_key = state.columns[update_cell_index].key; diff --git a/src/helpers/Constants.ts b/src/helpers/Constants.ts index 0c4333ee..11c5aa25 100644 --- a/src/helpers/Constants.ts +++ b/src/helpers/Constants.ts @@ -85,6 +85,7 @@ export const DatabaseFrontmatterOptions = Object.freeze({ export const UpdateRowOptions = Object.freeze({ COLUMN_VALUE: 'column_value', COLUMN_KEY: 'column_key', + INLINE_VALUE: 'inline_value', REMOVE_COLUMN: 'remove_column', ADD_COLUMN: 'add_column' }); diff --git a/src/helpers/VaultManagement.ts b/src/helpers/VaultManagement.ts index 4fb899d5..4d5412d6 100644 --- a/src/helpers/VaultManagement.ts +++ b/src/helpers/VaultManagement.ts @@ -193,10 +193,22 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str }; await VaultManagerDB.editNoteContent(noteObject); } + + async function inlineColumnEdit(): Promise { + const frontmatterRegex = new RegExp(`(^${columnId}[:]{2}\\s)+([\\w\\W]+?$)`, 'gm'); + const noteObject = { + action: 'replace', + file: file, + regexp: frontmatterRegex, + newValue: `$1${newValue}` + }; + await VaultManagerDB.editNoteContent(noteObject); + } // Record of options const updateOptions: Record = {}; updateOptions[UpdateRowOptions.COLUMN_VALUE] = columnValue; updateOptions[UpdateRowOptions.COLUMN_KEY] = columnKey; + updateOptions[UpdateRowOptions.INLINE_VALUE] = inlineColumnEdit; updateOptions[UpdateRowOptions.REMOVE_COLUMN] = removeColumn; updateOptions[UpdateRowOptions.ADD_COLUMN] = addColumn; // Execute action @@ -214,6 +226,11 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str LOGGER.info(`<=updateRowFile. asociatedFilePathToCell: ${file.path} | columnId: ${columnId} | newValue: ${newValue} | option: ${option}`); } +/** + * After update a row value, move the file to the new folder path + * @param folderPath + * @param action + */ export async function moveFile(folderPath: string, action: ActionType): Promise { await updateRowFile( action.file, From 774e7a9551690c6e58c67143f31d487b6a11032a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Fri, 6 May 2022 14:46:54 +0200 Subject: [PATCH 08/10] big refactor starting to support inline fields --- src/cdm/DatabaseModel.ts | 5 + src/components/reducers/DatabaseDispatch.tsx | 14 +- src/helpers/Constants.ts | 3 +- src/helpers/VaultManagement.ts | 140 +++++++------------ src/parsers/FileToRowDatabaseFields.ts | 26 ++++ src/parsers/RowDatabaseFieldsToFile.ts | 9 ++ src/services/DatabaseInfo.ts | 9 +- src/services/DataviewService.ts | 17 +++ 8 files changed, 118 insertions(+), 105 deletions(-) create mode 100644 src/parsers/FileToRowDatabaseFields.ts create mode 100644 src/parsers/RowDatabaseFieldsToFile.ts create mode 100644 src/services/DataviewService.ts diff --git a/src/cdm/DatabaseModel.ts b/src/cdm/DatabaseModel.ts index 32896881..568539ea 100644 --- a/src/cdm/DatabaseModel.ts +++ b/src/cdm/DatabaseModel.ts @@ -25,4 +25,9 @@ export type DatabaseYaml = { columns: Record /** database local configuration */ config?: LocalSettings +} + +export type RowDatabaseFields = { + frontmatter: Record; + inline: Record; } \ No newline at end of file diff --git a/src/components/reducers/DatabaseDispatch.tsx b/src/components/reducers/DatabaseDispatch.tsx index ae5fa97f..dc5d0ab7 100644 --- a/src/components/reducers/DatabaseDispatch.tsx +++ b/src/components/reducers/DatabaseDispatch.tsx @@ -97,7 +97,7 @@ export function databaseReducer(state: TableDataType, action: ActionType) { state.view.diskConfig.updateColumnProperties( action.columnId, { label: action.label, accessor: update_col_key, key: update_col_key }, - state.data // Update all rows with new key + state // Update all rows with new key ); // Update state return { @@ -247,7 +247,8 @@ export function databaseReducer(state: TableDataType, action: ActionType) { row.note.getFile(), newLeftColumn.key, "", - UpdateRowOptions.ADD_COLUMN + state, + UpdateRowOptions.COLUMN_VALUE ); }) ); @@ -294,7 +295,8 @@ export function databaseReducer(state: TableDataType, action: ActionType) { row.note.getFile(), newRIghtColumn.key, "", - UpdateRowOptions.ADD_COLUMN + state, + UpdateRowOptions.COLUMN_VALUE ); }) ); @@ -329,6 +331,7 @@ export function databaseReducer(state: TableDataType, action: ActionType) { row.note.getFile(), action.key, undefined, // delete does not need this field + state, UpdateRowOptions.REMOVE_COLUMN ); }) @@ -387,9 +390,8 @@ export function databaseReducer(state: TableDataType, action: ActionType) { action.file, action.key, action.value, - state.columns[update_cell_index].isInline - ? UpdateRowOptions.INLINE_VALUE - : UpdateRowOptions.COLUMN_VALUE + state, + UpdateRowOptions.COLUMN_VALUE ); const update_option_cell_column_key = state.columns[update_cell_index].key; diff --git a/src/helpers/Constants.ts b/src/helpers/Constants.ts index 11c5aa25..853740bb 100644 --- a/src/helpers/Constants.ts +++ b/src/helpers/Constants.ts @@ -86,8 +86,7 @@ export const UpdateRowOptions = Object.freeze({ COLUMN_VALUE: 'column_value', COLUMN_KEY: 'column_key', INLINE_VALUE: 'inline_value', - REMOVE_COLUMN: 'remove_column', - ADD_COLUMN: 'add_column' + REMOVE_COLUMN: 'remove_column' }); export const StyleClasses = Object.freeze({ diff --git a/src/helpers/VaultManagement.ts b/src/helpers/VaultManagement.ts index 4d5412d6..b7ced063 100644 --- a/src/helpers/VaultManagement.ts +++ b/src/helpers/VaultManagement.ts @@ -1,11 +1,13 @@ -import { RowDataType, NormalizedPath } from 'cdm/FolderModel'; -import { Notice, TFile } from 'obsidian'; -import { getAPI } from "obsidian-dataview" +import { RowDataType, NormalizedPath, TableDataType } from 'cdm/FolderModel'; +import { TFile } from 'obsidian'; import { ActionType } from 'react-table'; import { VaultManagerDB } from 'services/FileManagerService'; import { LOGGER } from "services/Logger"; import NoteInfo from 'services/NoteInfo'; import { DatabaseCore, UpdateRowOptions } from "helpers/Constants"; +import obtainRowDatabaseFields from 'parsers/FileToRowDatabaseFields'; +import { parseFrontmatterFieldsToString } from 'parsers/RowDatabaseFieldsToFile'; +import { getDataviewAPI } from 'services/DataviewService'; const noBreakSpace = /\u00A0/g; @@ -60,12 +62,11 @@ export function getNormalizedPath(path: string): NormalizedPath { * @returns */ export async function adapterTFilesToRows(folderPath: string): Promise> { - dataviewIsLoaded(); LOGGER.debug(`=> adapterTFilesToRows. folderPath:${folderPath}`); const rows: Array = []; let id = 0; - const folderFiles = getAPI(app).pages(`"${folderPath}"`).where(p => !p[DatabaseCore.FRONTMATTER_KEY]); + const folderFiles = getDataviewAPI().pages(`"${folderPath}"`).where(p => !p[DatabaseCore.FRONTMATTER_KEY]); await Promise.all(folderFiles.map(async (page) => { const noteInfo = new NoteInfo(page, ++id); rows.push(noteInfo.getRowDataType()); @@ -92,10 +93,10 @@ export function adapterRowToDatabaseYaml(rowInfo: any): string { * @param newColumnValue * @param option */ -export async function updateRowFile(file: TFile, columnId: string, newValue: string, option: string): Promise { +export async function updateRowFile(file: TFile, columnId: string, newValue: string, state: TableDataType, option: string): Promise { LOGGER.info(`=>updateRowFile. file: ${file.path} | columnId: ${columnId} | newValue: ${newValue} | option: ${option}`); - let content = await VaultManagerDB.obtainContentFromTfile(file); - + const rowFields = obtainRowDatabaseFields(file, state.columns); + const content = await VaultManagerDB.obtainContentFromTfile(file); // Adds an empty frontmatter at the beginning of the file async function addFrontmatter(): Promise { /* Regex explanation @@ -109,98 +110,65 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str newValue: `---\n---\n$1` }; // update content on disk and in memory - content = await VaultManagerDB.editNoteContent(noteObject); + await VaultManagerDB.editNoteContent(noteObject); } + // Modify value of a column async function columnValue(): Promise { - /* Regex explanation - * group 1 is frontmatter centinel until current column - * group 2 is key of current column - * group 3 is value we want to replace - * group 4 is the rest of the frontmatter - */ - const frontmatterRegex = new RegExp(`(^---\\s[\\w\\W]*?)+([\\s]*${columnId}[:]{1})+(.*)+([\\w\\W]*?\\s---)`, 'g'); - // Check if the column is already in the frontmatter - if (!frontmatterRegex.test(content)) { - // if the column is not in the frontmatter, add it - await addColumn(); - } else { - // If it is, replace the value - const noteObject = { - action: 'replace', - file: file, - regexp: frontmatterRegex, - newValue: `$1$2 ${newValue}$4` - }; - await VaultManagerDB.editNoteContent(noteObject); - } + const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); + + rowFields.frontmatter[columnId] = newValue; + const noteObject = { + action: 'replace', + file: file, + regexp: frontmatterGroupRegex, + newValue: parseFrontmatterFieldsToString(rowFields.frontmatter) + }; + + await VaultManagerDB.editNoteContent(noteObject); } + // Modify key of a column async function columnKey(): Promise { - // If it is, replace the value - /* Regex explanation - * group 1 is the frontmatter centinel until previous to current column - * group 2 is the column we want to replace - * group 3 is the rest of the frontmatter - */ - const frontmatterRegex = new RegExp(`(^---\\s[\\w\\W]*?)+([\\n]{1}${columnId}[:]{1})+([\\w\\W]*?\\s---)`, 'g'); + const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); // Check if the column is already in the frontmatter - if (!frontmatterRegex.test(content)) { - // if the column is not in the frontmatter, add it with the already updated key - columnId = newValue; - // then assign an empty value to the new key - newValue = ''; - await addColumn(); - } else { - const noteObject = { - action: 'replace', - file: file, - regexp: frontmatterRegex, - newValue: `$1\n${newValue}:$3` - }; - await VaultManagerDB.editNoteContent(noteObject); - } - } - // Remove a column - async function removeColumn(): Promise { - /* Regex explanation - * group 1 is the frontmatter centinel until previous to current column - * group 2 is the column we want to remove - * group 3 is the rest of the frontmatter - */ - const frontmatterRegex = new RegExp(`(^---[\\w\\W]*?)+([\\s]*${columnId}[:]{1}.+)+([\\s]*[\\w\\W]*?\\s---)`, 'g'); + // assign an empty value to the new key + rowFields.frontmatter[newValue] = rowFields.frontmatter[columnId] ?? ""; + delete rowFields.frontmatter[columnId]; + const noteObject = { action: 'replace', file: file, - regexp: frontmatterRegex, - newValue: `$1$3` + regexp: frontmatterGroupRegex, + newValue: parseFrontmatterFieldsToString(rowFields.frontmatter) }; await VaultManagerDB.editNoteContent(noteObject); } - // Add a column - async function addColumn(): Promise { - /* Regex explanation - * group 1 the entire frontmatter until flag --- - * group 2 is the rest of the frontmatter - */ - const frontmatterRegex = new RegExp(`(^---[\\w\\W]*?)+([\\s]*---)`, 'g'); + // Remove a column + async function removeColumn(): Promise { + const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); + delete rowFields.frontmatter[columnId]; const noteObject = { action: 'replace', file: file, - regexp: frontmatterRegex, - newValue: `$1\n${columnId}: ${newValue}$2` + regexp: frontmatterGroupRegex, + newValue: parseFrontmatterFieldsToString(rowFields.frontmatter) }; await VaultManagerDB.editNoteContent(noteObject); } async function inlineColumnEdit(): Promise { - const frontmatterRegex = new RegExp(`(^${columnId}[:]{2}\\s)+([\\w\\W]+?$)`, 'gm'); + /* Regex explanation + * group 1 is inline field checking that starts in new line + * group 2 is the current value of inline field + */ + const frontmatterRegex = new RegExp(`(^${columnId}[:]{ 2}\\s) + ([\\w\\W] +? $)`, 'gm'); const noteObject = { action: 'replace', file: file, regexp: frontmatterRegex, - newValue: `$1${newValue}` + newValue: `$1${newValue} ` }; await VaultManagerDB.editNoteContent(noteObject); } @@ -208,9 +176,8 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str const updateOptions: Record = {}; updateOptions[UpdateRowOptions.COLUMN_VALUE] = columnValue; updateOptions[UpdateRowOptions.COLUMN_KEY] = columnKey; - updateOptions[UpdateRowOptions.INLINE_VALUE] = inlineColumnEdit; updateOptions[UpdateRowOptions.REMOVE_COLUMN] = removeColumn; - updateOptions[UpdateRowOptions.ADD_COLUMN] = addColumn; + updateOptions[UpdateRowOptions.INLINE_VALUE] = inlineColumnEdit; // Execute action if (updateOptions[option]) { // Check if file has frontmatter @@ -223,7 +190,7 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str } else { throw `Error: option ${option} not supported yet`; } - LOGGER.info(`<=updateRowFile. asociatedFilePathToCell: ${file.path} | columnId: ${columnId} | newValue: ${newValue} | option: ${option}`); + LOGGER.info(`<= updateRowFile.asociatedFilePathToCell: ${file.path} | columnId: ${columnId} | newValue: ${newValue} | option: ${option} `); } /** @@ -236,33 +203,20 @@ export async function moveFile(folderPath: string, action: ActionType): Promise< action.file, action.key, action.value, + action.state, UpdateRowOptions.COLUMN_VALUE ); try { createFolder(folderPath); } catch (error) { - LOGGER.error(` moveFile Error: ${error.message}`); + LOGGER.error(` moveFile Error: ${error.message} `); // Handle error throw error; } - const filePath = `${folderPath}/${action.file.name}`; + const filePath = `${folderPath} /${action.file.name}`; await app.fileManager.renameFile(action.file, filePath); } -/** - * Check if dataview plugin is installed - * @returns true if installed, false otherwise - * @throws Error if plugin is not installed - */ -export function dataviewIsLoaded(): boolean { - if (getAPI()) { - return true; - } else { - new Notice(`Dataview plugin is not installed. Please install it to load Databases.`); - throw new Error('Dataview plugin is not installed'); - } -} - export async function createFolder(folderPath: string): Promise { await app.vault.adapter.exists(folderPath).then(async exists => { if (!exists) { diff --git a/src/parsers/FileToRowDatabaseFields.ts b/src/parsers/FileToRowDatabaseFields.ts new file mode 100644 index 00000000..92dbfa8b --- /dev/null +++ b/src/parsers/FileToRowDatabaseFields.ts @@ -0,0 +1,26 @@ +import { RowDatabaseFields } from "cdm/DatabaseModel"; +import { TableColumn } from "cdm/FolderModel"; +import { TFile } from "obsidian"; +import { getDataviewAPI } from "services/DataviewService"; + + +const obtainRowDatabaseFields = (file: TFile, columns: TableColumn[]): RowDatabaseFields => { + const columnsToPersist = columns.filter(c => c.skipPersist); + const currentFileFields = getDataviewAPI().page(file.path); + const filteredFields: RowDatabaseFields = { frontmatter: {}, inline: {} }; + + columnsToPersist.forEach(column => { + let fieldValue = currentFileFields[column.key]; + if (fieldValue === undefined) { + fieldValue = ''; + } + if (column.isInline) { + filteredFields.inline[column.key] = fieldValue; + } else { + filteredFields.frontmatter[column.key] = fieldValue; + } + }); + return filteredFields; +}; + +export default obtainRowDatabaseFields; \ No newline at end of file diff --git a/src/parsers/RowDatabaseFieldsToFile.ts b/src/parsers/RowDatabaseFieldsToFile.ts new file mode 100644 index 00000000..fd9d758c --- /dev/null +++ b/src/parsers/RowDatabaseFieldsToFile.ts @@ -0,0 +1,9 @@ +export const parseFrontmatterFieldsToString = (frontmatterFields: Record): string => { + const array: string[] = []; + array.push(`---`); + Object.keys(frontmatterFields).forEach(key => { + array.push(`${key}: ${frontmatterFields[key]}`); + }); + array.push(`---`); + return array.join('\n'); +} \ No newline at end of file diff --git a/src/services/DatabaseInfo.ts b/src/services/DatabaseInfo.ts index c8fced99..aede01c8 100644 --- a/src/services/DatabaseInfo.ts +++ b/src/services/DatabaseInfo.ts @@ -6,7 +6,7 @@ import { import { LOGGER } from 'services/Logger'; import { VaultManagerDB } from 'services/FileManagerService'; import DatabaseYamlToStringParser from 'parsers/DatabaseYamlToStringParser'; -import { NoteContentAction, RowDataType } from 'cdm/FolderModel'; +import { NoteContentAction, RowDataType, TableDataType } from 'cdm/FolderModel'; import { LocalSettings } from 'Settings'; import { isDatabaseNote, updateRowFile } from 'helpers/VaultManagement'; import { UpdateRowOptions } from 'helpers/Constants'; @@ -89,21 +89,22 @@ export default class DatabaseInfo { * @param columnId * @param properties */ - async updateColumnProperties

(columnId: string, properties: Record, rows?: RowDataType[]): Promise { + async updateColumnProperties

(columnId: string, properties: Record, state?: TableDataType): Promise { const colToUpdate = this.yaml.columns[columnId]; const currentKey = colToUpdate.key; for (const key in properties) { colToUpdate[key] = properties[key]; } this.yaml.columns[columnId] = colToUpdate; - if (rows) { + if (state !== undefined) { await this.updateColumnKey(columnId, colToUpdate.accessor); // Once the column is updated, update the rows in case the key is changed - await Promise.all(rows.map(async (row: RowDataType) => { + await Promise.all(state.data.map(async (row: RowDataType) => { updateRowFile( row.note.getFile(), currentKey, colToUpdate.key, + state, UpdateRowOptions.COLUMN_KEY ); })); diff --git a/src/services/DataviewService.ts b/src/services/DataviewService.ts new file mode 100644 index 00000000..e9e24356 --- /dev/null +++ b/src/services/DataviewService.ts @@ -0,0 +1,17 @@ +import { Notice } from "obsidian"; +import { getAPI } from "obsidian-dataview"; +import { DvAPIInterface } from "obsidian-dataview/lib/typings/api"; + +/** + * Check if dataview plugin is installed + * @returns true if installed, false otherwise + * @throws Error if plugin is not installed + */ +export function getDataviewAPI(): DvAPIInterface { + if (getAPI()) { + return getAPI(app); + } else { + new Notice(`Dataview plugin is not installed. Please install it to load Databases.`); + throw new Error('Dataview plugin is not installed'); + } +} \ No newline at end of file From 7d3ffd3853dfdd726a0f31c74ce55ec56a5c70df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Fri, 6 May 2022 17:19:06 +0200 Subject: [PATCH 09/10] refactor working --- src/components/reducers/DatabaseDispatch.tsx | 9 ++-- src/helpers/VaultManagement.ts | 48 ++++++-------------- src/parsers/FileToRowDatabaseFields.ts | 7 +-- src/parsers/RowDatabaseFieldsToFile.ts | 14 +++++- 4 files changed, 33 insertions(+), 45 deletions(-) diff --git a/src/components/reducers/DatabaseDispatch.tsx b/src/components/reducers/DatabaseDispatch.tsx index dc5d0ab7..a01a87c2 100644 --- a/src/components/reducers/DatabaseDispatch.tsx +++ b/src/components/reducers/DatabaseDispatch.tsx @@ -10,15 +10,12 @@ import { TableColumn, TableDataType, RowDataType } from "cdm/FolderModel"; import { LOGGER } from "services/Logger"; import { ActionType } from "react-table"; import { VaultManagerDB } from "services/FileManagerService"; -import { - adapterRowToDatabaseYaml, - moveFile, - updateRowFile, -} from "helpers/VaultManagement"; +import { moveFile, updateRowFile } from "helpers/VaultManagement"; import { randomColor } from "helpers/Colors"; import { DatabaseColumn } from "cdm/DatabaseModel"; import NoteInfo from "services/NoteInfo"; import { dbTrim } from "helpers/StylesHelper"; +import { parseFrontmatterFieldsToString } from "parsers/RowDatabaseFieldsToFile"; export function databaseReducer(state: TableDataType, action: ActionType) { LOGGER.debug( @@ -67,7 +64,7 @@ export function databaseReducer(state: TableDataType, action: ActionType) { VaultManagerDB.create_markdown_file( state.view.file.parent, action.filename, - adapterRowToDatabaseYaml(rowRecord) + parseFrontmatterFieldsToString(rowRecord) ); const row: RowDataType = { diff --git a/src/helpers/VaultManagement.ts b/src/helpers/VaultManagement.ts index b7ced063..ae799130 100644 --- a/src/helpers/VaultManagement.ts +++ b/src/helpers/VaultManagement.ts @@ -75,17 +75,6 @@ export async function adapterTFilesToRows(folderPath: string): Promise { - const [key, value] = entry; - yaml.push(`${key}: ${value ?? ''}`); - }); - yaml.push('---'); - return yaml.join('\n'); -} - /** * Modify the file asociated to the row in function of input options * @param asociatedCFilePathToCell @@ -97,6 +86,8 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str LOGGER.info(`=>updateRowFile. file: ${file.path} | columnId: ${columnId} | newValue: ${newValue} | option: ${option}`); const rowFields = obtainRowDatabaseFields(file, state.columns); const content = await VaultManagerDB.obtainContentFromTfile(file); + const column = state.columns.find(c => c.key === columnId); + // Adds an empty frontmatter at the beginning of the file async function addFrontmatter(): Promise { /* Regex explanation @@ -115,45 +106,36 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str // Modify value of a column async function columnValue(): Promise { - const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); - + if (column.isInline) { + await inlineColumnEdit(); + return; + } rowFields.frontmatter[columnId] = newValue; - const noteObject = { - action: 'replace', - file: file, - regexp: frontmatterGroupRegex, - newValue: parseFrontmatterFieldsToString(rowFields.frontmatter) - }; - - await VaultManagerDB.editNoteContent(noteObject); + await persistFrontmatter(); } // Modify key of a column async function columnKey(): Promise { - const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); // Check if the column is already in the frontmatter // assign an empty value to the new key rowFields.frontmatter[newValue] = rowFields.frontmatter[columnId] ?? ""; delete rowFields.frontmatter[columnId]; - - const noteObject = { - action: 'replace', - file: file, - regexp: frontmatterGroupRegex, - newValue: parseFrontmatterFieldsToString(rowFields.frontmatter) - }; - await VaultManagerDB.editNoteContent(noteObject); + await persistFrontmatter(); } // Remove a column async function removeColumn(): Promise { - const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); delete rowFields.frontmatter[columnId]; + await persistFrontmatter(columnId); + } + + async function persistFrontmatter(deletedColumn?: string): Promise { + const frontmatterGroupRegex = new RegExp(`^---\\s+([\\w\\W]+?)\\s+---`, "g"); const noteObject = { action: 'replace', file: file, regexp: frontmatterGroupRegex, - newValue: parseFrontmatterFieldsToString(rowFields.frontmatter) + newValue: parseFrontmatterFieldsToString(rowFields.frontmatter, content, deletedColumn) }; await VaultManagerDB.editNoteContent(noteObject); } @@ -163,7 +145,7 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str * group 1 is inline field checking that starts in new line * group 2 is the current value of inline field */ - const frontmatterRegex = new RegExp(`(^${columnId}[:]{ 2}\\s) + ([\\w\\W] +? $)`, 'gm'); + const frontmatterRegex = new RegExp(`(^${columnId}[:]{2}\\s)+([\\w\\W]+?$)`, 'gm'); const noteObject = { action: 'replace', file: file, diff --git a/src/parsers/FileToRowDatabaseFields.ts b/src/parsers/FileToRowDatabaseFields.ts index 92dbfa8b..2306b849 100644 --- a/src/parsers/FileToRowDatabaseFields.ts +++ b/src/parsers/FileToRowDatabaseFields.ts @@ -5,15 +5,12 @@ import { getDataviewAPI } from "services/DataviewService"; const obtainRowDatabaseFields = (file: TFile, columns: TableColumn[]): RowDatabaseFields => { - const columnsToPersist = columns.filter(c => c.skipPersist); + const columnsToPersist = columns.filter(c => !c.isMetadata); const currentFileFields = getDataviewAPI().page(file.path); const filteredFields: RowDatabaseFields = { frontmatter: {}, inline: {} }; columnsToPersist.forEach(column => { - let fieldValue = currentFileFields[column.key]; - if (fieldValue === undefined) { - fieldValue = ''; - } + const fieldValue = currentFileFields[column.key] ?? ""; if (column.isInline) { filteredFields.inline[column.key] = fieldValue; } else { diff --git a/src/parsers/RowDatabaseFieldsToFile.ts b/src/parsers/RowDatabaseFieldsToFile.ts index fd9d758c..31d2964a 100644 --- a/src/parsers/RowDatabaseFieldsToFile.ts +++ b/src/parsers/RowDatabaseFieldsToFile.ts @@ -1,9 +1,21 @@ -export const parseFrontmatterFieldsToString = (frontmatterFields: Record): string => { +import { parseYaml } from "obsidian"; +export const parseFrontmatterFieldsToString = (frontmatterFields: Record, original?: string, deletedColumn?: string): string => { + const match = original.match(/^---\s+([\w\W]+?)\s+---/); + const array: string[] = []; array.push(`---`); Object.keys(frontmatterFields).forEach(key => { array.push(`${key}: ${frontmatterFields[key]}`); }); + if (match) { + const frontmatterRaw = match[1]; + const yaml = parseYaml(frontmatterRaw); + Object.keys(yaml).forEach(key => { + if (!frontmatterFields[key] && key !== deletedColumn) { + array.push(`${key}: ${yaml[key]}`); + } + }); + } array.push(`---`); return array.join('\n'); } \ No newline at end of file From ebd28555fafef8fe9771063ff655af23f8c27151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20G=C3=B3mez=20Bermejo?= Date: Fri, 6 May 2022 18:23:48 +0200 Subject: [PATCH 10/10] add inline field works! --- src/helpers/VaultManagement.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/helpers/VaultManagement.ts b/src/helpers/VaultManagement.ts index ae799130..48d8afc2 100644 --- a/src/helpers/VaultManagement.ts +++ b/src/helpers/VaultManagement.ts @@ -146,6 +146,10 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str * group 2 is the current value of inline field */ const frontmatterRegex = new RegExp(`(^${columnId}[:]{2}\\s)+([\\w\\W]+?$)`, 'gm'); + if (!frontmatterRegex.test(content)) { + await addInlineColumn(); + return; + } const noteObject = { action: 'replace', file: file, @@ -154,6 +158,17 @@ export async function updateRowFile(file: TFile, columnId: string, newValue: str }; await VaultManagerDB.editNoteContent(noteObject); } + + async function addInlineColumn(): Promise { + const inlineAddRegex = new RegExp(`(^---\\s+[\\w\\W]+?\\s+---\\s)+(.[\\w\\W]+)`, 'g'); + const noteObject = { + action: 'replace', + file: file, + regexp: inlineAddRegex, + newValue: `$1${columnId}:: ${newValue}\n$2` + }; + await VaultManagerDB.editNoteContent(noteObject); + } // Record of options const updateOptions: Record = {}; updateOptions[UpdateRowOptions.COLUMN_VALUE] = columnValue;