mirror of
https://github.com/mntno/obsidian-come-down.git
synced 2026-07-22 05:43:15 +00:00
1.1.1-rc.1
This commit is contained in:
parent
fa5323f538
commit
a424f3e48c
27 changed files with 2460 additions and 721 deletions
13
.gitignore
vendored
13
.gitignore
vendored
|
|
@ -13,13 +13,14 @@ data.json
|
|||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
# Hot Reload plugin
|
||||
.hotreload
|
||||
|
||||
# This plugin
|
||||
|
||||
## The compiled main.js file.
|
||||
main.js
|
||||
## Files are copied here on `pnpm run build`.
|
||||
/dist/
|
||||
|
||||
## Created by plugin.
|
||||
## Sym-linked to the plugin's folder in the dev/test vault. Files are copied here `pnpm run dev`.
|
||||
## Git stores symlinks as a special file containing the target path string, not as a directory. So the gitignore rule needs to match a file, not a directory pattern.
|
||||
dev-vault
|
||||
|
||||
main.js
|
||||
cache.json
|
||||
|
|
|
|||
|
|
@ -2,4 +2,6 @@
|
|||
//
|
||||
// For a full list of overridable settings, and general information on folder-specific settings,
|
||||
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
|
||||
{}
|
||||
{
|
||||
"project_name": "Come Down"
|
||||
}
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -27,7 +27,7 @@ Having the cache located inside the plugin’s folder serves several purposes:
|
|||
- It allows the cache to be automatically deleted if the plugin is uninstalled.
|
||||
- A .gitignore file can be placed in the cache folder to exclude it from Git. Had it been in a visible folder, users might mistakenly think that files they add there would be committed.
|
||||
|
||||
### Embedded Images Are Already Cached
|
||||
### Embedded Images are Already Cached
|
||||
|
||||
Obsidian is built on Electron, which embeds the Chromium web browser. Therefore, it has built-in caching just as any web browser. But this cache exists outside your vault.
|
||||
|
||||
|
|
@ -37,17 +37,10 @@ For example, if you copied your vault's root folder to a USB memory and opened i
|
|||
|
||||
If the plugin is disabled, everything will work as it did before the plugin was enabled — the embedded images will load by means of the underlying browser. Upon enabling it again, the plugin's already cached items will be used, bypassing the browser.
|
||||
|
||||
Note that if an embedded image is removed from a note while the plugin is disabled, its potential cached file will not be removed until that specific note is opened again with the plugin enabled. If you want to disable the plugin you may delete the cache first from settings.
|
||||
Note that if an embedded image is removed from a note while the plugin is disabled, its potential cached file will not be removed until that specific note is opened again with the plugin enabled. If you want to disable the plugin, you may want to delete the cache from the settings first.
|
||||
|
||||
### Syncing
|
||||
|
||||
It is best to avoid caching on several devices without synchronizing in between, as this can lead to synchronization conflicts and inconsistencies. For example, you may see the same image download again on another device.
|
||||
|
||||
[^1]: This excludes [Obsidian Sync](https://obsidian.md/sync).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import esbuild from "esbuild";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import process from "process";
|
||||
|
||||
const banner =
|
||||
`/*
|
||||
|
|
@ -10,6 +12,10 @@ if you want to view the source, please visit the github repository of this plugi
|
|||
`;
|
||||
|
||||
const prod = (process.argv[2] === "production");
|
||||
const outdir = prod ? "dist" : "dev-vault";
|
||||
|
||||
fs.copyFileSync("manifest.json", path.join(outdir, "manifest.json"));
|
||||
fs.copyFileSync("styles.css", path.join(outdir, "styles.css"));
|
||||
|
||||
const context = await esbuild.context({
|
||||
banner: {
|
||||
|
|
@ -33,11 +39,12 @@ const context = await esbuild.context({
|
|||
"@lezer/lr",
|
||||
...builtins],
|
||||
format: "cjs",
|
||||
target: "es2022",
|
||||
// Runtime of min supported Obsidian version 1.8.0, see manifest.json
|
||||
target: "es2024",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
outdir: outdir,
|
||||
minify: prod,
|
||||
define: {
|
||||
"process.env.NODE_ENV": prod ? '"production"' : '"development"',
|
||||
|
|
|
|||
78
eslint.config.js
Normal file
78
eslint.config.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// https://typescript-eslint.io/packages/typescript-eslint#usage
|
||||
import eslint from "@eslint/js";
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import tseslint from "typescript-eslint";
|
||||
import globals from "globals";
|
||||
|
||||
//import obsidianmd from "eslint-plugin-obsidianmd";
|
||||
|
||||
// If using Svelte
|
||||
// import sveltePlugin from "eslint-plugin-svelte";
|
||||
// import svelteParser from "svelte-eslint-parser";
|
||||
|
||||
export default defineConfig(
|
||||
{
|
||||
ignores: [
|
||||
"**/build/**",
|
||||
"**/dist/**",
|
||||
"./main.js",
|
||||
"./src/**/*js",
|
||||
],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
// https://typescript-eslint.io/users/configs#recommended-configurations
|
||||
...tseslint.configs.recommended,
|
||||
//...obsidianmd.configs.recommended,
|
||||
{
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
plugins: {
|
||||
// https://typescript-eslint.io/packages/typescript-eslint#manual-usage
|
||||
"@typescript-eslint": tseslint.plugin,
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
sourceType: "module",
|
||||
ecmaVersion: "latest",
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
|
||||
// Instead of adding `obsidian-typings` package. Add whatever is used here instead.
|
||||
//createDiv: "readonly",
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
// You should always have "no-unused-vars": "off" alongside @typescript-eslint/no-unused-vars,
|
||||
// https://typescript-eslint.io/rules/no-unused-vars/
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"args": "all",
|
||||
"argsIgnorePattern": "^_",
|
||||
"caughtErrors": "all",
|
||||
"caughtErrorsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true,
|
||||
}],
|
||||
|
||||
//
|
||||
"@typescript-eslint/ban-ts-comment": ["error", {
|
||||
"ts-expect-error": false,
|
||||
"ts-ignore": true,
|
||||
"ts-nocheck": true,
|
||||
"ts-check": true,
|
||||
}],
|
||||
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-unnecessary-condition": ["warn", {
|
||||
// https://typescript-eslint.io/rules/no-unnecessary-condition/#only-allowed-literals
|
||||
"allowConstantLoopConditions": "only-allowed-literals"
|
||||
}],
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
|
||||
import typeScriptPlugin from "@typescript-eslint/eslint-plugin";
|
||||
import typeScriptParser from "@typescript-eslint/parser";
|
||||
|
||||
// If using Svelte
|
||||
// import sveltePlugin from "eslint-plugin-svelte";
|
||||
// import svelteParser from "svelte-eslint-parser";
|
||||
|
||||
|
||||
export default [
|
||||
{
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
plugins: {
|
||||
"@typescript-eslint": typeScriptPlugin,
|
||||
},
|
||||
languageOptions: {
|
||||
parser: typeScriptParser,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
sourceType: 'module',
|
||||
ecmaVersion: "latest",
|
||||
},
|
||||
globals: {
|
||||
...globals.browser,
|
||||
//...globals.node
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.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",
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"**/*js",
|
||||
"**/node_modules/**",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "come-down",
|
||||
"name": "Come Down",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.1-rc.1",
|
||||
"minAppVersion": "1.8.0",
|
||||
"description": "Maintains a cache of your notes’ embedded external images.",
|
||||
"author": "mntno",
|
||||
|
|
|
|||
29
package.json
29
package.json
|
|
@ -4,30 +4,31 @@
|
|||
"description": "An Obsidian plugin for caching embedded external images.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"lint": "pnpm eslint .",
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"build": "(pnpm eslint . || true) && tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "mntno",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "6.11.3",
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/state": "6.5.0",
|
||||
"@codemirror/view": "6.38.1",
|
||||
"@eslint/js": "9.37.0",
|
||||
"@lezer/common": "^1.2.3",
|
||||
"@types/node": "24.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.45.0",
|
||||
"@typescript-eslint/parser": "8.45.0",
|
||||
"builtin-modules": "5.0.0",
|
||||
"esbuild": "0.25.10",
|
||||
"globals": "16.4.0",
|
||||
"@codemirror/view": "6.38.6",
|
||||
"@eslint/js": "^9.39.4",
|
||||
"@lezer/common": "^1.5.2",
|
||||
"@types/node": "25.6.0",
|
||||
"builtin-modules": "5.1.0",
|
||||
"esbuild": "^0.28.0",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-plugin-obsidianmd": "^0.1.9",
|
||||
"globals": "17.5.0",
|
||||
"image-size": "^2.0.2",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.45.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^6.0.2",
|
||||
"typescript-eslint": "^8.58.1",
|
||||
"xxhash-wasm": "1.1.0"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
|
|||
2680
pnpm-lock.yaml
2680
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
14
src/Env.ts
14
src/Env.ts
|
|
@ -54,11 +54,13 @@ export const Env = {
|
|||
w: console.warn,
|
||||
e: console.error,
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
debug: DevContext.logCategory.DEBUGGING ? devLogger.info : noopLogger.info,
|
||||
edit: DevContext.logCategory.EDIT_UPDATE_PASS ? devLogger.info : noopLogger.info,
|
||||
read: DevContext.logCategory.POST_PROCESS_PASS ? devLogger.info : noopLogger.info,
|
||||
cm: DevContext.logCategory.CACHE_MANAGER ? devLogger.info : noopLogger.info,
|
||||
workaround: DevContext.logCategory.WORKAROUNDS ? devLogger.info : noopLogger.info,
|
||||
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
|
||||
},
|
||||
|
||||
perf: {
|
||||
|
|
@ -69,18 +71,22 @@ export const Env = {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* @param appOrCallback - Either a callback function to execute after the cache is cleared, or an App instance to reload Obsidian.
|
||||
*/
|
||||
clearBrowserCache: (appOrCallback: (() => void) | App) => {
|
||||
if (isDev && Platform.isDesktopApp) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
require('electron').remote.session.defaultSession.clearCache()
|
||||
.then(() => {
|
||||
if (appOrCallback instanceof App) {
|
||||
// @ts-expect-error
|
||||
app.commands.executeCommandById("app:reload");
|
||||
appOrCallback.commands.executeCommandById("app:reload");
|
||||
}
|
||||
else
|
||||
appOrCallback();
|
||||
})
|
||||
.catch((error: any) => console.error('Error clearing cache:', error));
|
||||
.catch((error: unknown) => console.error('Error clearing cache:', error));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -93,6 +99,10 @@ export const Env = {
|
|||
return false;
|
||||
},
|
||||
|
||||
obj: {
|
||||
is: (value: unknown): value is object => typeof value === "object" && value !== null, // `null` is an object
|
||||
} as const,
|
||||
|
||||
str: {
|
||||
EMPTY: "",
|
||||
SPACE: " ",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { CacheManager } from "cache/CacheManager";
|
||||
import { Env } from "Env";
|
||||
import { Platform, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||
import { Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||
import { Notice } from "ui/Notice";
|
||||
|
||||
export interface PluginSettings {
|
||||
|
|
@ -29,7 +29,7 @@ type SettingsChanged = (settings: PluginSettings) => void;
|
|||
export class SettingsManager {
|
||||
public settings: PluginSettings;
|
||||
public save: () => Promise<void>;
|
||||
public onChangedCallback: (name: string, value: any) => void | undefined;
|
||||
public onChangedCallback: (name: string, value: unknown) => void | undefined;
|
||||
|
||||
static readonly DEFAULT_SETTINGS: PluginSettings = {
|
||||
noticeOnDownload: true,
|
||||
|
|
@ -43,7 +43,7 @@ export class SettingsManager {
|
|||
gitIgnoreCacheDir: "gitIgnoreCacheDir",
|
||||
} as const;
|
||||
|
||||
constructor(settings: any, save: (settings: PluginSettings) => Promise<void>, onChangedCallback: (name: string, value: any) => void | undefined) {
|
||||
constructor(settings: PluginSettings, save: (settings: PluginSettings) => Promise<void>, onChangedCallback: (name: string, value: unknown) => void | undefined) {
|
||||
this.settings = settings;
|
||||
this.save = () => save(this.settings);
|
||||
this.onChangedCallback = onChangedCallback;
|
||||
|
|
@ -87,7 +87,7 @@ export class SettingTab extends PluginSettingTab {
|
|||
this.cacheManager.checkIfMetadataFileChangedExternally().then(() => this.render());
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
override hide(): void {
|
||||
Env.log.d("SettingTab:hide");
|
||||
if (this.isShown)
|
||||
this.settingsManager.unregisterOnChangedCallback(this.onChangedCallback);
|
||||
|
|
@ -102,7 +102,7 @@ export class SettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
let compactDownloadMessage: Setting | undefined;
|
||||
let compactDownloadMessage: Setting | undefined = undefined;
|
||||
const setCompactDownloadMessageVisibility = () => {
|
||||
if (settings.noticeOnDownload)
|
||||
compactDownloadMessage?.settingEl.show();
|
||||
|
|
@ -161,11 +161,7 @@ export class SettingTab extends PluginSettingTab {
|
|||
|
||||
button.buttonEl.remove();
|
||||
|
||||
if (Env.isDev && Platform.isDesktopApp) {
|
||||
require('electron').remote.session.defaultSession.clearCache()
|
||||
.then(() => new Notice('Electron Cache cleared successfully. Restart vault.'))
|
||||
.catch((error: any) => Env.log.e('Error clearing cache:', error));
|
||||
}
|
||||
Env.clearBrowserCache(() => new Notice('Electron Cache cleared successfully. Restart vault.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -198,7 +194,7 @@ export class SettingTab extends PluginSettingTab {
|
|||
settings.gitIgnoreCacheDir = value;
|
||||
await this.settingsManager.save();
|
||||
refreshDisabled();
|
||||
this.settingsManager?.onChangedCallback(SettingsManager.SETTING_NAME.gitIgnoreCacheDir, value);
|
||||
this.settingsManager.onChangedCallback(SettingsManager.SETTING_NAME.gitIgnoreCacheDir, value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
29
src/cache/CacheManager.ts
vendored
29
src/cache/CacheManager.ts
vendored
|
|
@ -84,7 +84,7 @@ export class CacheFetchError extends CacheError {
|
|||
|
||||
constructor(cacheKey: string, readonly error: Error | null, readonly info: { readonly sourceUrl: string, readonly statusCode?: number }) {
|
||||
|
||||
super(cacheKey, `Failed to fetch cache: ${cacheKey}. Status: ${info?.statusCode ?? "Unknown"}`, { cause: error });
|
||||
super(cacheKey, `Failed to fetch cache: ${cacheKey}. Status: ${info.statusCode ?? "Unknown"}`, { cause: error });
|
||||
|
||||
if (error) {
|
||||
if (error.message && error.message.includes("net::ERR_NAME_NOT_RESOLVED")) {
|
||||
|
|
@ -100,7 +100,7 @@ export class CacheFetchError extends CacheError {
|
|||
}
|
||||
}
|
||||
else {
|
||||
this.isRetryable = !this.info?.statusCode || (this.info?.statusCode >= 500 && this.info?.statusCode < 600);
|
||||
this.isRetryable = !this.info.statusCode || (this.info.statusCode >= 500 && this.info.statusCode < 600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,6 +136,7 @@ export class CacheManager {
|
|||
private readonly metadataFilePath: string;
|
||||
|
||||
private static hasher: XXHashAPI;
|
||||
private static isHasherInitialized = false;
|
||||
|
||||
private readonly filePathsToOmitWhenClearingCache: string[]
|
||||
|
||||
|
|
@ -148,8 +149,10 @@ export class CacheManager {
|
|||
|
||||
public static async create(vault: Vault, cacheDir: string, metadataFilePath: string, filePathsToOmitWhenClearingCache: string[] = []) {
|
||||
const instance = new this(vault, cacheDir, metadataFilePath, filePathsToOmitWhenClearingCache);
|
||||
if (!this.hasher)
|
||||
this.hasher = await xxhash(); // Seems to be quick.
|
||||
if (!CacheManager.isHasherInitialized) {
|
||||
CacheManager.hasher = await xxhash(); // Seems to be quick.
|
||||
CacheManager.isHasherInitialized = true;
|
||||
}
|
||||
|
||||
await instance.initCache(); // This can be called lazily if it turns out reading the json file takes too long time.
|
||||
|
||||
|
|
@ -172,8 +175,6 @@ export class CacheManager {
|
|||
try {
|
||||
await this.loadMetadata();
|
||||
this.cacheInitiated = true;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
this.initPromise = null; // Reset when done.
|
||||
}
|
||||
|
|
@ -235,7 +236,7 @@ export class CacheManager {
|
|||
Env.log.cm(Env.dev.icon.CACHE_MANAGER, "\t", "Current retain count: ", this.retainCount());
|
||||
|
||||
// If retainer/file does not exist / is not yet registered, it will be.
|
||||
let retainer = this.metadataRoot.retainers[retainerPath];
|
||||
const retainer = this.metadataRoot.retainers[retainerPath];
|
||||
const cacheKeysCurrentlyReferenced = retainer ? [...retainer.ref] : [];
|
||||
|
||||
// Populate set of keys to retain.
|
||||
|
|
@ -340,7 +341,7 @@ export class CacheManager {
|
|||
public renameRetainer(oldPath: string, path: string) {
|
||||
Env.log.cm(Env.dev.icon.CACHE_MANAGER, `CacheManager.renameRetainer\n\t${oldPath}`);
|
||||
|
||||
let retainer = this.metadataRoot.retainers[oldPath];
|
||||
const retainer = this.metadataRoot.retainers[oldPath];
|
||||
if (retainer !== undefined) {
|
||||
this.metadataRoot.retainers[path] = retainer;
|
||||
delete this.metadataRoot.retainers[oldPath];
|
||||
|
|
@ -354,7 +355,7 @@ export class CacheManager {
|
|||
public async removeRetainer(path: string) {
|
||||
Env.log.cm(Env.dev.icon.CACHE_MANAGER, `CacheManager.removeRetainer\n\t${path}`);
|
||||
|
||||
let retainer = this.metadataRoot.retainers[path];
|
||||
const retainer = this.metadataRoot.retainers[path];
|
||||
if (retainer === undefined) {
|
||||
// This can happen if a file which hasn't been open was deleted without opening it.
|
||||
Env.log.cm("\tFailed: No retainer found.");
|
||||
|
|
@ -430,7 +431,7 @@ export class CacheManager {
|
|||
// Increase count for each reference found.
|
||||
for (const cacheKey of retainer.ref) {
|
||||
// Note: make sure to `if (count !== undefined)` rather than `if(count)` otherwise `count` will not be treated as a `number` and addition will fail.
|
||||
let count = retainCounts[cacheKey];
|
||||
const count = retainCounts[cacheKey];
|
||||
Env.assert(count !== undefined, "Retainer is referencing a cache key that does not exist", cacheKey);
|
||||
if (count !== undefined)
|
||||
retainCounts[cacheKey] = count + 1;
|
||||
|
|
@ -452,7 +453,7 @@ export class CacheManager {
|
|||
* @returns `true` if the key is retained in {@link retainCounts}.
|
||||
*/
|
||||
private isRetained(retainCounts: Record<string, number>, key: string) {
|
||||
let count = retainCounts[key];
|
||||
const count = retainCounts[key];
|
||||
return count === undefined || count === 0 ? false : true; // Since 1.0.6, retain counts of zero are actually not present in the dictionary anymore.
|
||||
}
|
||||
|
||||
|
|
@ -497,7 +498,7 @@ export class CacheManager {
|
|||
|
||||
//
|
||||
const retainers: CacheRetainer[] = Object.values(this.metadataRoot.retainers);
|
||||
const numberOfRetainers = retainers.length;
|
||||
//const numberOfRetainers = retainers.length;
|
||||
|
||||
// Here the Mardown file has been deleted but its still exists as a retainer.
|
||||
const retainersWithoutActualFile = [];
|
||||
|
|
@ -787,7 +788,7 @@ export class CacheManager {
|
|||
* Aborts current download requests for the specified file.
|
||||
* @todo
|
||||
*/
|
||||
async cancelOngoing(filePath: string) {
|
||||
async cancelOngoing(_filePath: string) {
|
||||
await sleep(100);
|
||||
}
|
||||
|
||||
|
|
@ -987,7 +988,7 @@ export class CacheManager {
|
|||
return {
|
||||
w: sizeCalcResult.width,
|
||||
h: sizeCalcResult.height,
|
||||
t: sizeCalcResult?.type ?? "",
|
||||
t: sizeCalcResult.type ?? Env.str.EMPTY,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError)
|
||||
|
|
|
|||
11
src/main.ts
11
src/main.ts
|
|
@ -26,7 +26,7 @@ export default class ComeDownPlugin extends Plugin {
|
|||
private settingsManager!: SettingsManager;
|
||||
private cacheManager!: CacheManager;
|
||||
|
||||
async onload() {
|
||||
override async onload() {
|
||||
Env.log.d("Plugin:onload");
|
||||
|
||||
Notice.setName(this.manifest.name);
|
||||
|
|
@ -104,21 +104,20 @@ export default class ComeDownPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
async onunload() {
|
||||
override async onunload() {
|
||||
Env.log.d("Plugin:onunload");
|
||||
await this.cacheManager?.cancelAllOngoing();
|
||||
await this.cacheManager.cancelAllOngoing();
|
||||
}
|
||||
|
||||
public onExternalSettingsChange() {
|
||||
override onExternalSettingsChange() {
|
||||
Env.log.d("Plugin:onExternalSettingsChange");
|
||||
if (this.data) {
|
||||
|
||||
ComeDownPlugin.loadPluginData(this).then(data => {
|
||||
this.data = data;
|
||||
this.settingsManager.onSettingsChangedExternally(this.data.settings);
|
||||
setTimeout(() => this.cacheManager.checkIfMetadataFileChangedExternally(), 1000);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private static async loadPluginData(plugin: Plugin): Promise<PluginData> {
|
||||
Env.log.d("Plugin:loadPluginData");
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export class EditorViewPlugin {
|
|||
Env.log.d("EditorViewPlugin:constructor");
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
update(_update: ViewUpdate) {
|
||||
//Env.log.d("EditorViewPlugin:update", this.getViewMetadata(update.view));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,20 +4,23 @@ import { Logger as BaseLogger } from "utils/Logger";
|
|||
export class Logger extends BaseLogger {
|
||||
|
||||
public beginMsg(...args: unknown[]) {
|
||||
if (!Env.isDev) return "";
|
||||
const joinedArgs = args.length > 0 ? `(${args.join(" ")})` : "";
|
||||
return `${this.symbol} Begin pass. ${joinedArgs} ➡️🚪 ${this.idString}`;
|
||||
if (!Env.isDev)
|
||||
return Env.str.EMPTY;
|
||||
|
||||
return `${this.symbol} Begin pass. ${this.joinArgs(args)} ➡️🚪 ${this.idString}`;
|
||||
}
|
||||
|
||||
public endMsg(...args: unknown[]) {
|
||||
if (!Env.isDev) return "";
|
||||
const joinedArgs = args.length > 0 ? `(${args.join(" ")})` : "";
|
||||
return `${this.symbol} End pass. Finished. ${joinedArgs} ✅🚪➡️ ${this.idString}`;
|
||||
if (!Env.isDev)
|
||||
return Env.str.EMPTY;
|
||||
|
||||
return `${this.symbol} End pass. Finished. ${this.joinArgs(args)} ✅🚪➡️ ${this.idString}`;
|
||||
}
|
||||
|
||||
public abortMsg(...args: unknown[]) {
|
||||
if (!Env.isDev) return "";
|
||||
const joinedArgs = args.length > 0 ? `(${args.join(" ")})` : "";
|
||||
return `${this.symbol} End pass. Aborted. ${joinedArgs} ❌🚪➡️ ${this.idString}`;
|
||||
if (!Env.isDev)
|
||||
return Env.str.EMPTY;
|
||||
|
||||
return `${this.symbol} End pass. Aborted. ${this.joinArgs(args)} ❌🚪➡️ ${this.idString}`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class ProcessingContext {
|
|||
|
||||
this.vuCtx = underlyingCtx instanceof ViewUpdateContext ? underlyingCtx : null;
|
||||
this.ppCtx = underlyingCtx instanceof PostProcessorContext ? underlyingCtx : null;
|
||||
Env.dev.assert(this.vuCtx || this.ppCtx);
|
||||
Env.dev.assert(this.vuCtx !== null || this.ppCtx !== null);
|
||||
|
||||
this.viewContainerEl = viewContainerEl;
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ export class ProcessingContext {
|
|||
const contentEl = viewUpdate.view.contentDOM;
|
||||
let viewContainerEl: HTMLElement | null = null;
|
||||
|
||||
let view = ProcessingContext.tryGetViewInstance(app, contentEl);
|
||||
const view = ProcessingContext.tryGetViewInstance(app, contentEl);
|
||||
if (view)
|
||||
viewContainerEl = view.containerEl;
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ export class ProcessingContext {
|
|||
if (viewContainerEl === null)
|
||||
viewContainerEl = Arr.firstOrNull(ObsAssistant.viewContainerEl({ element: contentEl, dir: "up" }));
|
||||
|
||||
let viewingMode = ProcessingContext.tryGetViewingMode(view ?? undefined, viewContainerEl ?? undefined);
|
||||
const viewingMode = ProcessingContext.tryGetViewingMode(view ?? undefined, viewContainerEl ?? undefined);
|
||||
|
||||
let associatedFile: TFile | null = null;
|
||||
if (view !== null)
|
||||
|
|
@ -101,7 +101,7 @@ export class ProcessingContext {
|
|||
|
||||
let viewContainerEl: HTMLElement | null = null;
|
||||
|
||||
let view = ProcessingContext.tryGetViewInstance(app, element);
|
||||
const view = ProcessingContext.tryGetViewInstance(app, element);
|
||||
if (view)
|
||||
viewContainerEl = view.containerEl;
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export class ProcessingPass {
|
|||
|
||||
public retainCache(urlOrCache: string | CacheRequest) {
|
||||
if (this.ctx.isCacheAccessReadWrite()) {
|
||||
Env.assert(urlOrCache);
|
||||
Env.assert(Env.str.nonEmpty(urlOrCache) !== undefined || Env.obj.is(urlOrCache));
|
||||
this.ctx.logr.log(this.ctx.logr.t(() => this.ctx.logr.msg(`retainCache: Will retain ${CacheManager.createCacheKeyFromOriginalSrc(Env.str.is(urlOrCache) ? urlOrCache : urlOrCache.source)} at the end of pass`)));
|
||||
|
||||
if (Env.str.is(urlOrCache)) {
|
||||
|
|
@ -69,7 +69,7 @@ export class ProcessingPass {
|
|||
|
||||
public ignoreCache(src: string) {
|
||||
if (this.ctx.isCacheAccessReadWrite()) {
|
||||
Env.assert(src);
|
||||
Env.assert(Env.str.nonEmpty(src) !== undefined);
|
||||
this.ctx.logr.log(this.ctx.logr.t(() => this.ctx.logr.msg(`ignoreCache: Will ignore ${CacheManager.createCacheKeyFromOriginalSrc(src)} at the end of pass`)));
|
||||
|
||||
this.requestsToIgnore.add(CacheManager.createRequest(src, this.ctx.associatedFile.path));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ViewUpdate } from "@codemirror/view";
|
||||
import { Env } from "Env";
|
||||
import { HtmlAssistant, HTMLElementAttribute, HTMLElementCacheState } from "processing/HtmlAssistant";
|
||||
import { HtmlAssistant, HTMLElementCacheState } from "processing/HtmlAssistant";
|
||||
import { Url } from "utils/Url";
|
||||
|
||||
export class Workarounds {
|
||||
|
|
@ -29,7 +29,7 @@ export class Workarounds {
|
|||
if (update.changes.empty)
|
||||
return null;
|
||||
|
||||
var sourcesToIgnore: string[] = [];
|
||||
const sourcesToIgnore: string[] = [];
|
||||
|
||||
update.changes.iterChanges((_fromA, _toA, cursorStartPos, cursorEndPosition, inserted) => {
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ export class Workarounds {
|
|||
* Will not match if there's no link because an img element is not inserted until the link
|
||||
* begins with `http:`, `https:`, `ftp:`, `ws:`, or `wss:`.
|
||||
*/
|
||||
private static readonly markdownLinkRegex = /(?<!\!)\[.*?\]\((.+?)\)/;
|
||||
private static readonly markdownLinkRegex = /(?<!!)\[.*?\]\((.+?)\)/;
|
||||
|
||||
/**
|
||||
* Since image elements are not available in {@link detectSourcesOfInvalidImageElements}, this method is used with the result of that when the elements are available.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Global (see tsconfig `include`)
|
||||
// Do not add `export`
|
||||
// types.ts - Global type declarations
|
||||
|
||||
type Prettify<T> = {
|
||||
// Global type aliases
|
||||
|
||||
export type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
} & {};
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export class InfoModal extends Modal {
|
|||
clearCacheButton!: ButtonComponent;
|
||||
closeButton!: ButtonComponent;
|
||||
|
||||
onOpen(): void {
|
||||
override onOpen(): void {
|
||||
super.onOpen();
|
||||
this.cacheManager.registerMetadataChanged(this.onMetadataChangedCallback);
|
||||
setTimeout(() => {
|
||||
|
|
@ -81,7 +81,7 @@ export class InfoModal extends Modal {
|
|||
this.populate();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
override onClose(): void {
|
||||
super.onClose();
|
||||
this.cacheManager.unregisterMetadataChanged(this.onMetadataChangedCallback);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ensureSyntaxTree, syntaxTree } from "@codemirror/language";
|
||||
import { EditorView, ViewUpdate } from "@codemirror/view";
|
||||
import { SyntaxNodeRef, Tree } from "@lezer/common";
|
||||
import { Tree } from "@lezer/common";
|
||||
import { Env } from "Env";
|
||||
|
||||
export interface ParsedImage {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export class Logger {
|
|||
public readonly log: LoggerFn;
|
||||
public readonly id: number;
|
||||
public readonly symbol: string;
|
||||
public readonly t = (action: () => string) => Env.isDev ? action() : "";
|
||||
public readonly t = (action: () => string) => Env.isDev ? action() : Env.str.EMPTY;
|
||||
|
||||
constructor(log: LoggerFn, id: number, symbol: string) {
|
||||
this.log = log;
|
||||
|
|
@ -17,9 +17,15 @@ export class Logger {
|
|||
return "— ID" + this.id;
|
||||
}
|
||||
|
||||
protected joinArgs(args: unknown[]): string {
|
||||
return args.length === 0 ? Env.str.EMPTY : `(${args.join(" ")})`;
|
||||
}
|
||||
|
||||
public msg(...args: unknown[]) {
|
||||
if (!Env.isDev) return "";
|
||||
if (!Env.isDev)
|
||||
return Env.str.EMPTY;
|
||||
|
||||
const mappedArgs = args.map(arg => arg === undefined ? "undefined" : arg);
|
||||
return `${this.symbol} ${mappedArgs.join(" ")} ${this.idString}`;
|
||||
return `${this.symbol} ${mappedArgs.join(Env.str.SPACE)} ${this.idString}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Env } from "Env";
|
||||
import { App, FileView, getIcon, ItemView, MarkdownPostProcessorContext, MarkdownView, TextFileView, TFile, View } from "obsidian";
|
||||
import { Prettify } from "types";
|
||||
import { childEl, firstChildEl, firstParentEl } from "utils/dom";
|
||||
import { Arr } from "utils/ts";
|
||||
|
||||
export type ObsViewMode = "none" | "reader" | "preview" | "source";
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ export class ObsAssistant {
|
|||
/** Internal API */
|
||||
public static containerElFromPostProcessorContext(postProcessorContext: MarkdownPostProcessorContext) {
|
||||
Env.assert("containerEl" in postProcessorContext, "MarkdownPostProcessorContext does not have `containerEl`");
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const containerEl = postProcessorContext.containerEl;
|
||||
if (containerEl)
|
||||
return containerEl as HTMLElement;
|
||||
|
|
@ -89,31 +89,37 @@ export class ObsAssistant {
|
|||
let element: HTMLElement[] | null = null;
|
||||
|
||||
const findChildOrChildren = (options as TryGetAllOptions).allDescendants === true ? childEl : firstChildEl;
|
||||
|
||||
const find = (el: HTMLElement) => {
|
||||
if (options.dir === "down") {
|
||||
element = Arr.orNull(findChildOrChildren(el, selectors));
|
||||
}
|
||||
else if (options.dir === "up") {
|
||||
element = Arr.orNull(firstParentEl(el, selectors));
|
||||
}
|
||||
else if (options.dir === "downup") {
|
||||
element = Arr.orNull(findChildOrChildren(el, selectors));
|
||||
|
||||
const orNull = (v: HTMLElement[] | HTMLElement | null): HTMLElement[] | null => v === null
|
||||
? null
|
||||
: (Array.isArray(v) ? v : [v]);
|
||||
|
||||
switch (options.dir) {
|
||||
case "down":
|
||||
return orNull(findChildOrChildren(el, selectors));
|
||||
case "up":
|
||||
return orNull(firstParentEl(el, selectors));
|
||||
case "downup": {
|
||||
let element = orNull(findChildOrChildren(el, selectors));
|
||||
if (element === null)
|
||||
element = Arr.orNull(firstParentEl(el, selectors));
|
||||
element = orNull(firstParentEl(el, selectors));
|
||||
return element;
|
||||
}
|
||||
else if (options.dir === "updown") {
|
||||
element = Arr.orNull(firstParentEl(el, selectors));
|
||||
case "updown": {
|
||||
let element = orNull(firstParentEl(el, selectors));
|
||||
if (element === null)
|
||||
element = Arr.orNull(findChildOrChildren(el, selectors));
|
||||
element = orNull(findChildOrChildren(el, selectors));
|
||||
return element;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (options.view !== undefined) {
|
||||
if (options.view instanceof ItemView)
|
||||
find(options.view.contentEl);
|
||||
element = find(options.view.contentEl);
|
||||
else
|
||||
find(options.view.containerEl);
|
||||
element = find(options.view.containerEl);
|
||||
}
|
||||
|
||||
if (element === null && options.postProcessorContext) {
|
||||
|
|
@ -229,7 +235,7 @@ export class ObsAssistant {
|
|||
return null;
|
||||
|
||||
const iconColor = color ?? getComputedStyle(el).getPropertyValue(CssVar.Icon.COLOR).trim();
|
||||
Env.assert(iconColor, "CSS variable not found:", CssVar.Icon.COLOR);
|
||||
Env.assert(Env.str.nonEmpty(iconColor) !== undefined, "CSS variable not found:", CssVar.Icon.COLOR);
|
||||
icon.setAttribute("stroke", iconColor || fallbackColor);
|
||||
icon.setAttribute("stroke-width", "1");
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export class Url {
|
|||
try {
|
||||
const url = new URL(src);
|
||||
return url.protocol === "http:" || url.protocol === "https:";
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
|
||||
export const Arr = {
|
||||
firstOrNull: <T>(a: Array<T>): T | null => a.first() ?? null,
|
||||
orNull: <T>(v: T[] | T | null): T[] | null =>
|
||||
v === null
|
||||
? null
|
||||
: (Array.isArray(v) ? v : [v]),
|
||||
} as const;
|
||||
|
||||
export const Err = {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,47 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
// Enable to check TS7 compatibility.
|
||||
"stableTypeOrdering": false,
|
||||
|
||||
"rootDir": "./src",
|
||||
"paths": {
|
||||
"*": ["./src/*"],
|
||||
"#/*": ["./src/*"]
|
||||
},
|
||||
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES2022",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
|
||||
"target": "ES2024",
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES2024"
|
||||
],
|
||||
"types": ["node"],
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
|
||||
"importHelpers": true,
|
||||
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"lib": ["DOM", "ES2022"]
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"erasableSyntaxOnly": false,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
|
||||
// For ESLint to handle:
|
||||
"allowUnreachableCode": true,
|
||||
"allowUnusedLabels": true,
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"./src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"./node_modules",
|
||||
"./dist",
|
||||
"./build",
|
||||
"./main.js",
|
||||
"./eslint.config.js"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"1.1.1": "1.8.0",
|
||||
"1.1.0": "1.8.0",
|
||||
"1.0.7": "1.8.0",
|
||||
"1.0.6": "1.8.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue