diff --git a/main.js b/main.js new file mode 100644 index 0000000..4b39733 --- /dev/null +++ b/main.js @@ -0,0 +1,453 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => ZeroQoLModulesPlugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian4 = require("obsidian"); + +// src/modules/attachment-clean-paste/index.ts +var import_obsidian = require("obsidian"); + +// src/modules/base-module.ts +var BaseModule = class { + constructor() { + this.cleanupFns = []; + } + get defaultSettings() { + return {}; + } + onunload(_plugin) { + for (const fn of this.cleanupFns) { + fn(); + } + this.cleanupFns = []; + } + renderInlineSettings(_containerEl, _settings, _saveSettings) { + } + registerCleanup(fn) { + this.cleanupFns.push(fn); + } +}; + +// src/locales/en.ts +var en = { + settings: { + title: "ZeroQ QoL Modules", + description: "Enable or disable modules as you wish." + }, + modules: { + "attachment-clean-paste": { + name: "Attachment Clean Paste", + description: "Replaces ![[embed]] with [[path/file|name]] for configured file extensions on paste/drop", + descriptionShort: "Replaces ![[embed]] with [[path/file|name]] for specified file extensions.", + extensionsLabel: "File extensions", + extensionsDesc: "Comma-separated extensions (e.g.: png, jpg, pdf, mp3). Files with these extensions will be inserted as [[path/file|name]] instead of ![[path/file]].", + extensionsPlaceholder: "png, jpg, jpeg, gif, svg, webp, pdf" + }, + "preserve-link-aliases": { + name: "Preserve Link Aliases", + description: "Preserves original link aliases when attachments are renamed", + processEmbedsLabel: "Process embed links", + processEmbedsDesc: "Also process ![[embed]] links (disabled by default)", + processCanvasLabel: "Process Canvas files", + processCanvasDesc: "Also process links in Canvas (.canvas) files" + } + } +}; + +// src/locales/ru.ts +var ru = { + settings: { + title: "ZeroQ QoL Modules", + description: "\u0412\u043A\u043B\u044E\u0447\u0430\u0439\u0442\u0435 \u0438 \u043E\u0442\u043A\u043B\u044E\u0447\u0430\u0439\u0442\u0435 \u043C\u043E\u0434\u0443\u043B\u0438 \u043F\u043E \u0441\u0432\u043E\u0435\u043C\u0443 \u0443\u0441\u043C\u043E\u0442\u0440\u0435\u043D\u0438\u044E." + }, + modules: { + "attachment-clean-paste": { + name: "Attachment Clean Paste", + description: "\u0417\u0430\u043C\u0435\u043D\u044F\u0435\u0442 ![[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435]] \u043D\u0430 [[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435|\u0438\u043C\u044F]] \u0434\u043B\u044F \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0445 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0439 \u043F\u0440\u0438 \u0432\u0441\u0442\u0430\u0432\u043A\u0435/\u043F\u0435\u0440\u0435\u0442\u0430\u0441\u043A\u0438\u0432\u0430\u043D\u0438\u0438", + descriptionShort: "\u0417\u0430\u043C\u0435\u043D\u044F\u0435\u0442 ![[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435]] \u043D\u0430 [[\u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0435|\u0438\u043C\u044F]] \u0434\u043B\u044F \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u044B\u0445 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0439.", + extensionsLabel: "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u043E\u0432", + extensionsDesc: "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: png, jpg, pdf, mp3). \u0424\u0430\u0439\u043B\u044B \u0441 \u044D\u0442\u0438\u043C\u0438 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F\u043C\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432\u0441\u0442\u0430\u0432\u043B\u044F\u0442\u044C\u0441\u044F \u043A\u0430\u043A [[path/file|name]] \u0432\u043C\u0435\u0441\u0442\u043E ![[path/file]].", + extensionsPlaceholder: "png, jpg, jpeg, gif, svg, webp, pdf" + }, + "preserve-link-aliases": { + name: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435 \u0430\u043B\u0438\u0430\u0441\u043E\u0432 \u0441\u0441\u044B\u043B\u043E\u043A", + description: "\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442 \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0435 \u0430\u043B\u0438\u0430\u0441\u044B \u0441\u0441\u044B\u043B\u043E\u043A \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u0438\u0438 \u0432\u043B\u043E\u0436\u0435\u043D\u0438\u0439", + processEmbedsLabel: "\u041E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C embed-\u0441\u0441\u044B\u043B\u043A\u0438", + processEmbedsDesc: "\u0422\u0430\u043A\u0436\u0435 \u043E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C ![[embed]] \u0441\u0441\u044B\u043B\u043A\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0432\u044B\u043A\u043B\u044E\u0447\u0435\u043D\u043E)", + processCanvasLabel: "\u041E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C Canvas-\u0444\u0430\u0439\u043B\u044B", + processCanvasDesc: "\u0422\u0430\u043A\u0436\u0435 \u043E\u0431\u0440\u0430\u0431\u0430\u0442\u044B\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438 \u0432 Canvas (.canvas) \u0444\u0430\u0439\u043B\u0430\u0445" + } + } +}; + +// src/locales/index.ts +var locales = { en, ru }; +function detectLocale() { + const lang = (document.documentElement.lang || "en").slice(0, 2); + return locales[lang] || en; +} +var locale = detectLocale(); + +// src/modules/attachment-clean-paste/index.ts +var MODULE_ID = "attachment-clean-paste"; +var AttachmentCleanPasteModule = class extends BaseModule { + constructor() { + super(...arguments); + this.id = MODULE_ID; + this.name = locale.modules["attachment-clean-paste"].name; + this.description = locale.modules["attachment-clean-paste"].description; + } + get defaultSettings() { + return { + extensions: "png, jpg, jpeg, gif, svg, webp, bmp, pdf" + }; + } + onload(plugin, moduleSettings) { + this.app = plugin.app; + const rawSettings = moduleSettings; + const pasteRef = plugin.app.workspace.on( + "editor-paste", + this.createPasteHandler(rawSettings) + ); + this.registerCleanup(() => plugin.app.workspace.offref(pasteRef)); + const dropRef = plugin.app.workspace.on( + "editor-drop", + this.createDropHandler(rawSettings) + ); + this.registerCleanup(() => plugin.app.workspace.offref(dropRef)); + } + renderInlineSettings(containerEl, settings, saveSettings) { + const loc = locale.modules["attachment-clean-paste"]; + const extSettings = settings; + new import_obsidian.Setting(containerEl).setName(loc.extensionsLabel).setDesc(loc.extensionsDesc).addTextArea((textarea) => { + var _a; + textarea.setPlaceholder(loc.extensionsPlaceholder).setValue((_a = extSettings == null ? void 0 : extSettings.extensions) != null ? _a : "").onChange(async (value) => { + settings.extensions = value; + await saveSettings(); + }); + textarea.inputEl.rows = 4; + textarea.inputEl.style.width = "100%"; + }); + } + async processAll(files, editor, settings) { + const matchingFiles = Array.from(files).filter( + (f) => this.shouldProcess(f.name, settings) + ); + if (matchingFiles.length === 0) + return; + const links = []; + for (const file of matchingFiles) { + const link = await this.processFile(file, settings); + if (link) + links.push(link); + } + if (links.length === 0) + return; + editor.replaceSelection(links.join("\n\n")); + } + createPasteHandler(settings) { + return async (evt, editor, _info) => { + var _a; + const files = (_a = evt.clipboardData) == null ? void 0 : _a.files; + if (!files || files.length === 0) + return; + evt.preventDefault(); + evt.stopPropagation(); + await this.processAll(files, editor, settings); + }; + } + createDropHandler(settings) { + return async (evt, editor, _info) => { + var _a; + const files = (_a = evt.dataTransfer) == null ? void 0 : _a.files; + if (!files || files.length === 0) + return; + evt.preventDefault(); + evt.stopPropagation(); + await this.processAll(files, editor, settings); + }; + } + shouldProcess(filename, settings) { + const dotIndex = filename.lastIndexOf("."); + if (dotIndex === -1) + return false; + const ext = filename.slice(dotIndex + 1).toLowerCase(); + const exts = settings.extensions.split(",").map((e) => e.trim().toLowerCase()).filter((e) => e.length > 0); + return exts.some((e) => { + const cleanExt = e.startsWith(".") ? e.slice(1) : e; + return cleanExt === ext; + }); + } + async processFile(file, _settings) { + try { + const arrayBuffer = await file.arrayBuffer(); + const filename = file.name; + const nameWithoutExt = filename.replace(/\.[^/.]+$/, ""); + let attachmentFolder = this.app.vault.getConfig( + "attachmentFolderPath" + ); + if (!attachmentFolder) + attachmentFolder = "attachments"; + const folderPath = (0, import_obsidian.normalizePath)(attachmentFolder); + if (!await this.app.vault.adapter.exists(folderPath)) { + await this.app.vault.createFolder(folderPath); + } + const ext = filename.slice(filename.lastIndexOf(".") + 1); + const basePath = (0, import_obsidian.normalizePath)(`${folderPath}/${nameWithoutExt}`); + const availablePath = await this.app.vault.getAvailablePath( + basePath, + ext + ); + await this.app.vault.createBinary(availablePath, arrayBuffer); + return `[[${availablePath}|${nameWithoutExt}]]`; + } catch (e) { + new import_obsidian.Notice(`Clean Paste error: ${e.message}`); + console.error("Attachment Clean Paste:", e); + } + return null; + } +}; + +// src/modules/preserve-link-aliases/index.ts +var import_obsidian2 = require("obsidian"); +var MODULE_ID2 = "preserve-link-aliases"; +var PreserveLinkAliasesModule = class extends BaseModule { + constructor() { + super(...arguments); + this.id = MODULE_ID2; + this.name = locale.modules["preserve-link-aliases"].name; + this.description = locale.modules["preserve-link-aliases"].description; + } + get defaultSettings() { + return { + processEmbeds: false, + processCanvas: false + }; + } + onload(plugin, moduleSettings) { + this.vault = plugin.app.vault; + const rawSettings = moduleSettings; + const renameRef = plugin.app.vault.on( + "rename", + (file, oldPath) => this.handleRename(file, oldPath, rawSettings) + ); + this.registerCleanup(() => plugin.app.vault.offref(renameRef)); + } + renderInlineSettings(containerEl, settings, saveSettings) { + const loc = locale.modules["preserve-link-aliases"]; + const s = settings; + new import_obsidian2.Setting(containerEl).setName(loc.processEmbedsLabel).setDesc(loc.processEmbedsDesc).addToggle( + (toggle) => toggle.setValue(s.processEmbeds).onChange(async (value) => { + s.processEmbeds = value; + await saveSettings(); + }) + ); + new import_obsidian2.Setting(containerEl).setName(loc.processCanvasLabel).setDesc(loc.processCanvasDesc).addToggle( + (toggle) => toggle.setValue(s.processCanvas).onChange(async (value) => { + s.processCanvas = value; + await saveSettings(); + }) + ); + } + async handleRename(file, oldPath, settings) { + var _a; + const oldName = (_a = oldPath.split("/").pop()) != null ? _a : ""; + const newName = file.name; + const oldBasename = oldName.replace(/\.[^/.]+$/, ""); + const newBasename = newName.replace(/\.[^/.]+$/, ""); + if (oldBasename === newBasename) + return; + const files = this.getFilesToProcess(settings); + for (const f of files) { + const content = await this.vault.read(f); + const updated = this.processContent(content, newName, newBasename, oldBasename, settings); + if (updated !== content) { + await this.vault.modify(f, updated); + } + } + } + getFilesToProcess(settings) { + const files = []; + for (const file of this.vault.getMarkdownFiles()) { + files.push(file); + } + if (settings.processCanvas) { + const abstractFiles = this.vault.getFiles(); + for (const file of abstractFiles) { + if (file.extension === "canvas") { + files.push(file); + } + } + } + return files; + } + processContent(content, newFileName, newBasename, oldBasename, settings) { + const wikiLinkRe = /(!?)\[\[([^\]]*?)]]/g; + return content.replace(wikiLinkRe, (fullMatch, prefix, inner) => { + if (prefix === "!" && !settings.processEmbeds) { + return fullMatch; + } + const pipeIndex = inner.indexOf("|"); + if (pipeIndex === -1) + return fullMatch; + const linkPath = inner.slice(0, pipeIndex); + const alias = inner.slice(pipeIndex + 1); + if (alias !== newBasename) + return fullMatch; + const linkFilename = linkPath.split("/").pop(); + if (linkFilename !== newFileName) + return fullMatch; + return `${prefix}[[${linkPath}|${oldBasename}]]`; + }); + } +}; + +// src/modules/index.ts +var MODULES = [ + new AttachmentCleanPasteModule(), + new PreserveLinkAliasesModule() +]; + +// src/settings.ts +var import_obsidian3 = require("obsidian"); +var MODULE_DEFAULTS = MODULES.reduce( + (acc, mod) => { + acc[mod.id] = { + enabled: true, + settings: { ...mod.defaultSettings } + }; + return acc; + }, + {} +); +var DEFAULT_SETTINGS = { + modules: MODULE_DEFAULTS +}; +function getModuleSettings(settings, moduleId) { + var _a, _b; + return (_b = (_a = settings.modules[moduleId]) == null ? void 0 : _a.settings) != null ? _b : {}; +} +var ZeroQSettingTab = class extends import_obsidian3.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + display() { + const { containerEl } = this; + containerEl.empty(); + containerEl.createEl("h1", { text: locale.settings.title }); + containerEl.createEl("p", { + text: locale.settings.description, + attr: { style: "color: var(--text-muted); margin-bottom: 20px;" } + }); + for (const module2 of MODULES) { + const moduleCfg = this.plugin.settings.modules[module2.id]; + new import_obsidian3.Setting(containerEl).setName(module2.name).setDesc(module2.description).addToggle( + (toggle) => { + var _a; + return toggle.setValue((_a = moduleCfg == null ? void 0 : moduleCfg.enabled) != null ? _a : true).onChange(async (value) => { + if (!this.plugin.settings.modules[module2.id]) { + this.plugin.settings.modules[module2.id] = { + enabled: value, + settings: { ...module2.defaultSettings } + }; + } else { + this.plugin.settings.modules[module2.id].enabled = value; + } + await this.plugin.saveSettings(); + if (value) { + this.plugin.loadModule(module2); + } else { + this.plugin.unloadModule(module2); + } + }); + } + ); + if (moduleCfg) { + module2.renderInlineSettings( + containerEl, + moduleCfg.settings, + () => this.plugin.saveSettings() + ); + } + } + } +}; + +// src/main.ts +var ZeroQoLModulesPlugin = class extends import_obsidian4.Plugin { + constructor() { + super(...arguments); + this.loadedModules = /* @__PURE__ */ new Set(); + } + async onload() { + await this.loadSettings(); + this.addSettingTab(new ZeroQSettingTab(this.app, this)); + for (const module2 of MODULES) { + const moduleCfg = this.settings.modules[module2.id]; + if (moduleCfg == null ? void 0 : moduleCfg.enabled) { + this.loadModule(module2); + } + } + } + onunload() { + for (const module2 of MODULES) { + this.unloadModule(module2); + } + } + async loadSettings() { + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); + for (const module2 of MODULES) { + if (!this.settings.modules[module2.id]) { + this.settings.modules[module2.id] = { + enabled: true, + settings: { ...module2.defaultSettings } + }; + } + } + } + async saveSettings() { + await this.saveData(this.settings); + } + loadModule(module2) { + if (this.loadedModules.has(module2.id)) + return; + const moduleSettings = getModuleSettings(this.settings, module2.id); + module2.onload(this, moduleSettings); + this.loadedModules.add(module2.id); + } + unloadModule(module2) { + if (!this.loadedModules.has(module2.id)) + return; + module2.onunload(this); + this.loadedModules.delete(module2.id); + } +}; diff --git a/manifest.json b/manifest.json index 7b15900..7f632ff 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "zeroq-qol-modules", "name": "ZeroQ QoL Modules", - "version": "1.1.1", + "version": "1.1.3", "minAppVersion": "0.15.0", "description": "QoL Modules", "author": "oqyude", diff --git a/package.json b/package.json index e5e5512..56be7a8 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,17 @@ { "name": "zeroq-qol-modules", - "version": "1.1.1", + "version": "1.1.3", "description": "Modular Obsidian plugin with toggleable QoL modules", "main": "main.js", "scripts": { "dev": "node esbuild.config.mjs", - "build": "node esbuild.config.mjs production" + "build": "node esbuild.config.mjs production", + "release": "bash scripts/release.sh" }, - "keywords": ["obsidian", "plugin"], + "keywords": [ + "obsidian", + "plugin" + ], "author": "", "license": "MIT", "devDependencies": { diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100644 index 0000000..626214f --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +BUMP="${1:-patch}" + +CURRENT_VERSION=$(node -p "require('./manifest.json').version") + +if [[ "$BUMP" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + NEW_VERSION="$BUMP" +elif [[ "$BUMP" == "patch" ]]; then + NEW_VERSION=$(node -p " + const [a,b,c] = '$CURRENT_VERSION'.split('.').map(Number); + \`\${a}.\${b}.\${c+1}\` + ") +elif [[ "$BUMP" == "minor" ]]; then + NEW_VERSION=$(node -p " + const [a,b] = '$CURRENT_VERSION'.split('.').map(Number); + \`\${a}.\${b+1}.0\` + ") +elif [[ "$BUMP" == "major" ]]; then + NEW_VERSION=$(node -p " + const [a] = '$CURRENT_VERSION'.split('.').map(Number); + \`\${a+1}.0.0\` + ") +else + echo "Usage: $0 [patch|minor|major|]" + exit 1 +fi + +echo "Current: $CURRENT_VERSION" +echo "New: $NEW_VERSION" +read -p "Proceed? [y/N] " CONFIRM +if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then + echo "Aborted."; exit 1 +fi + +node -e " + const fs = require('fs'); + const m = require('./manifest.json'); m.version = '$NEW_VERSION'; + fs.writeFileSync('./manifest.json', JSON.stringify(m, null, '\t') + '\n'); + const p = require('./package.json'); p.version = '$NEW_VERSION'; + fs.writeFileSync('./package.json', JSON.stringify(p, null, '\t') + '\n'); +" + +npm run build + +git add -f manifest.json package.json main.js styles.css +git commit -m "v$NEW_VERSION" +git tag "v$NEW_VERSION" + +echo "" +echo "✔ Release v$NEW_VERSION ready." +echo "Run: git push && git push --tags"