diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc02e9a..de18c0f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,6 @@ -name: Build obsidian plugin +name: Build Obsidian Plugin + +# adapted from https://github.com/argenos/nldates-obsidian/blob/master/.github/workflows/release.yml on: push: @@ -15,15 +17,14 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Use Node.js - uses: actions/setup-node@v3 + - uses: oven-sh/setup-bun@v1 with: - node-version: '16.x' # You might need to adjust this value to your own version + bun-version: latest - name: Build id: build run: | - yarn - yarn run build --if-present + bun install + bun run build mkdir ${{ env.PLUGIN_NAME }} cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }} zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }} diff --git a/automation/build/buildBanner.ts b/automation/build/buildBanner.ts new file mode 100644 index 0000000..b8a62cf --- /dev/null +++ b/automation/build/buildBanner.ts @@ -0,0 +1,38 @@ +import manifest from '../../manifest.json' assert { type: 'json' }; + +export function getBuildBanner(buildType: string, getVersion: (version: string) => string) { + return `/* +------------------------------------------- +${manifest.name} - ${buildType} +------------------------------------------- +By: ${manifest.author} (${manifest.authorUrl}) +Time: ${new Date().toUTCString()} +Version: ${getVersion(manifest.version)} +------------------------------------------- +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +------------------------------------------- +MIT License + +Copyright (c) ${new Date().getFullYear()} ${manifest.author} + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +`; +} diff --git a/esbuild.config.mjs b/automation/build/esbuild.config.ts similarity index 61% rename from esbuild.config.mjs rename to automation/build/esbuild.config.ts index 1ef8bd7..91c5fb9 100644 --- a/esbuild.config.mjs +++ b/automation/build/esbuild.config.ts @@ -1,21 +1,10 @@ -import esbuild from 'esbuild'; import builtins from 'builtin-modules'; +import esbuild from 'esbuild'; import esbuildSvelte from 'esbuild-svelte'; import sveltePreprocess from 'svelte-preprocess'; -import manifest from './manifest.json' assert { type: 'json' }; +import { getBuildBanner } from 'build/buildBanner'; -const banner = `/* -------------------------------------------- -${manifest.name} - Release Build -------------------------------------------- -By: ${manifest.author} (${manifest.authorUrl}) -Time: ${new Date().toUTCString()} -Version: ${manifest.version} -------------------------------------------- -THIS IS A GENERATED/BUNDLED FILE BY ESBUILD -if you want to view the source, please visit the github repository of this plugin -*/ -`; +const banner = getBuildBanner('Release Build', version => version); const build = await esbuild.build({ banner: { @@ -47,14 +36,22 @@ const build = await esbuild.build({ outfile: 'main.js', minify: true, metafile: true, + define: { + MB_GLOBAL_CONFIG_DEV_BUILD: 'false', + }, plugins: [ esbuildSvelte({ - compilerOptions: { css: 'injected' }, + compilerOptions: { css: 'injected', dev: false, sveltePath: 'svelte' }, preprocess: sveltePreprocess(), filterWarnings: warning => { // we don't want warnings from node modules that we can do nothing about - return !warning.filename.includes('node_modules'); + return !warning.filename?.includes('node_modules'); }, }), ], }); + +const file = Bun.file('meta.txt'); +await Bun.write(file, JSON.stringify(build.metafile, null, '\t')); + +process.exit(0); diff --git a/automation/build/esbuild.dev.config.ts b/automation/build/esbuild.dev.config.ts new file mode 100644 index 0000000..6b96549 --- /dev/null +++ b/automation/build/esbuild.dev.config.ts @@ -0,0 +1,65 @@ +import esbuild from 'esbuild'; +import copy from 'esbuild-plugin-copy-watch'; +import esbuildSvelte from 'esbuild-svelte'; +import sveltePreprocess from 'svelte-preprocess'; +import manifest from '../../manifest.json' assert { type: 'json' }; +import { getBuildBanner } from 'build/buildBanner'; + +const banner = getBuildBanner('Dev Build', _ => 'Dev Build'); + +const context = await esbuild.context({ + banner: { + js: banner, + }, + entryPoints: ['src/main.ts'], + bundle: true, + external: [ + 'obsidian', + 'electron', + '@codemirror/autocomplete', + '@codemirror/collab', + '@codemirror/commands', + '@codemirror/language', + '@codemirror/lint', + '@codemirror/search', + '@codemirror/state', + '@codemirror/view', + '@lezer/common', + '@lezer/highlight', + '@lezer/lr', + ], + format: 'cjs', + target: 'es2018', + logLevel: 'info', + sourcemap: 'inline', + treeShaking: true, + outdir: `exampleVault/.obsidian/plugins/${manifest.id}/`, + outbase: 'src', + define: { + MB_GLOBAL_CONFIG_DEV_BUILD: 'true', + }, + plugins: [ + copy({ + paths: [ + { + from: './styles.css', + to: '', + }, + { + from: './manifest.json', + to: '', + }, + ], + }), + esbuildSvelte({ + compilerOptions: { css: 'injected', dev: true, sveltePath: 'svelte' }, + preprocess: sveltePreprocess(), + filterWarnings: warning => { + // we don't want warnings from node modules that we can do nothing about + return !warning.filename?.includes('node_modules'); + }, + }), + ], +}); + +await context.watch(); diff --git a/automation/config.json b/automation/config.json new file mode 100644 index 0000000..c9a8947 --- /dev/null +++ b/automation/config.json @@ -0,0 +1,5 @@ +{ + "devBranch": "master", + "releaseBranch": "release", + "github": "https://github.com/mProjectsCode/obsidian-media-db-plugin" +} diff --git a/automation/release.ts b/automation/release.ts new file mode 100644 index 0000000..29203ba --- /dev/null +++ b/automation/release.ts @@ -0,0 +1,171 @@ +import { UserError } from 'utils/utils'; +import { CanaryVersion, Version, getIncrementOptions, parseVersion, stringifyVersion } from 'utils/versionUtils'; +import config from './config.json'; +import { $choice as $choice, $confirm, $seq, CMD_FMT, Verboseness } from 'utils/shellUtils'; + +async function runPreconditions(): Promise { + // run preconditions + await $seq( + [`bun run format`, `bun run lint:fix`, `bun run test`], + (cmd: string) => { + throw new UserError(`precondition "${cmd}" failed`); + }, + () => {}, + undefined, + Verboseness.VERBOSE, + ); + + // add changed files + await $seq( + [`git add .`], + () => { + throw new UserError('failed to add preconditions changes to git'); + }, + () => {}, + undefined, + Verboseness.NORMAL, + ); + + // check if there were any changes + let changesToCommit = false; + await $seq( + [`git diff --quiet`, `git diff --cached --quiet`], + () => { + changesToCommit = true; + }, + () => {}, + undefined, + Verboseness.QUITET, + ); + + // if there were any changes, commit them + if (changesToCommit) { + await $seq( + [`git commit -m "[auto] run release preconditions"`], + () => { + throw new UserError('failed to add preconditions changes to git'); + }, + () => {}, + undefined, + Verboseness.NORMAL, + ); + } +} + +async function run() { + console.log('looking for untracked changes ...'); + + // check for any uncommited files and exit if there are any + await $seq( + [`git add .`, `git diff --quiet`, `git diff --cached --quiet`, `git checkout ${config.devBranch}`], + () => { + throw new UserError('there are still untracked changes'); + }, + () => {}, + undefined, + Verboseness.QUITET, + ); + + console.log('\nrunning preconditions ...\n'); + + await runPreconditions(); + + console.log('\nbumping versions ...\n'); + + const manifestFile = Bun.file('./manifest.json'); + const manifest = await manifestFile.json(); + + const versionString: string = manifest.version; + const currentVersion: Version = parseVersion(versionString); + const currentVersionString = stringifyVersion(currentVersion); + + const versionIncrementOptions = getIncrementOptions(currentVersion); + + const selectedIndex = await $choice( + `Current version "${currentVersionString}". Select new version`, + versionIncrementOptions.map(x => stringifyVersion(x)), + ); + const newVersion = versionIncrementOptions[selectedIndex]; + const newVersionString = stringifyVersion(newVersion); + + console.log(''); + + await $confirm(`Version will be updated "${currentVersionString}" -> "${newVersionString}". Are you sure`, () => { + throw new UserError('user canceled script'); + }); + + if (!(newVersion instanceof CanaryVersion)) { + manifest.version = newVersionString; + } + + await Bun.write(manifestFile, JSON.stringify(manifest, null, '\t')); + + const betaManifest = structuredClone(manifest); + betaManifest.version = newVersionString; + + const betaManifestFile = Bun.file('./manifest-beta.json'); + await Bun.write(betaManifestFile, JSON.stringify(betaManifest, null, '\t')); + + if (!(newVersion instanceof CanaryVersion)) { + const versionsFile = Bun.file('./versions.json'); + const versionsJson = await versionsFile.json(); + + versionsJson[newVersionString] = manifest.minAppVersion; + + await Bun.write(versionsFile, JSON.stringify(versionsJson, null, '\t')); + + const packageFile = Bun.file('./package.json'); + const packageJson = await packageFile.json(); + + packageJson.version = newVersionString; + + await Bun.write(packageFile, JSON.stringify(packageJson, null, '\t')); + } + + await $seq( + [`bun run format`, `git add .`, `git commit -m "[auto] bump version to \`${newVersionString}\`"`], + () => { + throw new UserError('failed to add preconditions changes to git'); + }, + () => {}, + undefined, + Verboseness.NORMAL, + ); + + console.log('\ncreating release tag ...\n'); + + await $seq( + [ + `git checkout ${config.releaseBranch}`, + `git merge ${config.devBranch} --commit -m "[auto] merge \`${newVersionString}\` release commit"`, + `git push origin ${config.releaseBranch}`, + `git tag -a ${newVersionString} -m "release version ${newVersionString}"`, + `git push origin ${newVersionString}`, + `git checkout ${config.devBranch}`, + `git merge ${config.releaseBranch}`, + `git push origin ${config.devBranch}`, + ], + () => { + throw new UserError('failed to merge or create tag'); + }, + () => {}, + undefined, + Verboseness.NORMAL, + ); + + console.log(''); + + console.log(`${CMD_FMT.BgGreen}done${CMD_FMT.Reset}`); + console.log(`${config.github}`); + console.log(`${config.github}/releases/tag/${newVersionString}`); +} + +try { + await run(); +} catch (e) { + if (e instanceof UserError) { + console.error(e.message); + } else { + console.error(e); + } +} diff --git a/automation/stats.ts b/automation/stats.ts new file mode 100644 index 0000000..2a34c03 --- /dev/null +++ b/automation/stats.ts @@ -0,0 +1,160 @@ +import * as fs from 'fs'; + +interface Stat { + fileType: string; + count: number; + lines: number; +} + +abstract class StatsBase { + parent: StatsBase | undefined; + path: string; + name: string; + stats: Stat[]; + + constructor(parent: StatsBase | undefined, path: string, name: string, stats: Stat[]) { + this.parent = parent; + + this.path = path; + this.name = name; + this.stats = stats; + } + + abstract addChild(child: StatsBase): void; + + abstract mergeStats(stats: Stat[]): void; + + abstract print(depth: number, lastChildArr: boolean[]): void; + + abstract sort(): void; + + getPrefix(depth: number, lastChildArr: boolean[]): string { + let prefix = ''; + for (let i = 0; i < depth; i++) { + prefix += lastChildArr[i] ? ' ' : '│ '; + } + + if (lastChildArr.at(-1)) { + prefix += '└─ '; + } else { + prefix += '├─ '; + } + + return prefix; + } +} + +class FolderStats extends StatsBase { + children: StatsBase[]; + + constructor(parent: StatsBase | undefined, path: string, name: string) { + super(parent, path, name, []); + + this.children = []; + } + + addChild(child: StatsBase) { + this.children.push(child); + this.mergeStats(child.stats); + } + + mergeStats(stats: Stat[]): void { + // console.log(this, stats); + for (const stat of stats) { + const existingStat = this.stats.find(s => s.fileType === stat.fileType); + if (existingStat) { + existingStat.count += stat.count; + existingStat.lines += stat.lines; + } else { + this.stats.push(structuredClone(stat)); + } + } + + this.parent?.mergeStats(stats); + } + + print(depth: number, lastChildArr: boolean[]): void { + console.log( + `${this.getPrefix(depth, lastChildArr)}${this.name} | ${this.stats.reduce((acc, s) => acc + s.count, 0)} files | ${this.stats.reduce((acc, s) => acc + s.lines, 0)} lines`, + ); + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + child.print(depth + 1, [...lastChildArr, i === this.children.length - 1]); + } + } + + sort(): void { + this.children.sort((a, b) => { + if (a instanceof FolderStats && b instanceof FileStats) { + return 1; + } else if (a instanceof FileStats && b instanceof FolderStats) { + return -1; + } else { + return a.name.localeCompare(b.name); + } + }); + this.children.forEach(c => c.sort()); + } +} + +class FileStats extends StatsBase { + constructor(parent: StatsBase, path: string, name: string, stats: Stat[]) { + super(parent, path, name, stats); + } + + addChild(_child: StatsBase): void { + throw new Error('Cannot add child to file'); + } + + mergeStats(_stats: Stat[]): void { + throw new Error('Cannot merge stats to file'); + } + + print(depth: number, lastChildArr: boolean[]): void { + console.log(`${this.getPrefix(depth, lastChildArr)}${this.name} | ${this.stats[0].lines} lines`); + } + + sort(): void {} +} + +function collectStats() { + const root = new FolderStats(undefined, './src', 'src'); + const ignore = ['node_modules', 'extraTypes', 'bun.lockb']; + + const todo: FolderStats[] = [root]; + + while (todo.length > 0) { + const current = todo.pop()!; + const children = fs.readdirSync(current.path, { withFileTypes: true }); + + for (const child of children) { + if (ignore.includes(child.name)) { + continue; + } + + if (child.isDirectory()) { + const folder = new FolderStats(current, `${current.path}/${child.name}`, child.name); + current.addChild(folder); + + todo.push(folder); + } else { + const content = fs.readFileSync(`${current.path}/${child.name}`, 'utf-8'); + + const file = new FileStats(current, `${current.path}/${child.name}`, child.name, [ + { + fileType: child.name.split('.').splice(1).join('.'), + count: 1, + lines: content.split('\n').length, + }, + ]); + current.addChild(file); + } + } + } + + root.sort(); + + root.print(0, [true]); +} + +collectStats(); diff --git a/automation/tsconfig.json b/automation/tsconfig.json new file mode 100644 index 0000000..1fd339b --- /dev/null +++ b/automation/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "module": "ESNext", + "target": "ESNext", + "allowJs": true, + "noImplicitAny": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "lib": ["DOM", "ES5", "ES6", "ES7", "Es2021"], + "types": ["bun-types"], + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "include": ["**/*.ts"] +} diff --git a/automation/utils/shellUtils.ts b/automation/utils/shellUtils.ts new file mode 100644 index 0000000..d1c58e0 --- /dev/null +++ b/automation/utils/shellUtils.ts @@ -0,0 +1,168 @@ +import { Subprocess } from 'bun'; +import stringArgv from 'string-argv'; + +export enum Verboseness { + QUITET, + NORMAL, + VERBOSE, +} + +function exec(c: string, cwd?: string): Subprocess<'ignore', 'pipe', 'inherit'> { + return Bun.spawn(stringArgv(c), { cwd: cwd }); +} + +export async function $(cmd: string, cwd?: string | undefined, verboseness: Verboseness = Verboseness.NORMAL): Promise<{ stdout: string; stderr: string; exit: number }> { + if (verboseness === Verboseness.NORMAL || verboseness === Verboseness.VERBOSE) { + if (cwd !== undefined) { + console.log(`\n${CMD_FMT.Bright}running${CMD_FMT.Reset} in ${cwd} - ${cmd}\n`); + } else { + console.log(`\n${CMD_FMT.Bright}running${CMD_FMT.Reset} - ${cmd}\n`); + } + } + + const proc = exec(cmd, cwd); + const stdout = await new Response(proc.stdout).text(); + const stderr = await new Response(proc.stderr).text(); + + if (verboseness === Verboseness.VERBOSE) { + if (stdout !== '') { + console.log( + stdout + .split('\n') + .map(x => `${CMD_FMT.FgGray}>${CMD_FMT.Reset} ${x}\n`) + .join(''), + ); + } + + if (stderr !== '') { + console.log( + stderr + .split('\n') + .map(x => `${CMD_FMT.FgRed}>${CMD_FMT.Reset} ${x}\n`) + .join(''), + ); + } + } + + const exit = await proc.exited; + + if (verboseness === Verboseness.NORMAL || verboseness === Verboseness.VERBOSE) { + if (exit === 0) { + console.log(`${CMD_FMT.FgGreen}success${CMD_FMT.Reset} - ${cmd}\n`); + } else { + console.log(`${CMD_FMT.FgRed}fail${CMD_FMT.Reset} - ${cmd} - code ${exit}\n`); + } + } + + return { + stdout, + stderr, + exit, + }; +} + +export async function $seq( + cmds: string[], + onError: (cmd: string, index: number) => void, + onSuccess: () => void, + cwd?: string | undefined, + verboseness: Verboseness = Verboseness.NORMAL, +): Promise { + const results = []; + for (let i = 0; i < cmds.length; i += 1) { + const cmd = cmds[i]; + const result = await $(cmd, cwd, verboseness); + + if (result.exit !== 0) { + onError(cmd, i); + return; + } + + results.push(result); + } + onSuccess(); +} + +export async function $input(message: string): Promise { + console.write(`${message} `); + const stdin = Bun.stdin.stream(); + const reader = stdin.getReader(); + const chunk = await reader.read(); + reader.releaseLock(); + const text = Buffer.from(chunk.value ?? '').toString(); + return text.trim(); +} + +export async function $choice(message: string, options: string[]): Promise { + console.log(`${message} `); + + let optionNumbers = new Map(); + + for (let i = 0; i < options.length; i++) { + const option = options[i]; + console.log(`[${i}] ${option}`); + + optionNumbers.set(i.toString(), i); + } + + let ret: undefined | number = undefined; + + while (ret === undefined) { + const selectedStr = await $input(`Select [${[...optionNumbers.keys()].join('/')}]:`); + + ret = optionNumbers.get(selectedStr); + + if (ret === undefined) { + console.log(`${CMD_FMT.FgRed}invalid selection, please select a valid option${CMD_FMT.Reset}`); + } + } + + return ret; +} + +export async function $confirm(message: string, onReject: () => void): Promise { + while (true) { + const answer = await $input(`${message} [Y/N]?`); + + if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { + return; + } + + if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') { + onReject(); + return; + } + + console.log(`${CMD_FMT.FgRed}invalid selection, please select a valid option${CMD_FMT.Reset}`); + } +} + +export const CMD_FMT = { + Reset: '\x1b[0m', + Bright: '\x1b[1m', + Dim: '\x1b[2m', + Underscore: '\x1b[4m', + Blink: '\x1b[5m', + Reverse: '\x1b[7m', + Hidden: '\x1b[8m', + + FgBlack: '\x1b[30m', + FgRed: '\x1b[31m', + FgGreen: '\x1b[32m', + FgYellow: '\x1b[33m', + FgBlue: '\x1b[34m', + FgMagenta: '\x1b[35m', + FgCyan: '\x1b[36m', + FgWhite: '\x1b[37m', + FgGray: '\x1b[90m', + + BgBlack: '\x1b[40m', + BgRed: '\x1b[41m', + BgGreen: '\x1b[42m', + BgYellow: '\x1b[43m', + BgBlue: '\x1b[44m', + BgMagenta: '\x1b[45m', + BgCyan: '\x1b[46m', + BgWhite: '\x1b[47m', + BgGray: '\x1b[100m', +}; diff --git a/automation/utils/utils.ts b/automation/utils/utils.ts new file mode 100644 index 0000000..f5d72ee --- /dev/null +++ b/automation/utils/utils.ts @@ -0,0 +1,6 @@ +export class UserError extends Error {} + +export interface ProjectConfig { + corePackages: string[]; + packages: string[]; +} diff --git a/automation/utils/versionUtils.ts b/automation/utils/versionUtils.ts new file mode 100644 index 0000000..62aad22 --- /dev/null +++ b/automation/utils/versionUtils.ts @@ -0,0 +1,108 @@ +import { Parser } from '@lemons_dev/parsinom/lib/Parser'; +import { P_UTILS } from '@lemons_dev/parsinom/lib/ParserUtils'; +import { P } from '@lemons_dev/parsinom/lib/ParsiNOM'; +import Moment from 'moment'; +import { UserError } from 'utils/utils'; + +export class Version { + major: number; + minor: number; + patch: number; + + constructor(major: number, minor: number, patch: number) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + toString(): string { + return `${this.major}.${this.minor}.${this.patch}`; + } +} + +export class CanaryVersion extends Version { + canary: string; + + constructor(major: number, minor: number, patch: number, canary: string) { + super(major, minor, patch); + this.canary = canary; + } + + toString(): string { + return `${super.toString()}-canary.${this.canary}`; + } +} + +const numberParser: Parser = P_UTILS.digits() + .map(x => Number.parseInt(x)) + .chain(x => { + if (Number.isNaN(x)) { + return P.fail('a number'); + } else { + return P.succeed(x); + } + }); + +const canaryParser: Parser = P.sequenceMap( + (_, c1, c2, c3) => { + return c1 + c2 + c3; + }, + P.string('-canary.'), + P_UTILS.digit() + .repeat(8, 8) + .map(x => x.join('')), + P.string('T'), + P_UTILS.digit() + .repeat(6, 6) + .map(x => x.join('')), +); + +export const versionParser: Parser = P.or( + P.sequenceMap( + (major, _1, minor, _2, patch) => { + return new Version(major, minor, patch); + }, + numberParser, + P.string('.'), + numberParser, + P.string('.'), + numberParser, + P_UTILS.eof(), + ), + P.sequenceMap( + (major, _1, minor, _2, patch, canary) => { + return new CanaryVersion(major, minor, patch, canary); + }, + numberParser, + P.string('.'), + numberParser, + P.string('.'), + numberParser, + canaryParser, + P_UTILS.eof(), + ), +); + +export function parseVersion(str: string): Version { + const parserRes = versionParser.tryParse(str); + if (parserRes.success) { + return parserRes.value; + } else { + throw new UserError(`failed to parse manifest version "${str}"`); + } +} + +export function stringifyVersion(version: Version): string { + return version.toString(); +} + +export function getIncrementOptions(version: Version): [Version, Version, Version, CanaryVersion] { + const moment = Moment(); + const canary = moment.utcOffset(0).format('YYYYMMDDTHHmmss'); + return [ + new Version(version.major + 1, 0, 0), + new Version(version.major, version.minor + 1, 0), + new Version(version.major, version.minor, version.patch + 1), + new CanaryVersion(version.major, version.minor, version.patch, canary), + ]; +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..233fe38 Binary files /dev/null and b/bun.lockb differ diff --git a/esbuild.dev.config.mjs b/esbuild.dev.config.mjs deleted file mode 100644 index 6b95535..0000000 --- a/esbuild.dev.config.mjs +++ /dev/null @@ -1,63 +0,0 @@ -import esbuild from 'esbuild'; -import process from 'process'; -import builtins from 'builtin-modules'; -import esbuildSvelte from 'esbuild-svelte'; -import sveltePreprocess from 'svelte-preprocess'; -import manifest from './manifest.json' assert { type: 'json' }; - -const banner = `/* -------------------------------------------- -${manifest.name} - Dev Build -------------------------------------------- -By: ${manifest.author} (${manifest.authorUrl}) -Time: ${new Date().toUTCString()} -Version: Dev Build -------------------------------------------- -THIS IS A GENERATED/BUNDLED FILE BY ESBUILD -if you want to view the source, please visit the github repository of this plugin -*/ -`; - -const context = await esbuild - .context({ - banner: { - js: banner, - }, - entryPoints: ['src/main.ts'], - bundle: true, - external: [ - 'obsidian', - 'electron', - '@codemirror/autocomplete', - '@codemirror/collab', - '@codemirror/commands', - '@codemirror/language', - '@codemirror/lint', - '@codemirror/search', - '@codemirror/state', - '@codemirror/view', - '@lezer/common', - '@lezer/highlight', - '@lezer/lr', - ...builtins, - ], - format: 'cjs', - target: 'es2018', - logLevel: 'info', - sourcemap: 'inline', - treeShaking: true, - outfile: 'main.js', - plugins: [ - esbuildSvelte({ - compilerOptions: { css: 'injected' }, - preprocess: sveltePreprocess(), - filterWarnings: warning => { - // we don't want warnings from node modules that we can do nothing about - return !warning.filename.includes('node_modules'); - }, - }), - ], - }) - .catch(() => process.exit(1)); - -await context.watch(); diff --git a/exampleVault/.obsidian/app.json b/exampleVault/.obsidian/app.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/exampleVault/.obsidian/app.json @@ -0,0 +1 @@ +{} diff --git a/exampleVault/.obsidian/appearance.json b/exampleVault/.obsidian/appearance.json new file mode 100644 index 0000000..af4da16 --- /dev/null +++ b/exampleVault/.obsidian/appearance.json @@ -0,0 +1,4 @@ +{ + "accentColor": "", + "theme": "obsidian" +} diff --git a/exampleVault/.obsidian/community-plugins.json b/exampleVault/.obsidian/community-plugins.json new file mode 100644 index 0000000..35fa2d8 --- /dev/null +++ b/exampleVault/.obsidian/community-plugins.json @@ -0,0 +1 @@ +["obsidian-media-db-plugin"] diff --git a/exampleVault/.obsidian/core-plugins-migration.json b/exampleVault/.obsidian/core-plugins-migration.json new file mode 100644 index 0000000..ba79b99 --- /dev/null +++ b/exampleVault/.obsidian/core-plugins-migration.json @@ -0,0 +1,31 @@ +{ + "file-explorer": true, + "global-search": true, + "switcher": true, + "graph": true, + "backlink": true, + "canvas": true, + "outgoing-link": true, + "tag-pane": true, + "properties": false, + "page-preview": true, + "daily-notes": true, + "templates": true, + "note-composer": true, + "command-palette": true, + "slash-command": false, + "editor-status": true, + "bookmarks": true, + "markdown-importer": false, + "zk-prefixer": false, + "random-note": false, + "outline": true, + "word-count": true, + "slides": false, + "audio-recorder": false, + "workspaces": false, + "file-recovery": true, + "publish": false, + "sync": false, + "browser": false +} diff --git a/exampleVault/.obsidian/core-plugins.json b/exampleVault/.obsidian/core-plugins.json new file mode 100644 index 0000000..abcf2ca --- /dev/null +++ b/exampleVault/.obsidian/core-plugins.json @@ -0,0 +1,20 @@ +[ + "file-explorer", + "global-search", + "switcher", + "graph", + "backlink", + "canvas", + "outgoing-link", + "tag-pane", + "page-preview", + "daily-notes", + "templates", + "note-composer", + "command-palette", + "editor-status", + "bookmarks", + "outline", + "word-count", + "file-recovery" +] diff --git a/exampleVault/.obsidian/hotkeys.json b/exampleVault/.obsidian/hotkeys.json new file mode 100644 index 0000000..94c3be5 --- /dev/null +++ b/exampleVault/.obsidian/hotkeys.json @@ -0,0 +1,8 @@ +{ + "app:reload": [ + { + "modifiers": ["Mod"], + "key": "R" + } + ] +} diff --git a/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/.hotreload b/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/.hotreload new file mode 100644 index 0000000..e69de29 diff --git a/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/manifest.json b/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/manifest.json new file mode 100644 index 0000000..2535f1e --- /dev/null +++ b/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "obsidian-media-db-plugin", + "name": "Media DB", + "version": "0.6.0", + "minAppVersion": "1.5.0", + "description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.", + "author": "Moritz Jung", + "authorUrl": "https://www.moritzjung.dev", + "isDesktopOnly": false +} diff --git a/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/styles.css b/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/styles.css new file mode 100644 index 0000000..6d4bb8d --- /dev/null +++ b/exampleVault/.obsidian/plugins/obsidian-media-db-plugin/styles.css @@ -0,0 +1,126 @@ +.media-db-plugin-list-wrapper { + display: flex; + align-content: center; + margin-bottom: 5px; + margin-top: 5px; +} + +.media-db-plugin-list-toggle { +} + +.media-db-plugin-list-text-wrapper { + flex: 1; +} + +.media-db-plugin-list-text { + display: block; +} + +small.media-db-plugin-list-text { + color: var(--text-muted); +} + +.media-db-plugin-select-modal { + display: contents; +} + +.media-db-plugin-select-wrapper { + display: flex; + flex-direction: column; + margin: 5px; + overflow-y: auto; +} + +.media-db-plugin-select-element { + cursor: pointer; + border-left: 5px solid transparent; + padding: 5px; + margin: 5px 0 5px 0; + border-radius: 5px; + white-space: pre-wrap; + font-size: 16px; +} + +.media-db-plugin-select-element-selected { + border-left: 5px solid var(--interactive-accent) !important; + background: var(--background-secondary-alt); +} + +.media-db-plugin-select-element-hover { + background: var(--background-secondary-alt); +} + +.media-db-plugin-preview-modal { + display: contents; +} + +.media-db-plugin-preview-wrapper { + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.media-db-plugin-spacer { + margin-bottom: 10px; +} + +/* region property mappings */ +.media-db-plugin-property-mappings-model-container { + border: 1px solid var(--background-modifier-border); + border-radius: 5px; + padding: 10px; + width: 100%; +} + +.media-db-plugin-property-mappings-container { + margin: 10px 0; + display: flex; + flex-direction: column; + gap: 5px; +} + +.media-db-plugin-property-mapping-element { + display: flex; + flex-direction: row; + gap: 10px; +} + +.media-db-plugin-property-mapping-element-property-name-wrapper { + min-width: 160px; + background: var(--background-modifier-form-field); + padding: 2px 5px; + border-radius: 5px; + + display: flex; + align-items: center; +} + +.media-db-plugin-property-mapping-element-property-name { + margin: 0; +} + +.media-db-plugin-property-mappings-save-button { + margin: 0; +} + +.media-db-plugin-property-mapping-to { + display: flex; + align-items: center; +} + +.media-db-plugin-property-mapping-validation { + color: var(--text-error); + margin-bottom: 5px; +} + +.media-db-plugin-button:focus { + /*outline: 1px solid white;*/ +} + +.media-db-plugin-preview { + border-radius: var(--modal-radius); + border: var(--modal-border-width) solid var(--modal-border-color); + padding: var(--size-4-4); +} + +/* endregion */ diff --git a/exampleVault/.obsidian/workspace.json b/exampleVault/.obsidian/workspace.json new file mode 100644 index 0000000..83c106a --- /dev/null +++ b/exampleVault/.obsidian/workspace.json @@ -0,0 +1,151 @@ +{ + "main": { + "id": "9618d22a4d8c511c", + "type": "split", + "children": [ + { + "id": "982b4a04c4a0e996", + "type": "tabs", + "children": [ + { + "id": "b912b847e62b0980", + "type": "leaf", + "state": { + "type": "empty", + "state": {} + } + } + ] + } + ], + "direction": "vertical" + }, + "left": { + "id": "a448fa9a0495e7a4", + "type": "split", + "children": [ + { + "id": "0148e129668970ae", + "type": "tabs", + "children": [ + { + "id": "5f549c8ae9af971e", + "type": "leaf", + "state": { + "type": "file-explorer", + "state": { + "sortOrder": "alphabetical" + } + } + }, + { + "id": "b782c94865111b8f", + "type": "leaf", + "state": { + "type": "search", + "state": { + "query": "", + "matchingCase": false, + "explainSearch": false, + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical" + } + } + }, + { + "id": "942c8e2bba79177d", + "type": "leaf", + "state": { + "type": "bookmarks", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300 + }, + "right": { + "id": "8a6d32cd2e32c39d", + "type": "split", + "children": [ + { + "id": "b712a27a0deec50f", + "type": "tabs", + "children": [ + { + "id": "81240f6821eb54dc", + "type": "leaf", + "state": { + "type": "backlink", + "state": { + "collapseAll": false, + "extraContext": false, + "sortOrder": "alphabetical", + "showSearch": false, + "searchQuery": "", + "backlinkCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "2dda1a67c07f367d", + "type": "leaf", + "state": { + "type": "outgoing-link", + "state": { + "linksCollapsed": false, + "unlinkedCollapsed": true + } + } + }, + { + "id": "cb0aecbffefcb594", + "type": "leaf", + "state": { + "type": "tag", + "state": { + "sortOrder": "frequency", + "useHierarchy": true + } + } + }, + { + "id": "1be09685a129aa6e", + "type": "leaf", + "state": { + "type": "outline", + "state": {} + } + } + ] + } + ], + "direction": "horizontal", + "width": 300, + "collapsed": true + }, + "left-ribbon": { + "hiddenItems": { + "obsidian-media-db-plugin:Add new Media DB entry": false, + "switcher:Open quick switcher": false, + "graph:Open graph view": false, + "canvas:Create new canvas": false, + "daily-notes:Open today's daily note": false, + "templates:Insert template": false, + "command-palette:Open command palette": false + } + }, + "active": "b912b847e62b0980", + "lastOpenFiles": [ + "Media DB/games/Limbo (2010).md", + "Media DB/games/Hollow Knight (2017).md", + "Media DB/movies/2001 - A Space Odyssey (1968).md", + "Media DB/games", + "Media DB/movies", + "Media DB" + ] +} \ No newline at end of file diff --git a/exampleVault/index.md b/exampleVault/index.md new file mode 100644 index 0000000..e69de29 diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 6807d50..0000000 --- a/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - roots: ['/src', ''], - testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - }, -}; diff --git a/manifest-beta.json b/manifest-beta.json new file mode 100644 index 0000000..2535f1e --- /dev/null +++ b/manifest-beta.json @@ -0,0 +1,10 @@ +{ + "id": "obsidian-media-db-plugin", + "name": "Media DB", + "version": "0.6.0", + "minAppVersion": "1.5.0", + "description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.", + "author": "Moritz Jung", + "authorUrl": "https://www.moritzjung.dev", + "isDesktopOnly": false +} diff --git a/manifest.json b/manifest.json index 9ff6d1f..2535f1e 100644 --- a/manifest.json +++ b/manifest.json @@ -2,9 +2,9 @@ "id": "obsidian-media-db-plugin", "name": "Media DB", "version": "0.6.0", - "minAppVersion": "0.14.0", + "minAppVersion": "1.5.0", "description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.", "author": "Moritz Jung", - "authorUrl": "https://mprojectscode.github.io/", + "authorUrl": "https://www.moritzjung.dev", "isDesktopOnly": false } diff --git a/package.json b/package.json index af4b1df..fe25423 100644 --- a/package.json +++ b/package.json @@ -4,34 +4,48 @@ "description": "A plugin that can query multiple APIs for movies, series, anime, games, music and wiki articles, and import them into your vault.", "main": "main.js", "scripts": { - "dev": "node esbuild.dev.config.mjs", - "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", - "version": "node version-bump.mjs && git add manifest.json versions.json", - "test": "jest", - "format": "prettier --write ." + "dev": "bun run automation/build/esbuild.dev.config.ts", + "build": "bun run tsc && bun run automation/build/esbuild.config.ts", + "tsc": "tsc -noEmit -skipLibCheck", + "test": "bun test", + "test:log": "LOG_TESTS=true bun test", + "format": "prettier --write --plugin prettier-plugin-svelte .", + "format:check": "prettier --check --plugin prettier-plugin-svelte .", + "lint": "eslint --max-warnings=0 src/**", + "lint:fix": "eslint --max-warnings=0 --fix src/**", + "svelte-check": "svelte-check --compiler-warnings \"unused-export-let:ignore\"", + "check": "bun run format:check && bun run tsc && bun run lint && bun run test", + "check:fix": "bun run format && bun run tsc && bun run lint:fix && bun run test", + "release": "bun run automation/release.ts", + "stats": "bun run automation/stats.ts" }, "keywords": [], "author": "Moritz Jung", "license": "GPL-3.0", "devDependencies": { "@popperjs/core": "^2.11.8", - "@tsconfig/svelte": "^5.0.2", - "@types/jest": "^29.5.11", - "@types/node": "^20.10.4", - "@typescript-eslint/eslint-plugin": "^6.14.0", - "@typescript-eslint/parser": "^6.14.0", + "@lemons_dev/parsinom": "^0.0.12", + "@happy-dom/global-registrator": "^14.3.6", + "@tsconfig/svelte": "^5.0.3", + "@types/bun": "^1.0.10", + "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/parser": "^7.3.1", "builtin-modules": "^3.3.0", - "esbuild": "^0.19.9", + "esbuild": "^0.20.2", + "esbuild-plugin-copy-watch": "^2.1.0", "esbuild-svelte": "^0.8.0", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-isaacscript": "^3.12.2", "eslint-plugin-only-warn": "^1.1.0", - "jest": "^29.7.0", - "jest-fetch-mock": "^3.0.3", - "obsidian": "^1.4.11", - "prettier": "3.1.1", - "svelte": "^4.2.8", - "svelte-preprocess": "^5.1.2", - "ts-jest": "^29.1.1", - "tslib": "2.6.2", - "typescript": "^5.3.3" + "obsidian": "latest", + "prettier": "^3.2.5", + "prettier-plugin-svelte": "^3.2.2", + "string-argv": "^0.3.2", + "svelte": "^4.2.12", + "svelte-check": "^3.6.8", + "svelte-preprocess": "^5.1.3", + "tslib": "^2.6.2", + "typescript": "^5.4.3" } } diff --git a/src/api/APIManager.ts b/src/api/APIManager.ts index 17f0f36..3340a67 100644 --- a/src/api/APIManager.ts +++ b/src/api/APIManager.ts @@ -21,8 +21,12 @@ export class APIManager { for (const api of this.apis) { if (apisToQuery.contains(api.apiName)) { - const apiRes = await api.searchByTitle(query); - res = res.concat(apiRes); + try { + const apiRes = await api.searchByTitle(query); + res = res.concat(apiRes); + } catch (e) { + console.warn(e); + } } } diff --git a/src/api/APIModel.ts b/src/api/APIModel.ts index 6230457..5ea4e23 100644 --- a/src/api/APIModel.ts +++ b/src/api/APIModel.ts @@ -1,6 +1,5 @@ import { MediaTypeModel } from '../models/MediaTypeModel'; import { MediaType } from '../utils/MediaType'; -import { MediaDbPluginSettings } from 'src/settings/Settings'; import MediaDbPlugin from '../main'; export abstract class APIModel { @@ -20,7 +19,10 @@ export abstract class APIModel { abstract getById(id: string): Promise; hasType(type: MediaType): boolean { - if (this.types.contains(type) && (Boolean((this.plugin.settings.apiToggle as any)?.[this.apiName]?.[type] as MediaDbPluginSettings) === true || (this.plugin.settings.apiToggle as any)?.[this.apiName]?.[type] as MediaDbPluginSettings === undefined)) { + if ( + this.types.contains(type) && + (Boolean((this.plugin.settings.apiToggle as any)?.[this.apiName]?.[type]) === true || (this.plugin.settings.apiToggle as any)?.[this.apiName]?.[type] === undefined) + ) { return true; } } diff --git a/src/api/apis/LocGovAPI.ts b/src/api/apis/LocGovAPI.ts deleted file mode 100644 index 97addde..0000000 --- a/src/api/apis/LocGovAPI.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { APIModel } from '../APIModel'; -import { MediaTypeModel } from '../../models/MediaTypeModel'; -import MediaDbPlugin from '../../main'; - -// WIP -export class LocGovAPI extends APIModel { - plugin: MediaDbPlugin; - typeMappings: Map; - - constructor(plugin: MediaDbPlugin) { - super(); - - this.plugin = plugin; - this.apiName = 'loc.gov API'; - this.apiDescription = 'A free API for the Library of Congress collections.'; - this.apiUrl = 'https://libraryofcongress.github.io/data-exploration/index.html'; - this.types = []; - this.typeMappings = new Map(); - // this.typeMappings.set('movie', 'movie'); - } - - async searchByTitle(title: string): Promise { - console.log(`MDB | api "${this.apiName}" queried by Title`); - - const searchUrl = `https://www.loc.gov/search/?q=${encodeURIComponent(title)}&fo=json&c=20`; - const fetchData = await fetch(searchUrl); - console.debug(fetchData); - - if (fetchData.status !== 200) { - throw Error(`MDB | Received status code ${fetchData.status} from an API.`); - } - - const data = await fetchData.json(); - console.debug(data); - let ret: MediaTypeModel[] = []; - - throw new Error('MDB | Under construction, API implementation not finished'); - - // return ret; - } - - async getById(id: string): Promise { - console.log(`MDB | api "${this.apiName}" queried by ID`); - - const searchUrl = `https://www.loc.gov/item/${encodeURIComponent(id)}/?fo=json`; - const fetchData = await fetch(searchUrl); - if (fetchData.status !== 200) { - throw Error(`MDB | Received status code ${fetchData.status} from an API.`); - } - - const data = await fetchData.json(); - console.debug(data); - const result = data.data; - - const type = this.typeMappings.get(result.type.toLowerCase()); - if (type === undefined) { - throw Error(`${result.type.toLowerCase()} is an unsupported type.`); - } - - throw new Error('MDB | Under construction, API implementation not finished'); - - // return; - } -} diff --git a/src/api/apis/SteamAPI.ts b/src/api/apis/SteamAPI.ts index 1f06ace..45182e0 100644 --- a/src/api/apis/SteamAPI.ts +++ b/src/api/apis/SteamAPI.ts @@ -41,7 +41,13 @@ export class SteamAPI extends APIModel { const filteredData = []; for (const app of data.applist.apps) { - if (app.name.toLowerCase().includes(title.toLowerCase())) { + if ( + app.name + .normalize('NFD') + .replace(/\p{Diacritic}/gu, '') + .toLowerCase() + .includes(title.toLowerCase()) + ) { filteredData.push(app); } if (filteredData.length > 20) { diff --git a/src/main.ts b/src/main.ts index 46f0b7d..60faa26 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,11 +22,19 @@ import { BoardGameGeekAPI } from './api/apis/BoardGameGeekAPI'; import { OpenLibraryAPI } from './api/apis/OpenLibraryAPI'; import { MobyGamesAPI } from './api/apis/MobyGamesAPI'; import { PropertyMapper } from './settings/PropertyMapper'; -import { YAMLConverter } from './utils/YAMLConverter'; import { MediaDbFolderImportModal } from './modals/MediaDbFolderImportModal'; import { PropertyMapping, PropertyMappingModel } from './settings/PropertyMapping'; import { ModalHelper, ModalResultCode, SearchModalOptions } from './utils/ModalHelper'; import { DateFormatter } from './utils/DateFormatter'; +import { MediaType } from 'src/utils/MediaType'; + +export type Metadata = Record; + +export interface MediaTypeModelObj { + id: string; + type: MediaType; + dataSource: string; +} export default class MediaDbPlugin extends Plugin { settings: MediaDbPluginSettings; @@ -186,7 +194,12 @@ export default class MediaDbPlugin extends Plugin { let apiSearchResults: MediaTypeModel[] = await this.modalHelper.openSearchModal(searchModalOptions ?? {}, async searchModalData => { types = searchModalData.types; const apis = this.apiManager.apis.filter(x => x.hasTypeOverlap(searchModalData.types)).map(x => x.apiName); - return await this.apiManager.query(searchModalData.query, apis); + try { + return await this.apiManager.query(searchModalData.query, apis); + } catch (e) { + console.warn(e); + return []; + } }); if (!apiSearchResults) { @@ -309,21 +322,40 @@ export default class MediaDbPlugin extends Plugin { generateMediaDbNoteFrontmatterPreview(mediaTypeModel: MediaTypeModel): string { const fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject()); - return this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata); + return stringifyYaml(fileMetadata); } + /** + * Generates the content of a note from a media model and some options. + * + * @param mediaTypeModel + * @param options + */ async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions): Promise { - let template = await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app); + const template = await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app); - if (this.settings.useDefaultFrontMatter || !template) { - return this.generateContentWithDefaultFrontMatter(mediaTypeModel, options, template); - } else { - return this.generateContentWithCustomFrontMatter(mediaTypeModel, options, template); - } + return this.generateContentWithDefaultFrontMatter(mediaTypeModel, options, template); + + // if (this.settings.useDefaultFrontMatter || !template) { + // return this.generateContentWithDefaultFrontMatter(mediaTypeModel, options, template); + // } else { + // return this.generateContentWithCustomFrontMatter(mediaTypeModel, options, template); + // } } async generateContentWithDefaultFrontMatter(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions, template?: string): Promise { - let fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject()); + let fileMetadata: Record; + + if (this.settings.useDefaultFrontMatter) { + fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject()); + } else { + fileMetadata = { + id: mediaTypeModel.id, + type: mediaTypeModel.type, + dataSource: mediaTypeModel.dataSource, + }; + } + let fileContent = ''; template = options.attachTemplate ? template : ''; @@ -331,27 +363,20 @@ export default class MediaDbPlugin extends Plugin { ({ fileMetadata, fileContent } = await this.attachTemplate(fileMetadata, fileContent, template)); if (this.settings.enableTemplaterIntegration && hasTemplaterPlugin(this.app)) { - // Only support stringifyYaml for templater plugin // Include the media variable in all templater commands by using a top level JavaScript execution command. fileContent = `---\n<%* const media = ${JSON.stringify(mediaTypeModel)} %>\n${stringifyYaml(fileMetadata)}---\n${fileContent}`; } else { - fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata)}---\n` + fileContent; + fileContent = `---\n${stringifyYaml(fileMetadata)}---\n${fileContent}`; } return fileContent; } async generateContentWithCustomFrontMatter(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions, template: string): Promise { - const frontMatterRegex = /^---*\n([\s\S]*?)\n---\h*/; + const regExp = new RegExp(this.frontMatterRexExpPattern); - const match = template.match(frontMatterRegex); - - if (!match || match.length !== 2) { - throw new Error('Cannot find YAML front matter for template.'); - } - - let frontMatter = parseYaml(match[1]); - let fileContent: string = template.replace(frontMatterRegex, ''); + const frontMatter = this.getMetaDataFromFileContent(template); + let fileContent: string = template.replace(regExp, ''); // Updating a previous file if (options.attachFile) { @@ -359,7 +384,7 @@ export default class MediaDbPlugin extends Plugin { // Use contents (below front matter) from previous file fileContent = await this.app.vault.read(options.attachFile); - const regExp = new RegExp(this.frontMatterRexExpPattern); + fileContent = fileContent.replace(regExp, ''); fileContent = fileContent.startsWith('\n') ? fileContent.substring(1) : fileContent; @@ -391,18 +416,19 @@ export default class MediaDbPlugin extends Plugin { // Include the media variable in all templater commands by using a top level JavaScript execution command. fileContent = `---\n<%* const media = ${JSON.stringify(mediaTypeModel)} %>\n${stringifyYaml(frontMatter)}---\n${fileContent}`; } else { - fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(frontMatter) : stringifyYaml(frontMatter)}---\n` + fileContent; + fileContent = `---\n${stringifyYaml(frontMatter)}---\n${fileContent}`; } return fileContent; } - async attachFile(fileMetadata: any, fileContent: string, fileToAttach?: TFile): Promise<{ fileMetadata: any; fileContent: string }> { + async attachFile(fileMetadata: Metadata, fileContent: string, fileToAttach?: TFile): Promise<{ fileMetadata: Metadata; fileContent: string }> { if (!fileToAttach) { return { fileMetadata: fileMetadata, fileContent: fileContent }; } const attachFileMetadata: any = this.getMetadataFromFileCache(fileToAttach); + // TODO: better object merging fileMetadata = Object.assign(attachFileMetadata, fileMetadata); let attachFileContent: string = await this.app.vault.read(fileToAttach); @@ -414,12 +440,13 @@ export default class MediaDbPlugin extends Plugin { return { fileMetadata: fileMetadata, fileContent: fileContent }; } - async attachTemplate(fileMetadata: any, fileContent: string, template: string): Promise<{ fileMetadata: any; fileContent: string }> { + async attachTemplate(fileMetadata: Metadata, fileContent: string, template: string): Promise<{ fileMetadata: Metadata; fileContent: string }> { if (!template) { return { fileMetadata: fileMetadata, fileContent: fileContent }; } - const templateMetadata: any = this.getMetaDataFromFileContent(template); + const templateMetadata: Metadata = this.getMetaDataFromFileContent(template); + // TODO: better object merging fileMetadata = Object.assign(templateMetadata, fileMetadata); const regExp = new RegExp(this.frontMatterRexExpPattern); @@ -429,8 +456,8 @@ export default class MediaDbPlugin extends Plugin { return { fileMetadata: fileMetadata, fileContent: fileContent }; } - getMetaDataFromFileContent(fileContent: string): any { - let metadata: any; + getMetaDataFromFileContent(fileContent: string): Metadata { + let metadata: Metadata; const regExp = new RegExp(this.frontMatterRexExpPattern); const frontMatterRegExpResult = regExp.exec(fileContent); @@ -455,15 +482,9 @@ export default class MediaDbPlugin extends Plugin { return metadata; } - getMetadataFromFileCache(file: TFile): any { - let metadata: any = this.app.metadataCache.getFileCache(file).frontmatter; - if (metadata) { - metadata = Object.assign({}, metadata); // copy - delete metadata.position; - } else { - metadata = {}; - } - return metadata; + getMetadataFromFileCache(file: TFile): Metadata { + const metadata: Metadata | undefined = this.app.metadataCache.getFileCache(file).frontmatter; + return structuredClone(metadata ?? {}); } /** @@ -513,7 +534,7 @@ export default class MediaDbPlugin extends Plugin { throw new Error('MDB | there is no active note'); } - let metadata: any = this.getMetadataFromFileCache(activeFile); + let metadata = this.getMetadataFromFileCache(activeFile); metadata = this.modelPropertyMapper.convertObjectBack(metadata); console.debug(`MDB | read metadata`, metadata); @@ -522,10 +543,12 @@ export default class MediaDbPlugin extends Plugin { throw new Error('MDB | active note is not a Media DB entry or is missing metadata'); } - const oldMediaTypeModel = this.mediaTypeManager.createMediaTypeModelFromMediaType(metadata, metadata.type); + const validOldMetadata: MediaTypeModelObj = metadata as unknown as MediaTypeModelObj; + + const oldMediaTypeModel = this.mediaTypeManager.createMediaTypeModelFromMediaType(validOldMetadata, validOldMetadata.type); // console.debug(oldMediaTypeModel); - let newMediaTypeModel = await this.apiManager.queryDetailedInfoById(metadata.id, metadata.dataSource); + let newMediaTypeModel = await this.apiManager.queryDetailedInfoById(validOldMetadata.id, validOldMetadata.dataSource); if (!newMediaTypeModel) { return; } @@ -533,8 +556,6 @@ export default class MediaDbPlugin extends Plugin { newMediaTypeModel = Object.assign(oldMediaTypeModel, newMediaTypeModel.getWithOutUserData()); // console.debug(newMediaTypeModel); - // deletion not happening anymore why is this log statement still here - console.debug('MDB | deleting old entry'); if (onlyMetadata) { await this.createMediaDbNoteFromModel(newMediaTypeModel, { attachFile: activeFile, folder: activeFile.parent, openNote: true }); } else { diff --git a/src/models/MediaTypeModel.ts b/src/models/MediaTypeModel.ts index 5418c73..c59e87f 100644 --- a/src/models/MediaTypeModel.ts +++ b/src/models/MediaTypeModel.ts @@ -31,12 +31,12 @@ export abstract class MediaTypeModel { abstract getTags(): string[]; - toMetaDataObject(): object { + toMetaDataObject(): Record { return { ...this.getWithOutUserData(), ...this.userData, tags: this.getTags().join('/') }; } - getWithOutUserData(): object { - const copy = Object.assign({}, this); + getWithOutUserData(): Record { + const copy = structuredClone(this) as Record; delete copy.userData; return copy; } diff --git a/src/models/WikiModel.ts b/src/models/WikiModel.ts index fa385bc..f325008 100644 --- a/src/models/WikiModel.ts +++ b/src/models/WikiModel.ts @@ -17,7 +17,7 @@ export class WikiModel extends MediaTypeModel { length: number; article: string; - userData: {}; + userData: Record; constructor(obj: any = {}) { super(); @@ -45,8 +45,8 @@ export class WikiModel extends MediaTypeModel { return MediaType.Wiki; } - override getWithOutUserData(): object { - const copy = Object.assign({}, this); + override getWithOutUserData(): Record { + const copy = structuredClone(this) as Record; delete copy.userData; delete copy.article; return copy; diff --git a/src/settings/Icon.svelte b/src/settings/Icon.svelte index 23f2002..47edb27 100644 --- a/src/settings/Icon.svelte +++ b/src/settings/Icon.svelte @@ -1,8 +1,8 @@ - - {#if iconName.length > 0}
{/if} + + diff --git a/src/settings/PropertyMapper.ts b/src/settings/PropertyMapper.ts index 1ca6a28..90f930f 100644 --- a/src/settings/PropertyMapper.ts +++ b/src/settings/PropertyMapper.ts @@ -15,15 +15,13 @@ export class PropertyMapper { * * @param obj */ - convertObject(obj: object): object { + convertObject(obj: Record): Record { if (!obj.hasOwnProperty('type')) { return obj; } - // @ts-ignore // console.log(obj.type); - // @ts-ignore if (MEDIA_TYPES.filter(x => x.toString() == obj.type).length < 1) { return obj; } @@ -31,7 +29,7 @@ export class PropertyMapper { // @ts-ignore const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type).properties; - const newObj: object = {}; + const newObj: Record = {}; for (const [key, value] of Object.entries(obj)) { for (const propertyMapping of propertyMappings) { @@ -59,20 +57,18 @@ export class PropertyMapper { * * @param obj */ - convertObjectBack(obj: object): object { + convertObjectBack(obj: Record): Record { if (!obj.hasOwnProperty('type')) { return obj; } - // @ts-ignore - if (MEDIA_TYPES.contains(obj.type)) { + if (MEDIA_TYPES.contains(obj.type as any)) { return obj; } - // @ts-ignore const propertyMappings = this.plugin.settings.propertyMappingModels.find(x => x.type === obj.type).properties; - const originalObj: object = {}; + const originalObj: Record = {}; objLoop: for (const [key, value] of Object.entries(obj)) { // first try if it is a normal property diff --git a/src/settings/PropertyMapping.ts b/src/settings/PropertyMapping.ts index e46fdc0..f122c83 100644 --- a/src/settings/PropertyMapping.ts +++ b/src/settings/PropertyMapping.ts @@ -68,7 +68,7 @@ export class PropertyMappingModel { }; } - getMappedProperties() { + getMappedProperties(): PropertyMapping[] { return this.properties.filter(x => x.mapping === PropertyMappingOption.Map); } diff --git a/src/settings/PropertyMappingModelComponent.svelte b/src/settings/PropertyMappingModelComponent.svelte index 7e13b20..ffa4805 100644 --- a/src/settings/PropertyMappingModelComponent.svelte +++ b/src/settings/PropertyMappingModelComponent.svelte @@ -1,12 +1,12 @@ - -
{capitalizeFirstLetter(model.type)}
- { #each model.properties as property } + {#each model.properties as property}
{property.property}
- { #if property.locked } -
- property can not be remapped -
- { :else } + {#if property.locked} +
property can not be remapped
+ {:else} - { #if property.mapping === PropertyMappingOption.Map } - + {#if property.mapping === PropertyMappingOption.Map} +
- +
- { /if } - { /if } + {/if} + {/if}
- { /each } + {/each}
- { #if !validationResult?.res } + {#if !validationResult?.res}
{validationResult?.err?.message}
- { /if } + {/if}
+ + diff --git a/src/settings/PropertyMappingModelsComponent.svelte b/src/settings/PropertyMappingModelsComponent.svelte index 10442b7..dace243 100644 --- a/src/settings/PropertyMappingModelsComponent.svelte +++ b/src/settings/PropertyMappingModelsComponent.svelte @@ -1,19 +1,15 @@ - -
- { #each models as model } - - { /each } + {#each models as model} + + {/each}
+ + diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index d5a3578..5d91d47 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -13,7 +13,6 @@ export interface MediaDbPluginSettings { OMDbKey: string; MobyGamesKey: string; sfwFilter: boolean; - useCustomYamlStringifier: boolean; templates: boolean; customDateFormat: string; openNoteInNewTab: boolean; @@ -24,18 +23,18 @@ export interface MediaDbPluginSettings { movie: boolean; series: boolean; game: boolean; - }, + }; MALAPI: { movie: boolean; series: boolean; - }, + }; SteamAPI: { game: boolean; - }, + }; MobyGamesAPI: { game: boolean; - } - }, + }; + }; movieTemplate: string; seriesTemplate: string; mangaTemplate: string; @@ -79,7 +78,6 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = { OMDbKey: '', MobyGamesKey: '', sfwFilter: true, - useCustomYamlStringifier: true, templates: true, customDateFormat: 'L', openNoteInNewTab: true, @@ -100,7 +98,7 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = { }, MobyGamesAPI: { game: true, - } + }, }, movieTemplate: '', seriesTemplate: '', @@ -190,7 +188,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.OMDbKey) .onChange(data => { this.plugin.settings.OMDbKey = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -202,7 +200,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.MobyGamesKey) .onChange(data => { this.plugin.settings.MobyGamesKey = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -212,17 +210,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.sfwFilter).onChange(data => { this.plugin.settings.sfwFilter = data; - this.plugin.saveSettings(); - }); - }); - - new Setting(containerEl) - .setName('YAML formatter') - .setDesc('Add optional quotation marks around strings in the metadata block.') - .addToggle(cb => { - cb.setValue(this.plugin.settings.useCustomYamlStringifier).onChange(data => { - this.plugin.settings.useCustomYamlStringifier = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -232,7 +220,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.templates).onChange(data => { this.plugin.settings.templates = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -241,10 +229,10 @@ export class MediaDbSettingTab extends PluginSettingTab { .setDesc( fragWithHTML( "Your custom date format. Use 'YYYY-MM-DD' for example.
" + - "For more syntax, refer to format reference.
" + - "Your current syntax looks like this: " + - this.plugin.dateFormatter.getPreview() + - '', + "For more syntax, refer to format reference.
" + + "Your current syntax looks like this: " + + this.plugin.dateFormatter.getPreview() + + '', ), ) .addText(cb => { @@ -254,7 +242,7 @@ export class MediaDbSettingTab extends PluginSettingTab { const newDateFormat = data ? data : DEFAULT_SETTINGS.customDateFormat; this.plugin.settings.customDateFormat = newDateFormat; document.getElementById('media-db-dateformat-preview').textContent = this.plugin.dateFormatter.getPreview(newDateFormat); // update preview - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -264,17 +252,17 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.openNoteInNewTab).onChange(data => { this.plugin.settings.openNoteInNewTab = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); new Setting(containerEl) .setName('Use default front matter') - .setDesc('Wheter to use the default front matter. If disabled, the front matter from the template will be used. Same as mapping everything to remove.') + .setDesc('Whether to use the default front matter. If disabled, the front matter from the template will be used. Same as mapping everything to remove.') .addToggle(cb => { cb.setValue(this.plugin.settings.useDefaultFrontMatter).onChange(data => { this.plugin.settings.useDefaultFrontMatter = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); // Redraw settings to display/remove the property mappings this.display(); }); @@ -288,11 +276,10 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.enableTemplaterIntegration).onChange(data => { this.plugin.settings.enableTemplaterIntegration = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); - containerEl.createEl('h3', { text: 'APIs Per Media Type' }); containerEl.createEl('h5', { text: 'Movies' }); new Setting(containerEl) @@ -301,7 +288,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.OMDbAPI.movie).onChange(data => { this.plugin.settings.apiToggle.OMDbAPI.movie = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); new Setting(containerEl) @@ -310,7 +297,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.MALAPI.movie).onChange(data => { this.plugin.settings.apiToggle.MALAPI.movie = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); containerEl.createEl('h5', { text: 'Series' }); @@ -320,7 +307,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.OMDbAPI.series).onChange(data => { this.plugin.settings.apiToggle.OMDbAPI.series = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); new Setting(containerEl) @@ -329,7 +316,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.MALAPI.series).onChange(data => { this.plugin.settings.apiToggle.MALAPI.series = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); containerEl.createEl('h5', { text: 'Games' }); @@ -339,7 +326,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.OMDbAPI.game).onChange(data => { this.plugin.settings.apiToggle.OMDbAPI.game = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); new Setting(containerEl) @@ -348,7 +335,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.SteamAPI.game).onChange(data => { this.plugin.settings.apiToggle.SteamAPI.game = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); new Setting(containerEl) @@ -357,7 +344,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .addToggle(cb => { cb.setValue(this.plugin.settings.apiToggle.MobyGamesAPI.game).onChange(data => { this.plugin.settings.apiToggle.MobyGamesAPI.game = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -372,7 +359,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.movieFolder) .onChange(data => { this.plugin.settings.movieFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -385,7 +372,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.seriesFolder) .onChange(data => { this.plugin.settings.seriesFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -398,7 +385,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.mangaFolder) .onChange(data => { this.plugin.settings.mangaFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -411,7 +398,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.gameFolder) .onChange(data => { this.plugin.settings.gameFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -424,7 +411,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.wikiFolder) .onChange(data => { this.plugin.settings.wikiFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -437,7 +424,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.musicReleaseFolder) .onChange(data => { this.plugin.settings.musicReleaseFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -450,7 +437,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.boardgameFolder) .onChange(data => { this.plugin.settings.boardgameFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); new Setting(containerEl) @@ -462,7 +449,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.bookFolder) .onChange(data => { this.plugin.settings.bookFolder = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); // endregion @@ -478,7 +465,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.movieTemplate) .onChange(data => { this.plugin.settings.movieTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -491,7 +478,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.seriesTemplate) .onChange(data => { this.plugin.settings.seriesTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -504,7 +491,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.mangaTemplate) .onChange(data => { this.plugin.settings.mangaTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -517,7 +504,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.gameTemplate) .onChange(data => { this.plugin.settings.gameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -530,7 +517,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.wikiTemplate) .onChange(data => { this.plugin.settings.wikiTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -543,7 +530,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.musicReleaseTemplate) .onChange(data => { this.plugin.settings.musicReleaseTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -556,7 +543,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.boardgameTemplate) .onChange(data => { this.plugin.settings.boardgameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -569,7 +556,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.bookTemplate) .onChange(data => { this.plugin.settings.bookTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); // endregion @@ -584,7 +571,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.movieFileNameTemplate) .onChange(data => { this.plugin.settings.movieFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -596,7 +583,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.seriesFileNameTemplate) .onChange(data => { this.plugin.settings.seriesFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -608,7 +595,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.mangaFileNameTemplate) .onChange(data => { this.plugin.settings.mangaFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -620,7 +607,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.gameFileNameTemplate) .onChange(data => { this.plugin.settings.gameFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -632,7 +619,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.wikiFileNameTemplate) .onChange(data => { this.plugin.settings.wikiFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -644,7 +631,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.musicReleaseFileNameTemplate) .onChange(data => { this.plugin.settings.musicReleaseFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -656,7 +643,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.boardgameFileNameTemplate) .onChange(data => { this.plugin.settings.boardgameFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); @@ -668,7 +655,7 @@ export class MediaDbSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.bookFileNameTemplate) .onChange(data => { this.plugin.settings.bookFileNameTemplate = data; - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }); }); // endregion @@ -709,7 +696,7 @@ export class MediaDbSettingTab extends PluginSettingTab { this.plugin.settings.propertyMappingModels = propertyMappingModels; new Notice(`MDB: Property Mappings for ${model.type} saved successfully.`); - this.plugin.saveSettings(); + void this.plugin.saveSettings(); }, }, }); diff --git a/src/settings/suggesters/Suggest.ts b/src/settings/suggesters/Suggest.ts index 331db6b..e64ebd3 100644 --- a/src/settings/suggesters/Suggest.ts +++ b/src/settings/suggesters/Suggest.ts @@ -165,7 +165,7 @@ export abstract class TextInputSuggest implements ISuggestOwner { (this.app).keymap.popScope(this.scope); this.suggest.setSuggestions([]); - this.popper.destroy(); + this.popper?.destroy(); this.suggestEl.detach(); } diff --git a/src/tests/ParameterizedAPI.test.ts b/src/tests/ParameterizedAPI.test.ts deleted file mode 100644 index a72430b..0000000 --- a/src/tests/ParameterizedAPI.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'; -import { MediaDbPluginSettings } from 'src/settings/Settings'; -import { LocGovAPI } from '../api/apis/LocGovAPI'; -import { MALAPI } from '../api/apis/MALAPI'; -import { MusicBrainzAPI } from '../api/apis/MusicBrainzAPI'; -import { OMDbAPI } from '../api/apis/OMDbAPI'; -import { SteamAPI } from '../api/apis/SteamAPI'; -import { WikipediaAPI } from '../api/apis/WikipediaAPI'; -import MediaDbPlugin from '../main'; -import { setMALResponseMock, setMusicBrainzResponseMock, setOMDbResponseMock, setSteamResponseMock, setWikipediaResponseMock } from './mockHelpers'; -import MALMockMovie from './ResponseMocks/MALMockMovie.json'; -import MusicBrainzResponseMock from './ResponseMocks/MusicBrainzMockResponse.json'; -import OMDBMockMovie from './ResponseMocks/OMDBMockResponse.json'; -import SteamAPIResponseMock from './ResponseMocks/SteamAPIMockResponse.json'; -import WikipediaMockResponse from './ResponseMocks/WikipediaMockResponse.json'; - -enableFetchMocks(); -export let apiMock: OMDbAPI | MALAPI | LocGovAPI | MusicBrainzAPI | SteamAPI | WikipediaAPI; - -describe.each([{ name: OMDbAPI }, { name: MALAPI }, { name: LocGovAPI }, { name: MusicBrainzAPI }, { name: SteamAPI }, { name: WikipediaAPI }])( - '$name.name', - ({ name: parameterizedApi }) => { - beforeAll(() => { - let settingsMock: MediaDbPluginSettings = {} as MediaDbPluginSettings; - let pluginMock = {} as MediaDbPlugin; - pluginMock.settings = settingsMock; - // TODO: add fake API key? - apiMock = new parameterizedApi(pluginMock); - }); - - beforeEach(() => { - fetchMock.resetMocks(); - }); - - test('searchByTitle behavior when API returns garbage data', async () => { - const garbageResponse = JSON.stringify({ - data: 'string', - }); - fetchMock.mockResponseOnce(garbageResponse); - await expect(apiMock.searchByTitle('sample')).resolves.toEqual([]); - // } - expect(fetch).toHaveBeenCalledTimes(1); - }); - - test('searchByTitle behavior when requestUrl/fetch returns 401', async () => { - let sampleResponse = { - data: 'string', - }; - fetchMock.mockResponse(JSON.stringify(sampleResponse), { status: 401 }); - // TODO: Check API name and fix message - // TODO: Externalize string - await expect(apiMock.searchByTitle('sample')).rejects.toThrow(`MDB | Received status code ${401} from an API.`); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - - test('searchByTitle behavior when requestUrl/fetch returns 403', async () => { - let sampleResponse = { - data: 'string', - }; - fetchMock.mockResponse(JSON.stringify(sampleResponse), { status: 403 }); - // TODO: Check API name and fix message - // TODO: Externalize string/import? - await expect(apiMock.searchByTitle('sample')).rejects.toThrow(`MDB | Received status code ${403} from an API.`); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - - test('searchByTitle behavior when requestUrl/fetch returns 200', async () => { - let sampleResponse; - let ret; - switch (parameterizedApi) { - case OMDbAPI: - ret = setOMDbResponseMock(); - sampleResponse = OMDBMockMovie; - break; - case WikipediaAPI: - ret = setWikipediaResponseMock(); - sampleResponse = WikipediaMockResponse; - break; - case MALAPI: - // TODO: MAL needs more tests for different types of content - ret = setMALResponseMock(); - sampleResponse = MALMockMovie; - case LocGovAPI: - // TODO: Add soon - break; - case SteamAPI: - sampleResponse = SteamAPIResponseMock; - ret = setSteamResponseMock(); - break; - case MusicBrainzAPI: - sampleResponse = MusicBrainzResponseMock; - ret = setMusicBrainzResponseMock(); - break; - default: - throw Error(); - } - fetchMock.mockResponse(JSON.stringify(sampleResponse), { status: 200 }); - // TODO: Check API name and fix message - // TODO: Externalize string - await expect(apiMock.searchByTitle('Hooking Season Playtest')).resolves.toEqual(ret); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - }, -); diff --git a/src/tests/ResponseMocks/MALMockMovie.json b/src/tests/ResponseMocks/MALMockMovie.json deleted file mode 100644 index 7a605fb..0000000 --- a/src/tests/ResponseMocks/MALMockMovie.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "data": [ - { - "mal_id": 2890, - "url": "https://myanimelist.net/anime/2890/Gake_no_Ue_no_Ponyo", - "title": "Gake no Ue no Ponyo", - "title_english": "Ponyo", - "type": "Movie", - "source": "Original", - "episodes": 1, - "aired": { - "from": "2008-07-19T00:00:00+00:00", - "to": null, - "prop": { - "from": { - "day": 19, - "month": 7, - "year": 2008 - }, - "to": { - "day": null, - "month": null, - "year": null - } - }, - "string": "Jul 19, 2008" - } - } - ] -} diff --git a/src/tests/ResponseMocks/MusicBrainzMockResponse.json b/src/tests/ResponseMocks/MusicBrainzMockResponse.json deleted file mode 100644 index ec89553..0000000 --- a/src/tests/ResponseMocks/MusicBrainzMockResponse.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "release-groups": [ - { - "id": "9cf08bf9-1948-4087-abe1-783210ea1fae", - "primary-type-id": "f529b476-6e62-324f-b0aa-1f3e33d313fc", - "title": "Halo Halo", - "first-release-date": "2013-07-08", - "primary-type": "Album", - "artist-credit": [ - { - "name": "Halo Halo", - "artist": { - "name": "Halo Halo" - } - } - ], - "releases": [ - { - "id": "58dd1d57-2201-472e-9e36-5d497dcedb6f", - "title": "Halo Halo" - } - ] - } - ] -} diff --git a/src/tests/ResponseMocks/OMDBMockResponse.json b/src/tests/ResponseMocks/OMDBMockResponse.json deleted file mode 100644 index 6f43f5b..0000000 --- a/src/tests/ResponseMocks/OMDBMockResponse.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Search": [ - { - "Title": "Guardians of the Galaxy", - "Year": "2014", - "imdbID": "tt2015381", - "Type": "movie", - "Poster": "https://m.media-amazon.com/images/M/MV5BMTAwMjU5OTgxNjZeQTJeQWpwZ15BbWU4MDUxNDYxODEx._V1_SX300.jpg" - } - ], - "totalResults": "1", - "Response": "True" -} diff --git a/src/tests/ResponseMocks/SteamAPIMockResponse.json b/src/tests/ResponseMocks/SteamAPIMockResponse.json deleted file mode 100644 index af2fc9c..0000000 --- a/src/tests/ResponseMocks/SteamAPIMockResponse.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "applist": { - "apps": [ - { - "appid": 2076590, - "name": "Hooking Season Playtest" - }, - { - "appid": 2076600, - "name": "MonsterTamer" - } - ] - } -} diff --git a/src/tests/ResponseMocks/WikipediaMockResponse.json b/src/tests/ResponseMocks/WikipediaMockResponse.json deleted file mode 100644 index 7f8e91e..0000000 --- a/src/tests/ResponseMocks/WikipediaMockResponse.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "query": { - "searchinfo": { - "totalhits": 1199001, - "suggestion": "book", - "suggestionsnippet": "book" - }, - "search": [ - { - "ns": 0, - "title": "Book", - "pageid": 3778, - "size": 68829, - "wordcount": 8821, - "snippet": "called books or chapters or parts, are parts. The intellectual content in a physical book need not be a composition, nor even be called a book. Books can", - "timestamp": "2022-08-19T19:13:56Z" - } - ] - } -} diff --git a/src/tests/TestAPI.ts b/src/tests/TestAPI.ts deleted file mode 100644 index db7d11e..0000000 --- a/src/tests/TestAPI.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { APIModel } from '../api/APIModel'; -import { MediaTypeModel } from '../models/MediaTypeModel'; -import MediaDbPlugin from '../main'; - -export class TestAPI extends APIModel { - plugin: MediaDbPlugin; - - constructor(plugin: MediaDbPlugin) { - super(); - - this.plugin = plugin; - this.apiName = 'TestAPI'; - this.apiDescription = 'A test API for automated testing.'; - this.apiUrl = ''; - this.types = []; - } - - async getById(id: string): Promise { - return undefined; - } - - async searchByTitle(title: string): Promise { - return [] as MediaTypeModel[]; - } -} diff --git a/src/tests/mockHelpers.ts b/src/tests/mockHelpers.ts deleted file mode 100644 index 190942b..0000000 --- a/src/tests/mockHelpers.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { GameModel } from '../models/GameModel'; -import { MovieModel } from '../models/MovieModel'; -import { MusicReleaseModel } from '../models/MusicReleaseModel'; -import { WikiModel } from '../models/WikiModel'; -import { MediaType } from '../utils/MediaType'; -import { apiMock } from './ParameterizedAPI.test'; -import MALMockMovie from './ResponseMocks/MALMockMovie.json'; -import MusicBrainzResponseMock from './ResponseMocks/MusicBrainzMockResponse.json'; -import OMDBMockMovie from './ResponseMocks/OMDBMockResponse.json'; -import SteamAPIResponseMock from './ResponseMocks/SteamAPIMockResponse.json'; -import WikipediaMockResponse from './ResponseMocks/WikipediaMockResponse.json'; - -export function setWikipediaResponseMock() { - let ret = []; - let wikiresponse = WikipediaMockResponse.query.search[0]; - ret.push( - new WikiModel({ - type: 'wiki', - title: wikiresponse.title, - englishTitle: wikiresponse.title, - year: '', - dataSource: apiMock.apiName, - id: wikiresponse.pageid, - }), - ); - return ret; -} - -export function setOMDbResponseMock() { - let ret = []; - let omdbresponse = OMDBMockMovie.Search[0]; - ret.push( - new MovieModel({ - type: 'wiki', - title: omdbresponse.Title, - englishTitle: omdbresponse.Title, - year: omdbresponse.Year, - dataSource: apiMock.apiName, - id: omdbresponse.imdbID, - }), - ); - return ret; -} - -export function setMALResponseMock() { - let ret = []; - let result = MALMockMovie.data[0]; - ret.push( - new MovieModel({ - type: result.type, - title: result.title, - englishTitle: result.title_english, - year: result.aired.prop.from.year, - dataSource: apiMock.apiName, - id: result.mal_id, - }), - ); - return ret; -} - -export function setSteamResponseMock() { - let ret = []; - let steamResponse = SteamAPIResponseMock.applist.apps[0]; - ret.push( - new GameModel({ - type: MediaType.Game, - title: steamResponse.name, - englishTitle: steamResponse.name, - year: '', - dataSource: apiMock.apiName, - id: steamResponse.appid, - }), - ); - return ret; -} - -export function setMusicBrainzResponseMock() { - let ret = []; - let result = MusicBrainzResponseMock['release-groups'][0]; - ret.push( - new MusicReleaseModel({ - type: 'musicRelease', - title: result.title, - englishTitle: result.title, - year: new Date(result['first-release-date']).getFullYear().toString(), - dataSource: apiMock.apiName, - url: '', - id: result.id, - - artists: result['artist-credit'].map((a: any) => a.name), - subType: result['primary-type'], - } as MusicReleaseModel), - ); - return ret; -} diff --git a/src/tests/utils.test.ts b/src/tests/utils.test.ts deleted file mode 100644 index f8a422c..0000000 --- a/src/tests/utils.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { containsOnlyLettersAndUnderscores, replaceIllegalFileNameCharactersInString, wrapAround } from '../utils/Utils'; - -test('If wrapAround wraps correctly', () => { - expect(wrapAround(100, 5)).toBe(0); - expect(wrapAround(100, 7)).toBe(2); -}); - -test('If wrapAround errors out when dividing by zero', () => { - expect(wrapAround(100, 0)).toThrow(); -}); - -test('If wrapAround errors out when size is negative', () => { - expect(wrapAround(100, -5)).toThrow(); -}); - -test('Letter and underscore string validity', () => { - expect(containsOnlyLettersAndUnderscores('asdkfj_')).toBe(true); - expect(containsOnlyLettersAndUnderscores('asdkfj0')).toBe(false); -}); - -// since this is used to check if a string is a valid name for an object property, unicode characters shouldn't be allowed, thus the name of the function is misleading -test('Letter and underscore unicode char test', () => { - expect(containsOnlyLettersAndUnderscores('asdkaÈj')).toBe(true); - expect(containsOnlyLettersAndUnderscores('asdkaÈj0')).toBe(false); -}); - -test('Valid filename test', () => { - expect(replaceIllegalFileNameCharactersInString('what?is\\this:')).toBe('whatisthis -'); -}); diff --git a/src/utils/MediaTypeManager.ts b/src/utils/MediaTypeManager.ts index 50ec761..54679af 100644 --- a/src/utils/MediaTypeManager.ts +++ b/src/utils/MediaTypeManager.ts @@ -28,7 +28,7 @@ export class MediaTypeManager { mediaTemplateMap: Map; mediaFolderMap: Map; - constructor() { } + constructor() {} updateTemplates(settings: MediaDbPluginSettings): void { this.mediaFileNameTemplateMap = new Map(); diff --git a/src/utils/ModalHelper.ts b/src/utils/ModalHelper.ts index f1e9acc..6a4efa1 100644 --- a/src/utils/ModalHelper.ts +++ b/src/utils/ModalHelper.ts @@ -5,7 +5,6 @@ import { MediaDbSearchResultModal } from '../modals/MediaDbSearchResultModal'; import { Notice } from 'obsidian'; import MediaDbPlugin from '../main'; import { MediaDbPreviewModal } from 'src/modals/MediaDbPreviewModal'; -import { CreateNoteOptions } from './Utils'; import { MediaDbSearchModal } from '../modals/MediaDbSearchModal'; import { MediaType } from './MediaType'; diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 56cb080..f6b4e9f 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -145,7 +145,9 @@ export function markdownTable(content: string[][]): string { return table; } -export const fragWithHTML = (html: string) => createFragment(frag => (frag.createDiv().innerHTML = html)); +export function fragWithHTML(html: string): DocumentFragment { + return createFragment(frag => (frag.createDiv().innerHTML = html)); +} export function dateToString(date: Date): string { return `${date.getMonth() + 1}-${date.getDate()}-${date.getFullYear()}`; @@ -207,13 +209,13 @@ export function unCamelCase(str: string): string { // space before last upper in a sequence followed by lower .replace(/\b([A-Z]+)([A-Z])([a-z])/, '$1 $2$3') // uppercase the first character - .replace(/^./, function(str) { + .replace(/^./, function (str) { return str.toUpperCase(); }) ); } -export function hasTemplaterPlugin(app: App) { +export function hasTemplaterPlugin(app: App): boolean { const templater = (app as any).plugins.plugins['templater-obsidian']; return !!templater; @@ -221,7 +223,7 @@ export function hasTemplaterPlugin(app: App) { // Copied from https://github.com/anpigon/obsidian-book-search-plugin // Licensed under the MIT license. Copyright (c) 2020 Jake Runzer -export async function useTemplaterPluginInFile(app: App, file: TFile) { +export async function useTemplaterPluginInFile(app: App, file: TFile): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any const templater = (app as any).plugins.plugins['templater-obsidian']; if (templater && !templater?.settings['trigger_on_file_creation']) { diff --git a/src/utils/YAMLConverter.ts b/src/utils/YAMLConverter.ts deleted file mode 100644 index 85ec21b..0000000 --- a/src/utils/YAMLConverter.ts +++ /dev/null @@ -1,43 +0,0 @@ -export class YAMLConverter { - static toYaml(obj: any): string { - let output = ''; - - for (const [key, value] of Object.entries(obj)) { - output += `${key}: ${YAMLConverter.toYamlString(value, 0)}\n`; - } - - return output; - } - - private static toYamlString(value: any, indentation: number): string { - if (value == null) { - return 'null'; - } - - if (typeof value === 'boolean') { - return value ? 'true' : 'false'; - } else if (typeof value === 'number') { - return value.toString(); - } else if (typeof value === 'string') { - return '"' + value.replace('"', '\\"') + '"'; - } else if (typeof value === 'object') { - let output = ''; - - if (Array.isArray(value)) { - for (const valueElement of value) { - output += `\n${YAMLConverter.calculateSpacing(indentation)} - ${YAMLConverter.toYamlString(valueElement, indentation + 1)}`; - } - } else { - for (const [objKey, objValue] of Object.entries(value)) { - output += `\n${YAMLConverter.calculateSpacing(indentation)} ${objKey}: ${YAMLConverter.toYamlString(objValue, indentation + 1)}`; - } - } - - return output; - } - } - - private static calculateSpacing(indentation: number): string { - return ' '.repeat(indentation * 4); - } -} diff --git a/tsconfig.json b/tsconfig.json index 162b4e8..c13a687 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,6 @@ { "compilerOptions": { - "types": ["svelte", "node", "jest"], "baseUrl": ".", - "inlineSourceMap": true, - "inlineSources": true, "module": "ESNext", "target": "ES6", "allowJs": true, @@ -11,9 +8,9 @@ "moduleResolution": "node", "importHelpers": true, "isolatedModules": true, - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "lib": ["DOM", "ES5", "ES6", "ES7", "Es2021"] + "lib": ["DOM", "ESNext"], + "types": ["svelte"], + "allowSyntheticDefaultImports": true }, - "include": ["**/*.ts"] + "include": ["src/**/*.ts", "tests/**/*.ts"] }