From 460c48937f9d2583d43a26398e2b94eff15f0ef8 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Tue, 14 Nov 2023 17:30:39 +0800 Subject: [PATCH] fix: lint code in CI (#161) * fix: lint code in CI * fix github ci * add eslint dependancy --- .editorconfig | 4 + .eslintignore | 4 +- .eslintrc | 44 ++- .github/workflows/test.yml | 47 +-- .prettierrc | 4 +- package.json | 4 + src/__mocks__/obsidian.ts | 12 +- src/__tests__/formatDate.spec.ts | 144 ++++---- src/__tests__/path_validation.spec.ts | 132 ++++---- src/api.ts | 20 +- src/main.ts | 460 +++++++++++++------------- src/settings/file-suggest.ts | 46 +-- src/settings/index.ts | 120 +++---- src/settings/suggest.ts | 198 +++++------ src/settings/template.ts | 240 +++++++------- src/util.ts | 138 ++++---- yarn.lock | 356 +++++++++++++++++++- 17 files changed, 1162 insertions(+), 811 deletions(-) diff --git a/.editorconfig b/.editorconfig index 29f5cb3..24155f6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,10 @@ indent_style = tab indent_size = 4 tab_width = 4 +[*.yml] +indent_style = space +indent_size = 2 + # 2 space indentation [*.{ts,js}] indent_style = space diff --git a/.eslintignore b/.eslintignore index 32909b2..dd87e2d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ -npm node_modules -build \ No newline at end of file +node_modules +build diff --git a/.eslintrc b/.eslintrc index 766e9cf..58a795d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,24 +1,22 @@ { - "root": true, - "parser": "@typescript-eslint/parser", - "env": { "node": true }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ], - "parserOptions": { - "sourceType": "module" - }, - "rules": { - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], - "@typescript-eslint/ban-ts-comment": "off", - "no-prototype-builtins": "off", - "@typescript-eslint/no-empty-function": "off", - "semi": ["error", "always"] - } - } + "root": true, + "parser": "@typescript-eslint/parser", + "env": { "node": true }, + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], + "@typescript-eslint/ban-ts-comment": "off", + "no-prototype-builtins": "off", + "@typescript-eslint/no-empty-function": "off", + "semi": [2, "never"] + } +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22a15f8..7d3e8f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,28 +5,29 @@ name: Node.js CI on: - push: - branches: [ master, main, develop ] - pull_request: - branches: [ master, main, develop ] + push: + branches: [ master, main, develop ] + pull_request: + branches: [ master, main, develop ] jobs: - build: - runs-on: ${{ matrix.os }} - continue-on-error: true - strategy: - matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - node-version: [ 14.x, 16.x, 18.x, 19.x ] - max-parallel: 24 - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - run: yarn - # before the first 'run' each "run" is a script from your project: - # - run: npm run prettier - # - run: npm run linter - - run: yarn test + build: + runs-on: ${{ matrix.os }} + continue-on-error: true + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + node-version: [ 14.x, 16.x, 18.x, 19.x ] + max-parallel: 24 + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: yarn + - name: Lint + run: yarn lint + - name: Test + run: yarn test diff --git a/.prettierrc b/.prettierrc index 8d95c2d..1ccc975 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,4 @@ { - "semi": true, - "singleQuote": false + "semi": false, + "singleQuote": true } diff --git a/package.json b/package.json index 58649d8..82e7a42 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "dev": "node esbuild.config.mjs", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "version": "node version-bump.mjs && git add manifest.json versions.json", + "lint": "eslint ./src --ext .ts", + "lint:fix": "eslint ./src --ext .ts --fix", + "format": "prettier --write ./src/**/*.ts", "test": "jest", "test:watch": "jest --watch", "test:ci": "jest --ci --reporters='default' --reporters='./github-actions-reporter'", @@ -33,6 +36,7 @@ "@typescript-eslint/parser": "5.29.0", "builtin-modules": "3.3.0", "esbuild": "0.14.47", + "eslint": "^8.53.0", "jest": "^29.4.3", "jest-cli": "^29.4.3", "jest-junit-reporter": "^1.1.0", diff --git a/src/__mocks__/obsidian.ts b/src/__mocks__/obsidian.ts index a1ae754..8a3460f 100644 --- a/src/__mocks__/obsidian.ts +++ b/src/__mocks__/obsidian.ts @@ -1,7 +1,7 @@ export const mockObsidianApp = { // Mock implementation of the App API app: { - platform: () => "desktop", + platform: () => 'desktop', plugins: { getPlugins: () => [], isEnabled: () => true, @@ -11,7 +11,7 @@ export const mockObsidianApp = { // Mock implementation of the Workspace API workspace: { onLayoutReady: (callback: () => void) => { - setTimeout(callback, 0); + setTimeout(callback, 0) }, getLeavesOfType: () => [], getConfig: () => ({}), @@ -19,12 +19,12 @@ export const mockObsidianApp = { // Mock implementation of the MarkdownView API markdownView: { - getMode: () => "source", - getMarkdown: () => "", + getMode: () => 'source', + getMarkdown: () => '', }, -}; +} // Mock implementation of the Obsidian global object export const obsidian = { ...mockObsidianApp, -}; +} diff --git a/src/__tests__/formatDate.spec.ts b/src/__tests__/formatDate.spec.ts index 1090f53..22525aa 100644 --- a/src/__tests__/formatDate.spec.ts +++ b/src/__tests__/formatDate.spec.ts @@ -1,70 +1,70 @@ -import { DateTime } from "luxon"; -import { DEFAULT_SETTINGS } from "../settings"; -import { formatDate } from "../util"; +import { DateTime } from 'luxon' +import { DEFAULT_SETTINGS } from '../settings' +import { formatDate } from '../util' -const jsDate = new Date("2023-02-18 13:02:08.169"); -const apiDate = jsDate.toISOString(); // API returns ISO 8601 date strings +const jsDate = new Date('2023-02-18 13:02:08.169') +const apiDate = jsDate.toISOString() // API returns ISO 8601 date strings type testCase = { - format: string; - date: string; - expected: string; -}; + format: string + date: string + expected: string +} const luxonHierarchicalFormatWithTime = { date: apiDate, - expected: "2023/2023-02/2023-02-18/130208", - format: "yyyy/yyyy-MM/yyyy-MM-dd/HHmmss", -}; + expected: '2023/2023-02/2023-02-18/130208', + format: 'yyyy/yyyy-MM/yyyy-MM-dd/HHmmss', +} const luxonHierarchicalFormat = { date: apiDate, - expected: "2023/2023-02/2023-02-18", - format: "yyyy/yyyy-MM/yyyy-MM-dd", -}; + expected: '2023/2023-02/2023-02-18', + format: 'yyyy/yyyy-MM/yyyy-MM-dd', +} const defaultDateHighlightedFormatTestCase: testCase = { date: apiDate, - expected: "2023-02-18 13:02:08", + expected: '2023-02-18 13:02:08', format: DEFAULT_SETTINGS.dateHighlightedFormat, -}; +} const defaultDateSavedFormatTestCase: testCase = { date: apiDate, - expected: "2023-02-18 13:02:08", + expected: '2023-02-18 13:02:08', format: DEFAULT_SETTINGS.dateSavedFormat, -}; +} const defaultFolderDateFormatTestCase: testCase = { date: apiDate, - expected: "2023-02-18", + expected: '2023-02-18', format: DEFAULT_SETTINGS.folderDateFormat, -}; +} const testCases: testCase[] = [ defaultDateHighlightedFormatTestCase, defaultDateSavedFormatTestCase, defaultFolderDateFormatTestCase, luxonHierarchicalFormat, luxonHierarchicalFormatWithTime, -]; -describe("ensure default formats are as expected", () => { - test("dateHighlightedFormat", () => { - expect(DEFAULT_SETTINGS.dateHighlightedFormat).toBe("yyyy-MM-dd HH:mm:ss"); - }); - test("dateSavedFormat", () => { - expect(DEFAULT_SETTINGS.dateSavedFormat).toBe("yyyy-MM-dd HH:mm:ss"); - }); - test("folderDateFormat", () => { - expect(DEFAULT_SETTINGS.folderDateFormat).toBe("yyyy-MM-dd"); - }); -}); +] +describe('ensure default formats are as expected', () => { + test('dateHighlightedFormat', () => { + expect(DEFAULT_SETTINGS.dateHighlightedFormat).toBe('yyyy-MM-dd HH:mm:ss') + }) + test('dateSavedFormat', () => { + expect(DEFAULT_SETTINGS.dateSavedFormat).toBe('yyyy-MM-dd HH:mm:ss') + }) + test('folderDateFormat', () => { + expect(DEFAULT_SETTINGS.folderDateFormat).toBe('yyyy-MM-dd') + }) +}) -describe("formatDate on known formats", () => { - test.each(testCases)("should correctly format %s", (testCase) => { - const result = formatDate(testCase.date, testCase.format); - expect(result).toBe(testCase.expected); - }); -}); +describe('formatDate on known formats', () => { + test.each(testCases)('should correctly format %s', (testCase) => { + const result = formatDate(testCase.date, testCase.format) + expect(result).toBe(testCase.expected) + }) +}) function generateRandomISODateStrings(quantity: number): string[] { - const randomISODateStrings: string[] = []; - const timeZones = Intl.DateTimeFormat().resolvedOptions().timeZone.split(","); + const randomISODateStrings: string[] = [] + const timeZones = Intl.DateTimeFormat().resolvedOptions().timeZone.split(',') for (let i = 0; i < quantity; i++) { const date = new Date( @@ -77,80 +77,80 @@ function generateRandomISODateStrings(quantity: number): string[] { Math.floor(Math.random() * 60), Math.floor(Math.random() * 1000) ) - ); + ) // Randomly select a timezone from the available time zones const randomTimeZone = - timeZones[Math.floor(Math.random() * timeZones.length)]; + timeZones[Math.floor(Math.random() * timeZones.length)] // Convert the generated date to the randomly selected timezone // const dateTimeWithZone = DateTime.fromJSDate(date, { zone: randomTimeZone }).toUTC(); const jsDateTimeWithZone = new Date( - date.toLocaleString("en-US", { timeZone: randomTimeZone }) - ); - const luxonDate = DateTime.fromJSDate(jsDateTimeWithZone); - randomISODateStrings.push(luxonDate.toISO() as string); + date.toLocaleString('en-US', { timeZone: randomTimeZone }) + ) + const luxonDate = DateTime.fromJSDate(jsDateTimeWithZone) + randomISODateStrings.push(luxonDate.toISO() as string) } - return randomISODateStrings; + return randomISODateStrings } -describe("formatDate on random dates", () => { +describe('formatDate on random dates', () => { test.each(generateRandomISODateStrings(100))( - "should correctly format %s", + 'should correctly format %s', (date) => { - const result = formatDate(date, "yyyy-MM-dd HH:mm:ss"); + const result = formatDate(date, 'yyyy-MM-dd HH:mm:ss') // test with regex to ensure the format is correct - expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/); + expect(result).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/) } - ); -}); + ) +}) function getCasesWithRandomDates( testFormats: string[], quantity = 10 ): { - date: string; - luxonFormat: string; + date: string + luxonFormat: string }[] { return testFormats.flatMap((luxonFormat) => generateRandomISODateStrings(quantity).map((date) => ({ date, luxonFormat, })) - ); + ) } -describe("round trip on random dates", () => { +describe('round trip on random dates', () => { const testFormats = [ defaultDateHighlightedFormatTestCase.format, defaultDateSavedFormatTestCase.format, defaultFolderDateFormatTestCase.format, - ]; + ] // generate permutations of testCases.formats and 10 generated each - const casesWithRandomDates = getCasesWithRandomDates(testFormats); + const casesWithRandomDates = getCasesWithRandomDates(testFormats) test.each(casesWithRandomDates)( - "should be unchanged after round trip %s", + 'should be unchanged after round trip %s', (testCase) => { - const result = formatDate(testCase.date, testCase.luxonFormat); - const result2 = formatDate(result, testCase.luxonFormat); - expect(result2).toBe(result); + const result = formatDate(testCase.date, testCase.luxonFormat) + const result2 = formatDate(result, testCase.luxonFormat) + expect(result2).toBe(result) } - ); + ) const atypicalFormats = [ luxonHierarchicalFormat.format, luxonHierarchicalFormatWithTime.format, - ]; + ] test.each(getCasesWithRandomDates(atypicalFormats))( - "should be unchanged after round trip with atypical format %s", + 'should be unchanged after round trip with atypical format %s', (testCase) => { - const formattedDate = formatDate(testCase.date, testCase.luxonFormat); + const formattedDate = formatDate(testCase.date, testCase.luxonFormat) const parsedDate = DateTime.fromFormat( formattedDate, testCase.luxonFormat - ); - expect(parsedDate.isValid).toBe(true); + ) + expect(parsedDate.isValid).toBe(true) } - ); -}); + ) +}) diff --git a/src/__tests__/path_validation.spec.ts b/src/__tests__/path_validation.spec.ts index 87c8287..1454553 100644 --- a/src/__tests__/path_validation.spec.ts +++ b/src/__tests__/path_validation.spec.ts @@ -1,115 +1,115 @@ -import * as fs from "fs"; +import * as fs from 'fs' import { ILLEGAL_CHAR_REGEX, replaceIllegalChars, REPLACEMENT_CHAR, -} from "../util"; +} from '../util' const expectedManualIllegalChars: string[] = [ - "/", - "\\", - "?", - "*", - ":", - "|", + '/', + '\\', + '?', + '*', + ':', + '|', '"', - "<", - ">", - "\u0000", - "\u001F", -]; + '<', + '>', + '\u0000', + '\u001F', +] // ZERO WIDTH JOINER and SOFT HYPHEN -const expectedInvisibleChars: string[] = ["­", "‍"]; +const expectedInvisibleChars: string[] = ['­', '‍'] -describe("replaceIllegalChars() removes all expected characters", () => { +describe('replaceIllegalChars() removes all expected characters', () => { test.each(expectedManualIllegalChars)( 'Illegal character "%s" is removed', (character) => { - const input = `this${character}string`; - const output = replaceIllegalChars(input); - expect(output).not.toContain(character); + const input = `this${character}string` + const output = replaceIllegalChars(input) + expect(output).not.toContain(character) } - ); -}); + ) +}) -describe("replaceIllegalChars() function replaces illegal characters with replacement char", () => { +describe('replaceIllegalChars() function replaces illegal characters with replacement char', () => { test.each(expectedManualIllegalChars)( "Illegal character '%s' is replaced", (char) => { - const input = `this${char}string`; - const expectedOutput = `this${REPLACEMENT_CHAR}string`; - const output = replaceIllegalChars(input); - expect(output).toEqual(expectedOutput); + const input = `this${char}string` + const expectedOutput = `this${REPLACEMENT_CHAR}string` + const output = replaceIllegalChars(input) + expect(output).toEqual(expectedOutput) } - ); -}); + ) +}) -describe("replaceIllegalChars() function does not modify string without illegal characters", () => { - test.each(["this_is_a_valid_string", "this is a valid string"])( +describe('replaceIllegalChars() function does not modify string without illegal characters', () => { + test.each(['this_is_a_valid_string', 'this is a valid string'])( "String '%s' is not modified", (input) => { - const output = replaceIllegalChars(input); - expect(output).toEqual(input); + const output = replaceIllegalChars(input) + expect(output).toEqual(input) } - ); -}); + ) +}) -describe("replaceIllegalChars() function handles empty string", () => { - test("Empty string is not modified", () => { - const input = ""; - const output = replaceIllegalChars(input); - expect(output).toEqual(input); - }); -}); +describe('replaceIllegalChars() function handles empty string', () => { + test('Empty string is not modified', () => { + const input = '' + const output = replaceIllegalChars(input) + expect(output).toEqual(input) + }) +}) -describe("replaceIllegalChars() function replaces all occurrences of illegal characters", () => { +describe('replaceIllegalChars() function replaces all occurrences of illegal characters', () => { test.each(expectedManualIllegalChars)( "Illegal character '%s' is replaced", (char) => { - const input = `${char}foo${char}bar`; - const expectedOutput = `${REPLACEMENT_CHAR}foo${REPLACEMENT_CHAR}bar`; - const output = replaceIllegalChars(input); - expect(output).toEqual(expectedOutput); - expect(output.match(ILLEGAL_CHAR_REGEX)).toBeNull(); + const input = `${char}foo${char}bar` + const expectedOutput = `${REPLACEMENT_CHAR}foo${REPLACEMENT_CHAR}bar` + const output = replaceIllegalChars(input) + expect(output).toEqual(expectedOutput) + expect(output.match(ILLEGAL_CHAR_REGEX)).toBeNull() } - ); -}); + ) +}) -describe("file system behavior with non-alphanumeric characters not in the illegal character list", () => { +describe('file system behavior with non-alphanumeric characters not in the illegal character list', () => { const nonAlphanumericCharactersWithoutIllegal: string[] = Array.from( { length: 127 - 32 }, (_, i) => String.fromCharCode(i + 32) ) .filter((char) => !/^[a-zA-Z0-9]+$/.test(char)) - .map(replaceIllegalChars); + .map(replaceIllegalChars) test.each(nonAlphanumericCharactersWithoutIllegal)( "File system allows creation of file with character '%s'", (char) => { - const input = `test${char}test.txt`; + const input = `test${char}test.txt` // verify file does not already exist - expect(fs.existsSync(input)).toBe(false); - fs.writeFileSync(input, "test"); + expect(fs.existsSync(input)).toBe(false) + fs.writeFileSync(input, 'test') // verify the file exists - expect(fs.existsSync(input)).toBe(true); + expect(fs.existsSync(input)).toBe(true) // remove the file - fs.unlinkSync(input); + fs.unlinkSync(input) // verify the file has been deleted - expect(fs.existsSync(input)).toBe(false); + expect(fs.existsSync(input)).toBe(false) } - ); -}); + ) +}) -describe("replaceIllegalChars() function removes all occurrences of invisible characters", () => { +describe('replaceIllegalChars() function removes all occurrences of invisible characters', () => { test.each(expectedInvisibleChars)( "Invisible character '%s' is replaced", (char) => { - const input = `${char}foo${char}bar`; - const expectedOutput = "foobar"; - const output = replaceIllegalChars(input); - expect(output).toEqual(expectedOutput); - expect(output.match(ILLEGAL_CHAR_REGEX)).toBeNull(); + const input = `${char}foo${char}bar` + const expectedOutput = 'foobar' + const output = replaceIllegalChars(input) + expect(output).toEqual(expectedOutput) + expect(output.match(ILLEGAL_CHAR_REGEX)).toBeNull() } - ); -}); + ) +}) diff --git a/src/api.ts b/src/api.ts index 4ba915b..3d5ebf2 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,4 @@ -import { requestUrl } from "obsidian"; +import { requestUrl } from "obsidian" export interface SearchResponse { data: { @@ -84,7 +84,7 @@ const requestHeaders = (apiKey: string) => ({ "Content-Type": "application/json", authorization: apiKey, "X-OmnivoreClient": "obsidian-plugin", -}); +}) export const loadArticles = async ( endpoint: string, @@ -163,13 +163,13 @@ export const loadArticles = async ( }, }), method: "POST", - }); + }) - const jsonRes = res.json as SearchResponse; - const articles = jsonRes.data.search.edges.map((e) => e.node); + const jsonRes = res.json as SearchResponse + const articles = jsonRes.data.search.edges.map((e) => e.node) - return [articles, jsonRes.data.search.pageInfo.hasNextPage]; -}; + return [articles, jsonRes.data.search.pageInfo.hasNextPage] +} export const deleteArticleById = async (endpoint: string, apiKey: string, articleId: string) => { @@ -198,11 +198,11 @@ export const deleteArticleById = async (endpoint: string, apiKey: string, articl }, }), method: "POST", - }); + }) - const jsonRes = res.json as DeleteArticleResponse; + const jsonRes = res.json as DeleteArticleResponse if (jsonRes.data.setBookmarkArticle.bookmarkedArticle.id === articleId) { - return true; + return true } return false diff --git a/src/main.ts b/src/main.ts index e7f01c6..9348706 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { DateTime } from "luxon"; +import { DateTime } from "luxon" import { addIcon, App, @@ -11,22 +11,22 @@ import { stringifyYaml, TFile, TFolder, -} from "obsidian"; -import { Article, deleteArticleById, loadArticles, PageType } from "./api"; +} from "obsidian" +import { Article, deleteArticleById, loadArticles, PageType } from "./api" import { DEFAULT_SETTINGS, Filter, FRONT_MATTER_VARIABLES, HighlightOrder, OmnivoreSettings, -} from "./settings"; -import { FolderSuggest } from "./settings/file-suggest"; +} from "./settings" +import { FolderSuggest } from "./settings/file-suggest" import { preParseTemplate, renderArticleContnet, renderFilename, renderFolderName, -} from "./settings/template"; +} from "./settings/template" import { DATE_FORMAT, findFrontMatterIndex, @@ -35,42 +35,42 @@ import { parseFrontMatterFromContent, removeFrontMatterFromContent, replaceIllegalChars, -} from "./util"; +} from "./util" export default class OmnivorePlugin extends Plugin { - settings: OmnivoreSettings; + settings: OmnivoreSettings async onload() { - await this.loadSettings(); - await this.resetSyncingStateSetting(); + await this.loadSettings() + await this.resetSyncingStateSetting() // update version if needed - const latestVersion = this.manifest.version; - const currentVersion = this.settings.version; + const latestVersion = this.manifest.version + const currentVersion = this.settings.version if (latestVersion !== currentVersion) { - this.settings.version = latestVersion; - this.saveSettings(); + this.settings.version = latestVersion + this.saveSettings() // show release notes const releaseNotes = `Omnivore plugin is upgraded to ${latestVersion}. What's new: https://github.com/omnivore-app/obsidian-omnivore/blob/main/CHANGELOG.md - `; - new Notice(releaseNotes, 10000); + ` + new Notice(releaseNotes, 10000) } this.addCommand({ id: "sync", name: "Sync", callback: () => { - this.fetchOmnivore(); + this.fetchOmnivore() }, - }); + }) this.addCommand({ id: "deleteArticle", name: "Delete Current Article from Omnivore", callback: () => { - this.deleteCurrentArticle(this.app.workspace.getActiveFile()); + this.deleteCurrentArticle(this.app.workspace.getActiveFile()) } }) @@ -78,89 +78,89 @@ export default class OmnivorePlugin extends Plugin { id: "resync", name: "Resync all articles", callback: () => { - this.settings.syncAt = ""; - this.saveSettings(); - new Notice("Omnivore Last Sync reset"); - this.fetchOmnivore(); + this.settings.syncAt = "" + this.saveSettings() + new Notice("Omnivore Last Sync reset") + this.fetchOmnivore() }, - }); + }) - const iconId = "Omnivore"; + const iconId = "Omnivore" // add icon addIcon( iconId, `` - ); + ) // This creates an icon in the left ribbon. this.addRibbonIcon(iconId, iconId, async (evt: MouseEvent) => { // Called when the user clicks the icon. - await this.fetchOmnivore(); - }); + await this.fetchOmnivore() + }) // This adds a settings tab so the user can configure various aspects of the plugin - this.addSettingTab(new OmnivoreSettingTab(this.app, this)); + this.addSettingTab(new OmnivoreSettingTab(this.app, this)) - this.scheduleSync(); + this.scheduleSync() } onunload() {} async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) } async saveSettings() { - await this.saveData(this.settings); + await this.saveData(this.settings) } scheduleSync() { // clear previous interval if (this.settings.intervalId > 0) { - window.clearInterval(this.settings.intervalId); + window.clearInterval(this.settings.intervalId) } - const frequency = this.settings.frequency; + const frequency = this.settings.frequency if (frequency > 0) { // schedule new interval const intervalId = window.setInterval(async () => { - await this.fetchOmnivore(false); - }, frequency * 60 * 1000); + await this.fetchOmnivore(false) + }, frequency * 60 * 1000) // save new interval id - this.settings.intervalId = intervalId; - this.saveSettings(); + this.settings.intervalId = intervalId + this.saveSettings() // clear interval when plugin is unloaded - this.registerInterval(intervalId); + this.registerInterval(intervalId) } } async downloadFileAsAttachment(article: Article): Promise { // download pdf from the URL to the attachment folder - const url = article.url; + const url = article.url const response = await requestUrl({ url, contentType: "application/pdf", - }); + }) const folderName = normalizePath( renderFolderName( article, this.settings.attachmentFolder, this.settings.folderDateFormat ) - ); - const folder = app.vault.getAbstractFileByPath(folderName); + ) + const folder = app.vault.getAbstractFileByPath(folderName) if (!(folder instanceof TFolder)) { - await app.vault.createFolder(folderName); + await app.vault.createFolder(folderName) } - const fileName = normalizePath(`${folderName}/${article.id}.pdf`); - const file = app.vault.getAbstractFileByPath(fileName); + const fileName = normalizePath(`${folderName}/${article.id}.pdf`) + const file = app.vault.getAbstractFileByPath(fileName) if (!(file instanceof TFile)) { const newFile = await app.vault.createBinary( fileName, response.arrayBuffer - ); - return newFile.path; + ) + return newFile.path } - return file.path; + return file.path } async fetchOmnivore(manualSync = true) { @@ -177,38 +177,38 @@ export default class OmnivorePlugin extends Plugin { isSingleFile, frontMatterVariables, frontMatterTemplate, - } = this.settings; + } = this.settings if (syncing) { - new Notice("🐢 Already syncing ..."); - return; + new Notice("🐢 Already syncing ...") + return } if (!apiKey) { - new Notice("Missing Omnivore api key"); - return; + new Notice("Missing Omnivore api key") + return } - this.settings.syncing = true; - await this.saveSettings(); + this.settings.syncing = true + await this.saveSettings() try { - console.log(`obsidian-omnivore starting sync since: '${syncAt}'`); + console.log(`obsidian-omnivore starting sync since: '${syncAt}'`) - manualSync && new Notice("🚀 Fetching articles ..."); + manualSync && new Notice("🚀 Fetching articles ...") // pre-parse template - frontMatterTemplate && preParseTemplate(frontMatterTemplate); - const templateSpans = preParseTemplate(template); + frontMatterTemplate && preParseTemplate(frontMatterTemplate) + const templateSpans = preParseTemplate(template) // check if we need to include content or file attachment const includeContent = templateSpans.some( (templateSpan) => templateSpan[1] === "content" - ); + ) const includeFileAttachment = templateSpans.some( (templateSpan) => templateSpan[1] === "fileAttachment" - ); + ) - const size = 50; + const size = 50 for ( let hasNextPage = true, articles: Article[] = [], after = 0; hasNextPage; @@ -223,21 +223,21 @@ export default class OmnivorePlugin extends Plugin { getQueryFromFilter(filter, customQuery), includeContent, "highlightedMarkdown" - ); + ) for (const article of articles) { const folderName = normalizePath( renderFolderName(article, folder, this.settings.folderDateFormat) - ); + ) const omnivoreFolder = - this.app.vault.getAbstractFileByPath(folderName); + this.app.vault.getAbstractFileByPath(folderName) if (!(omnivoreFolder instanceof TFolder)) { - await this.app.vault.createFolder(folderName); + await this.app.vault.createFolder(folderName) } const fileAttachment = article.pageType === PageType.File && includeFileAttachment ? await this.downloadFileAsAttachment(article) - : undefined; + : undefined const content = await renderArticleContnet( article, template, @@ -248,139 +248,139 @@ export default class OmnivorePlugin extends Plugin { frontMatterVariables, frontMatterTemplate, fileAttachment - ); + ) // use the custom filename const customFilename = replaceIllegalChars( renderFilename(article, filename, this.settings.filenameDateFormat) - ); - const pageName = `${folderName}/${customFilename}.md`; - const normalizedPath = normalizePath(pageName); + ) + const pageName = `${folderName}/${customFilename}.md` + const normalizedPath = normalizePath(pageName) const omnivoreFile = - this.app.vault.getAbstractFileByPath(normalizedPath); + this.app.vault.getAbstractFileByPath(normalizedPath) if (omnivoreFile instanceof TFile) { // file exists, so we might need to update it if (isSingleFile) { // sync into a single file - const existingContent = await this.app.vault.read(omnivoreFile); + const existingContent = await this.app.vault.read(omnivoreFile) // we need to remove the front matter const contentWithoutFrontmatter = - removeFrontMatterFromContent(content); + removeFrontMatterFromContent(content) const existingContentWithoutFrontmatter = - removeFrontMatterFromContent(existingContent); + removeFrontMatterFromContent(existingContent) // get front matter from content let existingFrontMatter = - parseFrontMatterFromContent(existingContent) || []; + parseFrontMatterFromContent(existingContent) || [] if (!Array.isArray(existingFrontMatter)) { // convert front matter to array - existingFrontMatter = [existingFrontMatter]; + existingFrontMatter = [existingFrontMatter] } - const newFrontMatter = parseFrontMatterFromContent(content); + const newFrontMatter = parseFrontMatterFromContent(content) if ( !newFrontMatter || !Array.isArray(newFrontMatter) || newFrontMatter.length === 0 ) { - throw new Error("Front matter does not exist in the template"); + throw new Error("Front matter does not exist in the template") } - let newContentWithoutFrontMatter: string; + let newContentWithoutFrontMatter: string // find the front matter with the same id const frontMatterIdx = findFrontMatterIndex( existingFrontMatter, article.id - ); + ) if (frontMatterIdx >= 0) { // this article already exists in the file // we need to locate the article which is wrapped in comments // and replace the content - const sectionStart = `%%${article.id}_start%%`; - const sectionEnd = `%%${article.id}_end%%`; + const sectionStart = `%%${article.id}_start%%` + const sectionEnd = `%%${article.id}_end%%` const existingContentRegex = new RegExp( `${sectionStart}.*?${sectionEnd}`, "s" - ); + ) newContentWithoutFrontMatter = existingContentWithoutFrontmatter.replace( existingContentRegex, contentWithoutFrontmatter - ); + ) - existingFrontMatter[frontMatterIdx] = newFrontMatter[0]; + existingFrontMatter[frontMatterIdx] = newFrontMatter[0] } else { // this article doesn't exist in the file // prepend the article - newContentWithoutFrontMatter = `${contentWithoutFrontmatter}\n\n${existingContentWithoutFrontmatter}`; + newContentWithoutFrontMatter = `${contentWithoutFrontmatter}\n\n${existingContentWithoutFrontmatter}` // prepend new front matter which is an array - existingFrontMatter.unshift(newFrontMatter[0]); + existingFrontMatter.unshift(newFrontMatter[0]) } const newFrontMatterStr = `---\n${stringifyYaml( existingFrontMatter - )}---`; + )}---` await this.app.vault.modify( omnivoreFile, `${newFrontMatterStr}\n\n${newContentWithoutFrontMatter}` - ); - continue; + ) + continue } // sync into separate files await this.app.fileManager.processFrontMatter( omnivoreFile, async (frontMatter) => { - const id = frontMatter.id; + const id = frontMatter.id if (id && id !== article.id) { // this article has the same name but different id - const newPageName = `${folderName}/${customFilename}-${article.id}.md`; - const newNormalizedPath = normalizePath(newPageName); + const newPageName = `${folderName}/${customFilename}-${article.id}.md` + const newNormalizedPath = normalizePath(newPageName) const newOmnivoreFile = - this.app.vault.getAbstractFileByPath(newNormalizedPath); + this.app.vault.getAbstractFileByPath(newNormalizedPath) if (newOmnivoreFile instanceof TFile) { // a file with the same name and id already exists, so we need to update it const existingContent = await this.app.vault.read( newOmnivoreFile - ); + ) if (existingContent !== content) { - await this.app.vault.modify(newOmnivoreFile, content); + await this.app.vault.modify(newOmnivoreFile, content) } - return; + return } // a file with the same name but different id already exists, so we need to create it - await this.app.vault.create(newNormalizedPath, content); - return; + await this.app.vault.create(newNormalizedPath, content) + return } // a file with the same id already exists, so we might need to update it - const existingContent = await this.app.vault.read(omnivoreFile); + const existingContent = await this.app.vault.read(omnivoreFile) if (existingContent !== content) { - await this.app.vault.modify(omnivoreFile, content); + await this.app.vault.modify(omnivoreFile, content) } } - ); - continue; + ) + continue } // file doesn't exist, so we need to create it try { - await this.app.vault.create(normalizedPath, content); + await this.app.vault.create(normalizedPath, content) } catch (error) { if (error.toString().includes("File already exists")) { new Notice( `Skipping file creation: ${normalizedPath}. Please check if you have duplicated article titles and delete the file if needed.` - ); + ) } else { - throw error; + throw error } } } } - manualSync && new Notice("🔖 Articles fetched"); - this.settings.syncAt = DateTime.local().toFormat(DATE_FORMAT); + manualSync && new Notice("🔖 Articles fetched") + this.settings.syncAt = DateTime.local().toFormat(DATE_FORMAT) } catch (e) { - new Notice("Failed to fetch articles"); - console.error(e); + new Notice("Failed to fetch articles") + console.error(e) } finally { - this.settings.syncing = false; - await this.saveSettings(); + this.settings.syncing = false + await this.saveSettings() } } @@ -391,43 +391,43 @@ export default class OmnivorePlugin extends Plugin { //use frontmatter id to find the file const articleId = this.app.metadataCache.getFileCache(file)?.frontmatter?.id if (!articleId) { - new Notice("Failed to delete article: article id not found"); + new Notice("Failed to delete article: article id not found") } try{ const isDeleted = deleteArticleById(this.settings.endpoint, this.settings.apiKey, articleId) if(!isDeleted) { - new Notice("Failed to delete article in Omnivore"); + new Notice("Failed to delete article in Omnivore") } } catch (e) { - new Notice("Failed to delete article in Omnivore"); - console.error(e); + new Notice("Failed to delete article in Omnivore") + console.error(e) } await this.app.vault.delete(file) } private async resetSyncingStateSetting() { - this.settings.syncing = false; - this.settings.intervalId = 0; - await this.saveSettings(); + this.settings.syncing = false + this.settings.intervalId = 0 + await this.saveSettings() } } class OmnivoreSettingTab extends PluginSettingTab { - plugin: OmnivorePlugin; + plugin: OmnivorePlugin constructor(app: App, plugin: OmnivorePlugin) { - super(app, plugin); - this.plugin = plugin; + super(app, plugin) + this.plugin = plugin } display(): void { - const { containerEl } = this; + const { containerEl } = this - containerEl.empty(); + containerEl.empty() - containerEl.createEl("h2", { text: "Settings for Omnivore plugin" }); + containerEl.createEl("h2", { text: "Settings for Omnivore plugin" }) new Setting(containerEl) .setName("API Key") @@ -439,7 +439,7 @@ class OmnivoreSettingTab extends PluginSettingTab { text: "https://omnivore.app/settings/api", href: "https://omnivore.app/settings/api", }) - ); + ) }) ) .addText((text) => @@ -447,23 +447,23 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("Enter your Omnivore Api Key") .setValue(this.plugin.settings.apiKey) .onChange(async (value) => { - this.plugin.settings.apiKey = value; - await this.plugin.saveSettings(); + this.plugin.settings.apiKey = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Filter") .setDesc("Select an Omnivore search filter type") .addDropdown((dropdown) => { - dropdown.addOptions(Filter); + dropdown.addOptions(Filter) dropdown .setValue(this.plugin.settings.filter) .onChange(async (value) => { - this.plugin.settings.filter = value; - await this.plugin.saveSettings(); - }); - }); + this.plugin.settings.filter = value + await this.plugin.saveSettings() + }) + }) new Setting(containerEl) .setName("Custom query") @@ -476,7 +476,7 @@ class OmnivoreSettingTab extends PluginSettingTab { href: "https://docs.omnivore.app/using/search", }), " for more info on search query syntax. Make sure your Filter (in the section above) is set to advanced when using a custom query." - ); + ) }) ) .addText((text) => @@ -486,10 +486,10 @@ class OmnivoreSettingTab extends PluginSettingTab { ) .setValue(this.plugin.settings.customQuery) .onChange(async (value) => { - this.plugin.settings.customQuery = value; - await this.plugin.saveSettings(); + this.plugin.settings.customQuery = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Last Sync") @@ -500,23 +500,23 @@ class OmnivoreSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.syncAt) .setDefaultFormat("yyyy-MM-dd'T'HH:mm:ss") .onChange(async (value) => { - this.plugin.settings.syncAt = value; - await this.plugin.saveSettings(); + this.plugin.settings.syncAt = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Highlight Order") .setDesc("Select the order in which highlights are applied") .addDropdown((dropdown) => { - dropdown.addOptions(HighlightOrder); + dropdown.addOptions(HighlightOrder) dropdown .setValue(this.plugin.settings.highlightOrder) .onChange(async (value) => { - this.plugin.settings.highlightOrder = value; - await this.plugin.saveSettings(); - }); - }); + this.plugin.settings.highlightOrder = value + await this.plugin.saveSettings() + }) + }) new Setting(containerEl) .setName("Front Matter") @@ -534,7 +534,7 @@ class OmnivoreSettingTab extends PluginSettingTab { fragment.createEl("br"), fragment.createEl("br"), "If you want to use a custom front matter template, you can enter it below under the advanced settings" - ); + ) }) ) .addTextArea((text) => { @@ -550,12 +550,12 @@ class OmnivoreSettingTab extends PluginSettingTab { (v, i, a) => FRONT_MATTER_VARIABLES.includes(v.split("::")[0]) && a.indexOf(v) === i - ); - await this.plugin.saveSettings(); - }); - text.inputEl.setAttr("rows", 4); - text.inputEl.setAttr("cols", 30); - }); + ) + await this.plugin.saveSettings() + }) + text.inputEl.setAttr("rows", 4) + text.inputEl.setAttr("cols", 30) + }) new Setting(containerEl) .setName("Article Template") @@ -570,7 +570,7 @@ class OmnivoreSettingTab extends PluginSettingTab { fragment.createEl("br"), fragment.createEl("br"), "If you want to use a custom front matter template, you can enter it below under the advanced settings" - ); + ) }) ) .addTextArea((text) => { @@ -581,11 +581,11 @@ class OmnivoreSettingTab extends PluginSettingTab { // if template is empty, use default template this.plugin.settings.template = value ? value - : DEFAULT_SETTINGS.template; - await this.plugin.saveSettings(); - }); - text.inputEl.setAttr("rows", 25); - text.inputEl.setAttr("cols", 50); + : DEFAULT_SETTINGS.template + await this.plugin.saveSettings() + }) + text.inputEl.setAttr("rows", 25) + text.inputEl.setAttr("cols", 50) }) .addExtraButton((button) => { // add a button to reset template @@ -593,12 +593,12 @@ class OmnivoreSettingTab extends PluginSettingTab { .setIcon("reset") .setTooltip("Reset template") .onClick(async () => { - this.plugin.settings.template = DEFAULT_SETTINGS.template; - await this.plugin.saveSettings(); - this.display(); - new Notice("Template reset"); - }); - }); + this.plugin.settings.template = DEFAULT_SETTINGS.template + await this.plugin.saveSettings() + this.display() + new Notice("Template reset") + }) + }) new Setting(containerEl) .setName("Frequency") @@ -611,18 +611,18 @@ class OmnivoreSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.frequency.toString()) .onChange(async (value) => { // validate frequency - const frequency = parseInt(value); + const frequency = parseInt(value) if (isNaN(frequency)) { - new Notice("Frequency must be a positive integer"); - return; + new Notice("Frequency must be a positive integer") + return } // save frequency - this.plugin.settings.frequency = frequency; - await this.plugin.saveSettings(); + this.plugin.settings.frequency = frequency + await this.plugin.saveSettings() - this.plugin.scheduleSync(); + this.plugin.scheduleSync() }) - ); + ) new Setting(containerEl) .setName("Folder") @@ -630,30 +630,30 @@ class OmnivoreSettingTab extends PluginSettingTab { "Enter the folder where the data will be stored. {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the folder name" ) .addSearch((search) => { - new FolderSuggest(this.app, search.inputEl); + new FolderSuggest(this.app, search.inputEl) search .setPlaceholder("Enter the folder") .setValue(this.plugin.settings.folder) .onChange(async (value) => { - this.plugin.settings.folder = value; - await this.plugin.saveSettings(); - }); - }); + this.plugin.settings.folder = value + await this.plugin.saveSettings() + }) + }) new Setting(containerEl) .setName("Attachment Folder") .setDesc( "Enter the folder where the attachment will be downloaded to. {{{title}}}, {{{dateSaved}}} and {{{datePublished}}} could be used in the folder name" ) .addSearch((search) => { - new FolderSuggest(this.app, search.inputEl); + new FolderSuggest(this.app, search.inputEl) search .setPlaceholder("Enter the attachment folder") .setValue(this.plugin.settings.attachmentFolder) .onChange(async (value) => { - this.plugin.settings.attachmentFolder = value; - await this.plugin.saveSettings(); - }); - }); + this.plugin.settings.attachmentFolder = value + await this.plugin.saveSettings() + }) + }) new Setting(containerEl) .setName("Is Single File") @@ -664,10 +664,10 @@ class OmnivoreSettingTab extends PluginSettingTab { toggle .setValue(this.plugin.settings.isSingleFile) .onChange(async (value) => { - this.plugin.settings.isSingleFile = value; - await this.plugin.saveSettings(); + this.plugin.settings.isSingleFile = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Filename") @@ -679,10 +679,10 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("Enter the filename") .setValue(this.plugin.settings.filename) .onChange(async (value) => { - this.plugin.settings.filename = value; - await this.plugin.saveSettings(); + this.plugin.settings.filename = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Filename Date Format") @@ -694,7 +694,7 @@ class OmnivoreSettingTab extends PluginSettingTab { text: "reference", href: "https://moment.github.io/luxon/#/formatting?id=table-of-tokens", }) - ); + ) }) ) .addText((text) => @@ -702,10 +702,10 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("yyyy-MM-dd") .setValue(this.plugin.settings.filenameDateFormat) .onChange(async (value) => { - this.plugin.settings.filenameDateFormat = value; - await this.plugin.saveSettings(); + this.plugin.settings.filenameDateFormat = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Folder Date Format") @@ -717,7 +717,7 @@ class OmnivoreSettingTab extends PluginSettingTab { text: "reference", href: "https://moment.github.io/luxon/#/formatting?id=table-of-tokens", }) - ); + ) }) ) .addText((text) => @@ -725,10 +725,10 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("yyyy-MM-dd") .setValue(this.plugin.settings.folderDateFormat) .onChange(async (value) => { - this.plugin.settings.folderDateFormat = value; - await this.plugin.saveSettings(); + this.plugin.settings.folderDateFormat = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Date Saved Format") .setDesc( @@ -739,10 +739,10 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("yyyy-MM-dd'T'HH:mm:ss") .setValue(this.plugin.settings.dateSavedFormat) .onChange(async (value) => { - this.plugin.settings.dateSavedFormat = value; - await this.plugin.saveSettings(); + this.plugin.settings.dateSavedFormat = value + await this.plugin.saveSettings() }) - ); + ) new Setting(containerEl) .setName("Date Highlighted Format") .setDesc( @@ -753,19 +753,19 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("Date Highlighted Format") .setValue(this.plugin.settings.dateHighlightedFormat) .onChange(async (value) => { - this.plugin.settings.dateHighlightedFormat = value; - await this.plugin.saveSettings(); + this.plugin.settings.dateHighlightedFormat = value + await this.plugin.saveSettings() }) - ); + ) containerEl.createEl("h5", { cls: "omnivore-collapsible", text: "Advanced Settings", - }); + }) const advancedSettings = containerEl.createEl("div", { cls: "omnivore-content", - }); + }) new Setting(advancedSettings) .setName("API Endpoint") @@ -775,10 +775,10 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("API endpoint") .setValue(this.plugin.settings.endpoint) .onChange(async (value) => { - this.plugin.settings.endpoint = value; - await this.plugin.saveSettings(); + this.plugin.settings.endpoint = value + await this.plugin.saveSettings() }) - ); + ) new Setting(advancedSettings) .setName("Front Matter Template") @@ -796,7 +796,7 @@ class OmnivoreSettingTab extends PluginSettingTab { fragment.createEl("br"), fragment.createEl("br"), "If this template is set, it will override the Front Matter so please make sure your template is a valid YAML." - ); + ) }) ) .addTextArea((text) => { @@ -804,12 +804,12 @@ class OmnivoreSettingTab extends PluginSettingTab { .setPlaceholder("Enter the template") .setValue(this.plugin.settings.frontMatterTemplate) .onChange(async (value) => { - this.plugin.settings.frontMatterTemplate = value; - await this.plugin.saveSettings(); - }); + this.plugin.settings.frontMatterTemplate = value + await this.plugin.saveSettings() + }) - text.inputEl.setAttr("rows", 10); - text.inputEl.setAttr("cols", 30); + text.inputEl.setAttr("rows", 10) + text.inputEl.setAttr("cols", 30) }) .addExtraButton((button) => { // add a button to reset template @@ -818,30 +818,30 @@ class OmnivoreSettingTab extends PluginSettingTab { .setTooltip("Reset front matter template") .onClick(async () => { this.plugin.settings.frontMatterTemplate = - DEFAULT_SETTINGS.frontMatterTemplate; - await this.plugin.saveSettings(); - this.display(); - new Notice("Front matter template reset"); - }); - }); + DEFAULT_SETTINGS.frontMatterTemplate + await this.plugin.saveSettings() + this.display() + new Notice("Front matter template reset") + }) + }) - const help = containerEl.createEl("p"); - help.innerHTML = `For more information, please visit our GitHub page, email us at feedback@omnivore.app or join our Discord server.`; + const help = containerEl.createEl("p") + help.innerHTML = `For more information, please visit our GitHub page, email us at feedback@omnivore.app or join our Discord server.` // script to make collapsible sections - const coll = document.getElementsByClassName("omnivore-collapsible"); - let i; + const coll = document.getElementsByClassName("omnivore-collapsible") + let i for (i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function () { - this.classList.toggle("omnivore-active"); - const content = this.nextElementSibling; + this.classList.toggle("omnivore-active") + const content = this.nextElementSibling if (content.style.maxHeight) { - content.style.maxHeight = null; + content.style.maxHeight = null } else { - content.style.maxHeight = "fit-content"; + content.style.maxHeight = "fit-content" } - }); + }) } } } diff --git a/src/settings/file-suggest.ts b/src/settings/file-suggest.ts index b5b5463..b697e8a 100644 --- a/src/settings/file-suggest.ts +++ b/src/settings/file-suggest.ts @@ -1,64 +1,64 @@ // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes -import { TAbstractFile, TFile, TFolder } from "obsidian"; +import { TAbstractFile, TFile, TFolder } from 'obsidian' -import { TextInputSuggest } from "./suggest"; +import { TextInputSuggest } from './suggest' export class FileSuggest extends TextInputSuggest { getSuggestions(inputStr: string): TFile[] { - const abstractFiles = this.app.vault.getAllLoadedFiles(); - const files: TFile[] = []; - const lowerCaseInputStr = inputStr.toLowerCase(); + const abstractFiles = this.app.vault.getAllLoadedFiles() + const files: TFile[] = [] + const lowerCaseInputStr = inputStr.toLowerCase() abstractFiles.forEach((file: TAbstractFile) => { if ( file instanceof TFile && - file.extension === "md" && + file.extension === 'md' && file.path.toLowerCase().contains(lowerCaseInputStr) ) { - files.push(file); + files.push(file) } - }); + }) - return files; + return files } renderSuggestion(file: TFile, el: HTMLElement): void { - el.setText(file.path); + el.setText(file.path) } selectSuggestion(file: TFile): void { - this.inputEl.value = file.path; - this.inputEl.trigger("input"); - this.close(); + this.inputEl.value = file.path + this.inputEl.trigger('input') + this.close() } } export class FolderSuggest extends TextInputSuggest { getSuggestions(inputStr: string): TFolder[] { - const abstractFiles = this.app.vault.getAllLoadedFiles(); - const folders: TFolder[] = []; - const lowerCaseInputStr = inputStr.toLowerCase(); + const abstractFiles = this.app.vault.getAllLoadedFiles() + const folders: TFolder[] = [] + const lowerCaseInputStr = inputStr.toLowerCase() abstractFiles.forEach((folder: TAbstractFile) => { if ( folder instanceof TFolder && folder.path.toLowerCase().contains(lowerCaseInputStr) ) { - folders.push(folder); + folders.push(folder) } - }); + }) - return folders; + return folders } renderSuggestion(file: TFolder, el: HTMLElement): void { - el.setText(file.path); + el.setText(file.path) } selectSuggestion(file: TFolder): void { - this.inputEl.value = file.path; - this.inputEl.trigger("input"); - this.close(); + this.inputEl.value = file.path + this.inputEl.trigger('input') + this.close() } } diff --git a/src/settings/index.ts b/src/settings/index.ts index e7452c1..5e98d5e 100644 --- a/src/settings/index.ts +++ b/src/settings/index.ts @@ -1,79 +1,79 @@ -import { DEFAULT_TEMPLATE } from "./template"; +import { DEFAULT_TEMPLATE } from './template' export const FRONT_MATTER_VARIABLES = [ - "title", - "author", - "tags", - "date_saved", - "date_published", - "omnivore_url", - "site_name", - "original_url", - "description", - "note", - "type", - "date_read", - "words_count", - "read_length", - "state", - "date_archived", -]; + 'title', + 'author', + 'tags', + 'date_saved', + 'date_published', + 'omnivore_url', + 'site_name', + 'original_url', + 'description', + 'note', + 'type', + 'date_read', + 'words_count', + 'read_length', + 'state', + 'date_archived', +] export const DEFAULT_SETTINGS: OmnivoreSettings = { - dateHighlightedFormat: "yyyy-MM-dd HH:mm:ss", - dateSavedFormat: "yyyy-MM-dd HH:mm:ss", - apiKey: "", - filter: "HIGHLIGHTS", - syncAt: "", - customQuery: "", + dateHighlightedFormat: 'yyyy-MM-dd HH:mm:ss', + dateSavedFormat: 'yyyy-MM-dd HH:mm:ss', + apiKey: '', + filter: 'HIGHLIGHTS', + syncAt: '', + customQuery: '', template: DEFAULT_TEMPLATE, - highlightOrder: "LOCATION", + highlightOrder: 'LOCATION', syncing: false, - folder: "Omnivore/{{{date}}}", - folderDateFormat: "yyyy-MM-dd", - endpoint: "https://api-prod.omnivore.app/api/graphql", - filename: "{{{title}}}", - filenameDateFormat: "yyyy-MM-dd", - attachmentFolder: "Omnivore/attachments", - version: "0.0.0", + folder: 'Omnivore/{{{date}}}', + folderDateFormat: 'yyyy-MM-dd', + endpoint: 'https://api-prod.omnivore.app/api/graphql', + filename: '{{{title}}}', + filenameDateFormat: 'yyyy-MM-dd', + attachmentFolder: 'Omnivore/attachments', + version: '0.0.0', isSingleFile: false, frequency: 0, intervalId: 0, frontMatterVariables: [], - frontMatterTemplate: "", -}; + frontMatterTemplate: '', +} export enum Filter { - ALL = "import all my articles", - HIGHLIGHTS = "import just highlights", - ADVANCED = "advanced", + ALL = 'import all my articles', + HIGHLIGHTS = 'import just highlights', + ADVANCED = 'advanced', } export enum HighlightOrder { - LOCATION = "the location of highlights in the article", - TIME = "the time that highlights are updated", + LOCATION = 'the location of highlights in the article', + TIME = 'the time that highlights are updated', } export interface OmnivoreSettings { - apiKey: string; - filter: string; - syncAt: string; - customQuery: string; - highlightOrder: string; - template: string; - syncing: boolean; - folder: string; - folderDateFormat: string; - endpoint: string; - dateHighlightedFormat: string; - dateSavedFormat: string; - filename: string; - attachmentFolder: string; - version: string; - isSingleFile: boolean; - frequency: number; - intervalId: number; - frontMatterVariables: string[]; - frontMatterTemplate: string; - filenameDateFormat: string; + apiKey: string + filter: string + syncAt: string + customQuery: string + highlightOrder: string + template: string + syncing: boolean + folder: string + folderDateFormat: string + endpoint: string + dateHighlightedFormat: string + dateSavedFormat: string + filename: string + attachmentFolder: string + version: string + isSingleFile: boolean + frequency: number + intervalId: number + frontMatterVariables: string[] + frontMatterTemplate: string + filenameDateFormat: string } diff --git a/src/settings/suggest.ts b/src/settings/suggest.ts index fc0f29d..bceea2e 100644 --- a/src/settings/suggest.ts +++ b/src/settings/suggest.ts @@ -1,179 +1,187 @@ // Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes -import { createPopper, type Instance as PopperInstance } from "@popperjs/core"; -import { App, type ISuggestOwner, Scope } from "obsidian"; -import { wrapAround } from "../util"; +import { createPopper, type Instance as PopperInstance } from '@popperjs/core' +import { App, type ISuggestOwner, Scope } from 'obsidian' +import { wrapAround } from '../util' class Suggest { - private owner: ISuggestOwner; - private values: T[]; - private suggestions: HTMLDivElement[]; - private selectedItem: number; - private containerEl: HTMLElement; + private owner: ISuggestOwner + private values: T[] + private suggestions: HTMLDivElement[] + private selectedItem: number + private containerEl: HTMLElement constructor(owner: ISuggestOwner, containerEl: HTMLElement, scope: Scope) { - this.owner = owner; - this.containerEl = containerEl; + this.owner = owner + this.containerEl = containerEl - containerEl.on("click", ".suggestion-item", this.onSuggestionClick.bind(this)); containerEl.on( - "mousemove", - ".suggestion-item", + 'click', + '.suggestion-item', + this.onSuggestionClick.bind(this) + ) + containerEl.on( + 'mousemove', + '.suggestion-item', this.onSuggestionMouseover.bind(this) - ); + ) - scope.register([], "ArrowUp", (event) => { + scope.register([], 'ArrowUp', (event) => { if (!event.isComposing) { - this.setSelectedItem(this.selectedItem - 1, true); - return false; + this.setSelectedItem(this.selectedItem - 1, true) + return false } - }); + }) - scope.register([], "ArrowDown", (event) => { + scope.register([], 'ArrowDown', (event) => { if (!event.isComposing) { - this.setSelectedItem(this.selectedItem + 1, true); - return false; + this.setSelectedItem(this.selectedItem + 1, true) + return false } - }); + }) - scope.register([], "Enter", (event) => { + scope.register([], 'Enter', (event) => { if (!event.isComposing) { - this.useSelectedItem(event); - return false; + this.useSelectedItem(event) + return false } - }); + }) } onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void { - event.preventDefault(); + event.preventDefault() - const item = this.suggestions.indexOf(el); - this.setSelectedItem(item, false); - this.useSelectedItem(event); + const item = this.suggestions.indexOf(el) + this.setSelectedItem(item, false) + this.useSelectedItem(event) } onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void { - const item = this.suggestions.indexOf(el); - this.setSelectedItem(item, false); + const item = this.suggestions.indexOf(el) + this.setSelectedItem(item, false) } setSuggestions(values: T[]) { - this.containerEl.empty(); - const suggestionEls: HTMLDivElement[] = []; + this.containerEl.empty() + const suggestionEls: HTMLDivElement[] = [] values.forEach((value) => { - const suggestionEl = this.containerEl.createDiv("suggestion-item"); - this.owner.renderSuggestion(value, suggestionEl); - suggestionEls.push(suggestionEl); - }); - - this.values = values; - this.suggestions = suggestionEls; - this.setSelectedItem(0, false); + const suggestionEl = this.containerEl.createDiv('suggestion-item') + this.owner.renderSuggestion(value, suggestionEl) + suggestionEls.push(suggestionEl) + }) + + this.values = values + this.suggestions = suggestionEls + this.setSelectedItem(0, false) } useSelectedItem(event: MouseEvent | KeyboardEvent) { - const currentValue = this.values[this.selectedItem]; + const currentValue = this.values[this.selectedItem] if (currentValue) { - this.owner.selectSuggestion(currentValue, event); + this.owner.selectSuggestion(currentValue, event) } } setSelectedItem(selectedIndex: number, scrollIntoView: boolean) { - const normalizedIndex = wrapAround(selectedIndex, this.suggestions.length); - const prevSelectedSuggestion = this.suggestions[this.selectedItem]; - const selectedSuggestion = this.suggestions[normalizedIndex]; + const normalizedIndex = wrapAround(selectedIndex, this.suggestions.length) + const prevSelectedSuggestion = this.suggestions[this.selectedItem] + const selectedSuggestion = this.suggestions[normalizedIndex] - prevSelectedSuggestion?.removeClass("is-selected"); - selectedSuggestion?.addClass("is-selected"); + prevSelectedSuggestion?.removeClass('is-selected') + selectedSuggestion?.addClass('is-selected') - this.selectedItem = normalizedIndex; + this.selectedItem = normalizedIndex if (scrollIntoView) { - selectedSuggestion.scrollIntoView(false); + selectedSuggestion.scrollIntoView(false) } } } export abstract class TextInputSuggest implements ISuggestOwner { - protected app: App; - protected inputEl: HTMLInputElement; + protected app: App + protected inputEl: HTMLInputElement - private popper: PopperInstance; - private scope: Scope; - private suggestEl: HTMLElement; - private suggest: Suggest; + private popper: PopperInstance + private scope: Scope + private suggestEl: HTMLElement + private suggest: Suggest constructor(app: App, inputEl: HTMLInputElement) { - this.app = app; - this.inputEl = inputEl; - this.scope = new Scope(); - - this.suggestEl = createDiv("suggestion-container"); - const suggestion = this.suggestEl.createDiv("suggestion"); - this.suggest = new Suggest(this, suggestion, this.scope); - - this.scope.register([], "Escape", this.close.bind(this)); - - this.inputEl.addEventListener("input", this.onInputChanged.bind(this)); - this.inputEl.addEventListener("focus", this.onInputChanged.bind(this)); - this.inputEl.addEventListener("blur", this.close.bind(this)); - this.suggestEl.on("mousedown", ".suggestion-container", (event: MouseEvent) => { - event.preventDefault(); - }); + this.app = app + this.inputEl = inputEl + this.scope = new Scope() + + this.suggestEl = createDiv('suggestion-container') + const suggestion = this.suggestEl.createDiv('suggestion') + this.suggest = new Suggest(this, suggestion, this.scope) + + this.scope.register([], 'Escape', this.close.bind(this)) + + this.inputEl.addEventListener('input', this.onInputChanged.bind(this)) + this.inputEl.addEventListener('focus', this.onInputChanged.bind(this)) + this.inputEl.addEventListener('blur', this.close.bind(this)) + this.suggestEl.on( + 'mousedown', + '.suggestion-container', + (event: MouseEvent) => { + event.preventDefault() + } + ) } onInputChanged(): void { - const inputStr = this.inputEl.value; - const suggestions = this.getSuggestions(inputStr); + const inputStr = this.inputEl.value + const suggestions = this.getSuggestions(inputStr) if (suggestions.length > 0) { - this.suggest.setSuggestions(suggestions); + this.suggest.setSuggestions(suggestions) // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.open((this.app).dom.appContainerEl, this.inputEl); + this.open((this.app).dom.appContainerEl, this.inputEl) } } open(container: HTMLElement, inputEl: HTMLElement): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (this.app).keymap.pushScope(this.scope); + (this.app).keymap.pushScope(this.scope) - container.appendChild(this.suggestEl); + container.appendChild(this.suggestEl) this.popper = createPopper(inputEl, this.suggestEl, { - placement: "bottom-start", + placement: 'bottom-start', modifiers: [ { - name: "sameWidth", + name: 'sameWidth', enabled: true, fn: ({ state, instance }) => { // Note: positioning needs to be calculated twice - // first pass - positioning it according to the width of the popper // second pass - position it with the width bound to the reference element // we need to early exit to avoid an infinite loop - const targetWidth = `${state.rects.reference.width}px`; + const targetWidth = `${state.rects.reference.width}px` if (state.styles.popper.width === targetWidth) { - return; + return } - state.styles.popper.width = targetWidth; - instance.update(); + state.styles.popper.width = targetWidth + instance.update() }, - phase: "beforeWrite", - requires: ["computeStyles"], + phase: 'beforeWrite', + requires: ['computeStyles'], }, ], - }); + }) } close(): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any - (this.app).keymap.popScope(this.scope); + (this.app).keymap.popScope(this.scope) - this.suggest.setSuggestions([]); - this.popper.destroy(); - this.suggestEl.detach(); + this.suggest.setSuggestions([]) + this.popper.destroy() + this.suggestEl.detach() } - abstract getSuggestions(inputStr: string): T[]; - abstract renderSuggestion(item: T, el: HTMLElement): void; - abstract selectSuggestion(item: T): void; + abstract getSuggestions(inputStr: string): T[] + abstract renderSuggestion(item: T, el: HTMLElement): void + abstract selectSuggestion(item: T): void } diff --git a/src/settings/template.ts b/src/settings/template.ts index b3411d0..4b739ac 100644 --- a/src/settings/template.ts +++ b/src/settings/template.ts @@ -1,7 +1,7 @@ -import { truncate } from "lodash"; -import Mustache from "mustache"; -import { parseYaml, stringifyYaml } from "obsidian"; -import { Article, HighlightType, PageType } from "../api"; +import { truncate } from 'lodash' +import Mustache from 'mustache' +import { parseYaml, stringifyYaml } from 'obsidian' +import { Article, HighlightType, PageType } from '../api' import { compareHighlightsInFile, formatDate, @@ -10,14 +10,14 @@ import { removeFrontMatterFromContent, siteNameFromUrl, snakeToCamelCase, -} from "../util"; +} from '../util' type FunctionMap = { [key: string]: () => ( text: string, render: (text: string) => string - ) => string; -}; + ) => string +} export const DEFAULT_TEMPLATE = `# {{{title}}} #Omnivore @@ -36,99 +36,99 @@ export const DEFAULT_TEMPLATE = `# {{{title}}} {{/note}} {{/highlights}} -{{/highlights.length}}`; +{{/highlights.length}}` export interface LabelView { - name: string; + name: string } export interface HighlightView { - text: string; - highlightUrl: string; - highlightID: string; - dateHighlighted: string; - note?: string; - labels?: LabelView[]; - color: string; - positionPercent: number; - positionAnchorIndex: number; + text: string + highlightUrl: string + highlightID: string + dateHighlighted: string + note?: string + labels?: LabelView[] + color: string + positionPercent: number + positionAnchorIndex: number } export type ArticleView = | { - id: string; - title: string; - omnivoreUrl: string; - siteName: string; - originalUrl: string; - author?: string; - labels?: LabelView[]; - dateSaved: string; - highlights: HighlightView[]; - content?: string; - datePublished?: string; - fileAttachment?: string; - description?: string; - note?: string; - type: PageType; - dateRead?: string; - wordsCount?: number; - readLength?: number; - state: string; - dateArchived?: string; + id: string + title: string + omnivoreUrl: string + siteName: string + originalUrl: string + author?: string + labels?: LabelView[] + dateSaved: string + highlights: HighlightView[] + content?: string + datePublished?: string + fileAttachment?: string + description?: string + note?: string + type: PageType + dateRead?: string + wordsCount?: number + readLength?: number + state: string + dateArchived?: string } - | FunctionMap; + | FunctionMap enum ArticleState { - Inbox = "INBOX", - Reading = "READING", - Completed = "COMPLETED", - Archived = "ARCHIVED", + Inbox = 'INBOX', + Reading = 'READING', + Completed = 'COMPLETED', + Archived = 'ARCHIVED', } const getArticleState = (article: Article): string => { if (article.isArchived) { - return ArticleState.Archived; + return ArticleState.Archived } if (article.readingProgressPercent > 0) { return article.readingProgressPercent === 100 ? ArticleState.Completed - : ArticleState.Reading; + : ArticleState.Reading } - return ArticleState.Inbox; -}; + return ArticleState.Inbox +} function lowerCase() { return function (text: string, render: (text: string) => string) { - return render(text).toLowerCase(); - }; + return render(text).toLowerCase() + } } function upperCase() { return function (text: string, render: (text: string) => string) { - return render(text).toUpperCase(); - }; + return render(text).toUpperCase() + } } function upperCaseFirst() { return function (text: string, render: (text: string) => string) { - const str = render(text); - return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); - }; + const str = render(text) + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase() + } } function formatDateFunc() { return function (text: string, render: (text: string) => string) { // get the date and format from the text - const [dateVariable, format] = text.split(",", 2); - const date = render(dateVariable); + const [dateVariable, format] = text.split(',', 2) + const date = render(dateVariable) if (!date) { - return ""; + return '' } // format the date - return formatDate(date, format); - }; + return formatDate(date, format) + } } const functionMap: FunctionMap = { @@ -136,38 +136,38 @@ const functionMap: FunctionMap = { upperCase, upperCaseFirst, formatDate: formatDateFunc, -}; +} export const renderFilename = ( article: Article, filename: string, dateFormat: string ) => { - const date = formatDate(article.savedAt, dateFormat); + const date = formatDate(article.savedAt, dateFormat) const datePublished = article.publishedAt ? formatDate(article.publishedAt, dateFormat).trim() - : undefined; + : undefined const renderedFilename = Mustache.render(filename, { title: article.title, - author: article.author ?? "unknown-author", + author: article.author ?? 'unknown-author', date, dateSaved: date, datePublished, id: article.id, - }); + }) // truncate the filename to 100 characters return truncate(renderedFilename, { length: 100, - }); -}; + }) +} export const renderLabels = (labels?: LabelView[]) => { return labels?.map((l) => ({ // replace spaces with underscores because Obsidian doesn't allow spaces in tags - name: l.name.replaceAll(" ", "_"), - })); -}; + name: l.name.replaceAll(' ', '_'), + })) +} export const renderArticleContnet = async ( article: Article, @@ -182,22 +182,22 @@ export const renderArticleContnet = async ( ) => { // filter out notes and redactions const articleHighlights = - article.highlights?.filter((h) => h.type === HighlightType.Highlight) || []; + article.highlights?.filter((h) => h.type === HighlightType.Highlight) || [] // sort highlights by location if selected in options - if (highlightOrder === "LOCATION") { + if (highlightOrder === 'LOCATION') { articleHighlights.sort((a, b) => { try { if (article.pageType === PageType.File) { // sort by location in file - return compareHighlightsInFile(a, b); + return compareHighlightsInFile(a, b) } // for web page, sort by location in the page - return getHighlightLocation(a.patch) - getHighlightLocation(b.patch); + return getHighlightLocation(a.patch) - getHighlightLocation(b.patch) } catch (e) { - console.error(e); - return compareHighlightsInFile(a, b); + console.error(e) + return compareHighlightsInFile(a, b) } - }); + }) } const highlights: HighlightView[] = articleHighlights.map((highlight) => { return { @@ -210,25 +210,25 @@ export const renderArticleContnet = async ( color: highlight.color ?? 'yellow', positionPercent: highlight.highlightPositionPercent, positionAnchorIndex: highlight.highlightPositionAnchorIndex + 1, // PDF page numbers start at 1 - }; - }); - const dateSaved = formatDate(article.savedAt, dateSavedFormat); + } + }) + const dateSaved = formatDate(article.savedAt, dateSavedFormat) const siteName = - article.siteName || siteNameFromUrl(article.originalArticleUrl); - const publishedAt = article.publishedAt; + article.siteName || siteNameFromUrl(article.originalArticleUrl) + const publishedAt = article.publishedAt const datePublished = publishedAt ? formatDate(publishedAt, dateSavedFormat).trim() - : undefined; + : undefined const articleNote = article.highlights?.find( (h) => h.type === HighlightType.Note - ); + ) const dateRead = article.readAt ? formatDate(article.readAt, dateSavedFormat).trim() - : undefined; - const wordsCount = article.wordsCount; + : undefined + const wordsCount = article.wordsCount const readLength = wordsCount ? Math.round(Math.max(1, wordsCount / 235)) - : undefined; + : undefined const articleView: ArticleView = { id: article.id, title: article.title, @@ -251,101 +251,101 @@ export const renderArticleContnet = async ( state: getArticleState(article), dateArchived: article.archivedAt, ...functionMap, - }; + } let frontMatter: { [id: string]: unknown } = { id: article.id, // id is required for deduplication - }; + } // if the front matter template is set, use it if (frontMatterTemplate) { const frontMatterTemplateRendered = Mustache.render( frontMatterTemplate, articleView - ); + ) try { // parse the front matter template as yaml - const frontMatterParsed = parseYaml(frontMatterTemplateRendered); + const frontMatterParsed = parseYaml(frontMatterTemplateRendered) frontMatter = { ...frontMatterParsed, ...frontMatter, - }; + } } catch (error) { // if there's an error parsing the front matter template, log it - console.error("Error parsing front matter template", error); + console.error('Error parsing front matter template', error) // and add the error to the front matter frontMatter = { ...frontMatter, omnivore_error: - "There was an error parsing the front matter template. See console for details.", - }; + 'There was an error parsing the front matter template. See console for details.', + } } } else { // otherwise, use the front matter variables for (const item of frontMatterVariables) { // split the item into variable and alias - const aliasedVariables = item.split("::"); - const variable = aliasedVariables[0]; + const aliasedVariables = item.split('::') + const variable = aliasedVariables[0] // we use snake case for variables in the front matter - const articleVariable = snakeToCamelCase(variable); + const articleVariable = snakeToCamelCase(variable) // use alias if available, otherwise use variable - const key = aliasedVariables[1] || variable; + const key = aliasedVariables[1] || variable if ( - variable === "tags" && + variable === 'tags' && articleView.labels && articleView.labels.length > 0 ) { // tags are handled separately // use label names as tags - frontMatter[key] = articleView.labels.map((l) => l.name); - continue; + frontMatter[key] = articleView.labels.map((l) => l.name) + continue } - const value = (articleView as any)[articleVariable]; + const value = (articleView as any)[articleVariable] if (value) { // if variable is in article, use it - frontMatter[key] = value; + frontMatter[key] = value } } } // Build content string based on template - const content = Mustache.render(template, articleView); - let contentWithoutFrontMatter = removeFrontMatterFromContent(content); - let frontMatterYaml = stringifyYaml(frontMatter); + const content = Mustache.render(template, articleView) + let contentWithoutFrontMatter = removeFrontMatterFromContent(content) + let frontMatterYaml = stringifyYaml(frontMatter) if (isSingleFile) { // wrap the content without front matter in comments - const sectionStart = `%%${article.id}_start%%`; - const sectionEnd = `%%${article.id}_end%%`; - contentWithoutFrontMatter = `${sectionStart}\n${contentWithoutFrontMatter}\n${sectionEnd}`; + const sectionStart = `%%${article.id}_start%%` + const sectionEnd = `%%${article.id}_end%%` + contentWithoutFrontMatter = `${sectionStart}\n${contentWithoutFrontMatter}\n${sectionEnd}` // if single file, wrap the front matter in an array - frontMatterYaml = stringifyYaml([frontMatter]); + frontMatterYaml = stringifyYaml([frontMatter]) } - const frontMatterStr = `---\n${frontMatterYaml}---`; + const frontMatterStr = `---\n${frontMatterYaml}---` - return `${frontMatterStr}\n\n${contentWithoutFrontMatter}`; -}; + return `${frontMatterStr}\n\n${contentWithoutFrontMatter}` +} export const renderFolderName = ( article: Article, template: string, dateFormat: string ) => { - const date = formatDate(article.savedAt, dateFormat); + const date = formatDate(article.savedAt, dateFormat) const datePublished = article.publishedAt ? formatDate(article.publishedAt, dateFormat).trim() - : undefined; + : undefined return Mustache.render(template, { date, dateSaved: date, datePublished, - author: article.author ?? "unknown-author", - }); -}; + author: article.author ?? 'unknown-author', + }) +} export const preParseTemplate = (template: string) => { - return Mustache.parse(template); -}; + return Mustache.parse(template) +} diff --git a/src/util.ts b/src/util.ts index 6ce8e2b..3e2e7b2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,17 +1,17 @@ -import { diff_match_patch } from "diff-match-patch"; -import { DateTime } from "luxon"; -import escape from "markdown-escape"; -import { parseYaml } from "obsidian"; -import outOfCharacter from "out-of-character"; -import { Highlight } from "./api"; - -export const DATE_FORMAT_W_OUT_SECONDS = "yyyy-MM-dd'T'HH:mm"; -export const DATE_FORMAT = `${DATE_FORMAT_W_OUT_SECONDS}:ss`; -export const REPLACEMENT_CHAR = "-"; +import { diff_match_patch } from "diff-match-patch" +import { DateTime } from "luxon" +import escape from "markdown-escape" +import { parseYaml } from "obsidian" +import outOfCharacter from "out-of-character" +import { Highlight } from "./api" + +export const DATE_FORMAT_W_OUT_SECONDS = "yyyy-MM-dd'T'HH:mm" +export const DATE_FORMAT = `${DATE_FORMAT_W_OUT_SECONDS}:ss` +export const REPLACEMENT_CHAR = "-" // On Unix-like systems / is reserved and <>:"/\|?* as well as non-printable characters \u0000-\u001F on Windows // credit: https://github.com/sindresorhus/filename-reserved-regex // eslint-disable-next-line no-control-regex -export const ILLEGAL_CHAR_REGEX = /[<>:"/\\|?*\u0000-\u001F]/g; +export const ILLEGAL_CHAR_REGEX = /[<>:"/\\|?*\u0000-\u001F]/g export interface HighlightPoint { left: number; @@ -20,60 +20,60 @@ export interface HighlightPoint { export const getHighlightLocation = (patch: string | null): number => { if (!patch) { - return 0; + return 0 } - const dmp = new diff_match_patch(); - const patches = dmp.patch_fromText(patch); - return patches[0].start1 || 0; -}; + const dmp = new diff_match_patch() + const patches = dmp.patch_fromText(patch) + return patches[0].start1 || 0 +} export const getHighlightPoint = (patch: string | null): HighlightPoint => { if (!patch) { - return { left: 0, top: 0 }; + return { left: 0, top: 0 } } - const { bbox } = JSON.parse(patch) as { bbox: number[] }; + const { bbox } = JSON.parse(patch) as { bbox: number[] } if (!bbox || bbox.length !== 4) { - return { left: 0, top: 0 }; + return { left: 0, top: 0 } } - return { left: bbox[0], top: bbox[1] }; -}; + return { left: bbox[0], top: bbox[1] } +} export const compareHighlightsInFile = (a: Highlight, b: Highlight): number => { // get the position of the highlight in the file - const highlightPointA = getHighlightPoint(a.patch); - const highlightPointB = getHighlightPoint(b.patch); + const highlightPointA = getHighlightPoint(a.patch) + const highlightPointB = getHighlightPoint(b.patch) if (highlightPointA.top === highlightPointB.top) { // if top is same, sort by left - return highlightPointA.left - highlightPointB.left; + return highlightPointA.left - highlightPointB.left } // sort by top - return highlightPointA.top - highlightPointB.top; -}; + return highlightPointA.top - highlightPointB.top +} export const markdownEscape = (text: string): string => { try { - return escape(text); + return escape(text) } catch (e) { - console.error("markdownEscape error", e); - return text; + console.error("markdownEscape error", e) + return text } -}; +} export const escapeQuotationMarks = (text: string): string => { - return text.replace(/"/g, '\\"'); -}; + return text.replace(/"/g, '\\"') +} export const parseDateTime = (str: string): DateTime => { - const res = DateTime.fromFormat(str, DATE_FORMAT); + const res = DateTime.fromFormat(str, DATE_FORMAT) if (res.isValid) { - return res; + return res } - return DateTime.fromFormat(str, DATE_FORMAT_W_OUT_SECONDS); -}; + return DateTime.fromFormat(str, DATE_FORMAT_W_OUT_SECONDS) +} export const wrapAround = (value: number, size: number): number => { - return ((value % size) + size) % size; -}; + return ((value % size) + size) % size +} export const unicodeSlug = (str: string, savedAt: string) => { return ( @@ -94,20 +94,20 @@ export const unicodeSlug = (str: string, savedAt: string) => { .substring(0, 64) + "-" + new Date(savedAt).getTime().toString(16) - ); -}; + ) +} export const replaceIllegalChars = (str: string): string => { return removeInvisibleChars( str.replace(ILLEGAL_CHAR_REGEX, REPLACEMENT_CHAR) - ); -}; + ) +} export function formatDate(date: string, format: string): string { if (isNaN(Date.parse(date))) { - throw new Error(`Invalid date: ${date}`); + throw new Error(`Invalid date: ${date}`) } - return DateTime.fromJSDate(new Date(date)).toFormat(format); + return DateTime.fromJSDate(new Date(date)).toFormat(format) } export const getQueryFromFilter = ( @@ -116,68 +116,68 @@ export const getQueryFromFilter = ( ): string => { switch (filter) { case "ALL": - return ""; + return "" case "HIGHLIGHTS": - return `has:highlights`; + return `has:highlights` case "ADVANCED": - return customQuery; + return customQuery default: - return ""; + return "" } -}; +} export const siteNameFromUrl = (originalArticleUrl: string): string => { try { - return new URL(originalArticleUrl).hostname.replace(/^www\./, ""); + return new URL(originalArticleUrl).hostname.replace(/^www\./, "") } catch { - return ""; + return "" } -}; +} export const formatHighlightQuote = ( quote: string | null, template: string ): string => { if (!quote) { - return ""; + return "" } // if the template has highlights, we need to preserve paragraphs - const regex = /{{#highlights}}(\n)*>/gm; + const regex = /{{#highlights}}(\n)*>/gm if (regex.test(template)) { // replace all empty lines with blockquote '>' to preserve paragraphs - quote = quote.replaceAll(">", ">").replaceAll(/\n/gm, "\n> "); + quote = quote.replaceAll(">", ">").replaceAll(/\n/gm, "\n> ") } - return quote; -}; + return quote +} export const findFrontMatterIndex = ( frontMatter: any[], id: string ): number => { // find index of front matter with matching id - return frontMatter.findIndex((fm) => fm.id == id); -}; + return frontMatter.findIndex((fm) => fm.id == id) +} export const parseFrontMatterFromContent = (content: string) => { // get front matter yaml from content - const frontMatter = content.match(/^---\n(.*?)\n---/s); + const frontMatter = content.match(/^---\n(.*?)\n---/s) if (!frontMatter) { - return undefined; + return undefined } // parse yaml - return parseYaml(frontMatter[1]); -}; + return parseYaml(frontMatter[1]) +} export const removeFrontMatterFromContent = (content: string): string => { - const frontMatterRegex = /^---.*?---\n*/s; + const frontMatterRegex = /^---.*?---\n*/s - return content.replace(frontMatterRegex, ""); -}; + return content.replace(frontMatterRegex, "") +} export const snakeToCamelCase = (str: string) => - str.replace(/(_[a-z])/g, (group) => group.toUpperCase().replace("_", "")); + str.replace(/(_[a-z])/g, (group) => group.toUpperCase().replace("_", "")) const removeInvisibleChars = (str: string): string => { - return outOfCharacter.replace(str); -}; + return outOfCharacter.replace(str) +} diff --git a/yarn.lock b/yarn.lock index 7a2f98c..f21bbc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -371,11 +376,62 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.6.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + +"@eslint/eslintrc@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.3.tgz#797470a75fe0fbd5a53350ee715e85e87baff22d" + integrity sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.53.0": + version "8.53.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.53.0.tgz#bea56f2ed2b5baea164348ff4d5a879f6f81f20d" + integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w== + "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== +"@humanwhocodes/config-array@^0.11.13": + version "0.11.13" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" + integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== + dependencies: + "@humanwhocodes/object-schema" "^2.0.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" + integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -651,7 +707,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -1346,6 +1402,11 @@ "@typescript-eslint/types" "5.29.0" eslint-visitor-keys "^3.3.0" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -1359,6 +1420,16 @@ abbrev@^1.0.0, abbrev@~1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" + integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1390,6 +1461,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-escapes@^4.2.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" @@ -1476,6 +1557,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + argv-formatter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/argv-formatter/-/argv-formatter-1.0.0.tgz#a0ca0cbc29a5b73e836eebe1cbf6c5e0e4eb82f9" @@ -1988,7 +2074,7 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2012,7 +2098,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -2047,6 +2133,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -2123,6 +2214,13 @@ dir-glob@^3.0.0, dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -2336,6 +2434,11 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -2344,6 +2447,14 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + eslint-utils@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" @@ -2361,11 +2472,76 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.53.0: + version "8.53.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.53.0.tgz#14f2c8244298fcae1f46945459577413ba2697ce" + integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.3" + "@eslint/js" "8.53.0" + "@humanwhocodes/config-array" "^0.11.13" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -2378,11 +2554,16 @@ estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: +estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2419,6 +2600,11 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -2430,11 +2616,16 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fastest-levenshtein@^1.0.12: version "1.0.16" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" @@ -2468,6 +2659,13 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2490,6 +2688,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-versions@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" @@ -2497,6 +2703,20 @@ find-versions@^4.0.0: dependencies: semver-regex "^3.1.2" +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.2.9" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" @@ -2614,6 +2834,13 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob@7.1.7: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -2665,6 +2892,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.23.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" + integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + dependencies: + type-fest "^0.20.2" + globby@^11.0.0, globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -2687,6 +2921,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -2948,7 +3187,7 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2975,7 +3214,7 @@ is-path-cwd@^2.2.0: resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== -is-path-inside@^3.0.2: +is-path-inside@^3.0.2, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -3460,11 +3699,23 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -3475,6 +3726,16 @@ json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json-stringify-nice@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" @@ -3514,6 +3775,13 @@ just-diff@^5.0.1: resolved "https://registry.yarnpkg.com/just-diff/-/just-diff-5.2.0.tgz#60dca55891cf24cd4a094e33504660692348a241" integrity sha512-6ufhP9SHjb7jibNFrNxyFZ6od3g+An6Ai9mhGRvcYe8UJlH0prseN64M+6ZBBUoKYHZsitDP42gAJ8+eVWr3lw== +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -3529,6 +3797,14 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + libnpmaccess@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" @@ -3672,6 +3948,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash.capitalize@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" @@ -3702,6 +3985,11 @@ lodash.memoize@4.x: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.uniqby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" @@ -3885,7 +4173,7 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -minimatch@^3.0.4, minimatch@^3.1.1: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -4372,6 +4660,18 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + out-of-character@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/out-of-character/-/out-of-character-1.2.1.tgz#383bf683e58f654ee1f348029367edf5c79ba87d" @@ -4411,7 +4711,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -4432,6 +4732,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" @@ -4601,6 +4908,11 @@ postcss-selector-parser@^6.0.10: cssesc "^3.0.0" util-deprecate "^1.0.2" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prettier@^2.8.1: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" @@ -4673,6 +4985,11 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pure-rand@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" @@ -5288,7 +5605,7 @@ text-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== -text-table@~0.2.0: +text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== @@ -5386,6 +5703,13 @@ tsutils@^3.21.0: dependencies: tslib "^1.8.1" +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -5401,6 +5725,11 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -5484,6 +5813,13 @@ update-browserslist-db@^1.0.11: escalade "^3.1.1" picocolors "^1.0.0" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + url-join@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"