init
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
import { Plugin } from 'obsidian';
|
||||
import { ZeroQSettings, QoLModule } from './types';
|
||||
import { MODULES } from './modules';
|
||||
import { DEFAULT_SETTINGS, ZeroQSettingTab, getModuleSettings } from './settings';
|
||||
|
||||
export default class ZeroQoLModulesPlugin extends Plugin {
|
||||
settings: ZeroQSettings;
|
||||
private loadedModules: Set<string> = new Set();
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.addSettingTab(new ZeroQSettingTab(this.app, this));
|
||||
|
||||
for (const module of MODULES) {
|
||||
const moduleCfg = this.settings.modules[module.id];
|
||||
if (moduleCfg?.enabled) {
|
||||
this.loadModule(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onunload() {
|
||||
for (const module of MODULES) {
|
||||
this.unloadModule(module);
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData(),
|
||||
) as ZeroQSettings;
|
||||
|
||||
for (const module of MODULES) {
|
||||
if (!this.settings.modules[module.id]) {
|
||||
this.settings.modules[module.id] = {
|
||||
enabled: true,
|
||||
settings: { ...module.defaultSettings },
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
loadModule(module: QoLModule) {
|
||||
if (this.loadedModules.has(module.id)) return;
|
||||
|
||||
const moduleSettings = getModuleSettings(this.settings, module.id);
|
||||
module.onload(this, moduleSettings);
|
||||
this.loadedModules.add(module.id);
|
||||
}
|
||||
|
||||
unloadModule(module: QoLModule) {
|
||||
if (!this.loadedModules.has(module.id)) return;
|
||||
|
||||
module.onunload(this);
|
||||
this.loadedModules.delete(module.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import {
|
||||
App,
|
||||
Editor,
|
||||
MarkdownFileInfo,
|
||||
MarkdownView,
|
||||
Notice,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
normalizePath,
|
||||
} from 'obsidian';
|
||||
import type ZeroQoLModulesPlugin from '../../main';
|
||||
import { BaseModule } from '../base-module';
|
||||
|
||||
const MODULE_ID = 'attachment-clean-paste';
|
||||
|
||||
interface AttachmentCleanPasteSettings {
|
||||
extensions: string;
|
||||
}
|
||||
|
||||
export class AttachmentCleanPasteModule extends BaseModule {
|
||||
id = MODULE_ID;
|
||||
name = 'Attachment Clean Paste';
|
||||
description =
|
||||
'Заменяет ![[вложение]] на [[вложение|имя]] для указанных расширений при вставке/перетаскивании';
|
||||
|
||||
private app: App;
|
||||
|
||||
get defaultSettings(): Record<string, unknown> {
|
||||
return {
|
||||
extensions: 'png, jpg, jpeg, gif, svg, webp, bmp, pdf',
|
||||
};
|
||||
}
|
||||
|
||||
onload(plugin: ZeroQoLModulesPlugin, moduleSettings: Record<string, unknown>): void {
|
||||
this.app = plugin.app;
|
||||
const settings = moduleSettings as unknown as AttachmentCleanPasteSettings;
|
||||
|
||||
plugin.addSettingTab(
|
||||
new AttachmentCleanPasteSettingTab(plugin.app, plugin, MODULE_ID),
|
||||
);
|
||||
|
||||
const pasteRef = plugin.app.workspace.on(
|
||||
'editor-paste',
|
||||
this.createPasteHandler(settings),
|
||||
);
|
||||
this.registerCleanup(() => plugin.app.workspace.offref(pasteRef));
|
||||
|
||||
const dropRef = plugin.app.workspace.on(
|
||||
'editor-drop',
|
||||
this.createDropHandler(settings),
|
||||
);
|
||||
this.registerCleanup(() => plugin.app.workspace.offref(dropRef));
|
||||
}
|
||||
|
||||
private createPasteHandler(settings: AttachmentCleanPasteSettings) {
|
||||
return async (
|
||||
evt: ClipboardEvent,
|
||||
editor: Editor,
|
||||
_info: MarkdownView | MarkdownFileInfo,
|
||||
): Promise<void> => {
|
||||
const files = evt.clipboardData?.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const matchingFiles = Array.from(files).filter((f) =>
|
||||
this.shouldProcess(f.name, settings),
|
||||
);
|
||||
if (matchingFiles.length === 0) return;
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
for (const file of matchingFiles) {
|
||||
await this.processFile(file, editor, settings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private createDropHandler(settings: AttachmentCleanPasteSettings) {
|
||||
return async (
|
||||
evt: DragEvent,
|
||||
editor: Editor,
|
||||
_info: MarkdownView | MarkdownFileInfo,
|
||||
): Promise<void> => {
|
||||
const files = evt.dataTransfer?.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const matchingFiles = Array.from(files).filter((f) =>
|
||||
this.shouldProcess(f.name, settings),
|
||||
);
|
||||
if (matchingFiles.length === 0) return;
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
for (const file of matchingFiles) {
|
||||
await this.processFile(file, editor, settings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private shouldProcess(
|
||||
filename: string,
|
||||
settings: AttachmentCleanPasteSettings,
|
||||
): boolean {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
private async processFile(
|
||||
file: File,
|
||||
editor: Editor,
|
||||
_settings: AttachmentCleanPasteSettings,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const filename = file.name;
|
||||
const nameWithoutExt = filename.replace(/\.[^/.]+$/, '');
|
||||
|
||||
let attachmentFolder = this.app.vault.getConfig(
|
||||
'attachmentFolderPath',
|
||||
) as string;
|
||||
if (!attachmentFolder) attachmentFolder = 'attachments';
|
||||
|
||||
const folderPath = 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 = normalizePath(`${folderPath}/${nameWithoutExt}`);
|
||||
|
||||
const availablePath = await this.app.vault.getAvailablePath(
|
||||
basePath,
|
||||
ext,
|
||||
);
|
||||
|
||||
await this.app.vault.createBinary(availablePath, arrayBuffer);
|
||||
|
||||
const link = `[[${availablePath}|${nameWithoutExt}]]`;
|
||||
editor.replaceSelection(link);
|
||||
} catch (e) {
|
||||
new Notice(`Clean Paste error: ${e.message}`);
|
||||
console.error('Attachment Clean Paste:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentCleanPasteSettingTab extends PluginSettingTab {
|
||||
private plugin: ZeroQoLModulesPlugin;
|
||||
private moduleId: string;
|
||||
|
||||
constructor(app: App, plugin: ZeroQoLModulesPlugin, moduleId: string) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
this.moduleId = moduleId;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'Attachment Clean Paste' });
|
||||
containerEl.createEl('p', {
|
||||
text: 'Заменяет ![[вложение]] на [[вложение|имя]] для указанных расширений.',
|
||||
attr: { style: 'color: var(--text-muted); margin-bottom: 20px;' },
|
||||
});
|
||||
|
||||
const rawSettings = this.plugin.settings.modules[this.moduleId]?.settings as
|
||||
| AttachmentCleanPasteSettings
|
||||
| undefined;
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Расширения файлов')
|
||||
.setDesc(
|
||||
'Расширения через запятую (например: png, jpg, pdf, mp3). Файлы с этими расширениями будут вставляться как [[path/file|name]] вместо ![[path/file]].',
|
||||
)
|
||||
.addTextArea((textarea) => {
|
||||
textarea
|
||||
.setPlaceholder('png, jpg, jpeg, gif, svg, webp, pdf')
|
||||
.setValue(rawSettings?.extensions ?? '')
|
||||
.onChange(async (value) => {
|
||||
if (this.plugin.settings.modules[this.moduleId]) {
|
||||
this.plugin.settings.modules[this.moduleId].settings = {
|
||||
extensions: value,
|
||||
};
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
textarea.inputEl.rows = 4;
|
||||
textarea.inputEl.style.width = '100%';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Plugin } from 'obsidian';
|
||||
import { QoLModule } from '../types';
|
||||
|
||||
export abstract class BaseModule implements QoLModule {
|
||||
abstract id: string;
|
||||
abstract name: string;
|
||||
abstract description: string;
|
||||
|
||||
private cleanupFns: (() => void)[] = [];
|
||||
|
||||
get defaultSettings(): Record<string, unknown> {
|
||||
return {};
|
||||
}
|
||||
|
||||
abstract onload(plugin: Plugin, moduleSettings: Record<string, unknown>): void;
|
||||
|
||||
onunload(_plugin: Plugin): void {
|
||||
for (const fn of this.cleanupFns) {
|
||||
fn();
|
||||
}
|
||||
this.cleanupFns = [];
|
||||
}
|
||||
|
||||
protected registerCleanup(fn: () => void): void {
|
||||
this.cleanupFns.push(fn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { AttachmentCleanPasteModule } from './attachment-clean-paste';
|
||||
import { QoLModule } from '../types';
|
||||
|
||||
export const MODULES: QoLModule[] = [new AttachmentCleanPasteModule()];
|
||||
@@ -0,0 +1,74 @@
|
||||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import type ZeroQoLModulesPlugin from './main';
|
||||
import { MODULES } from './modules';
|
||||
import { ZeroQSettings } from './types';
|
||||
|
||||
const MODULE_DEFAULTS = MODULES.reduce(
|
||||
(acc, mod) => {
|
||||
acc[mod.id] = {
|
||||
enabled: true,
|
||||
settings: { ...mod.defaultSettings },
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{} as ZeroQSettings['modules'],
|
||||
);
|
||||
|
||||
export const DEFAULT_SETTINGS: ZeroQSettings = {
|
||||
modules: MODULE_DEFAULTS,
|
||||
};
|
||||
|
||||
export function getModuleSettings(
|
||||
settings: ZeroQSettings,
|
||||
moduleId: string,
|
||||
): Record<string, unknown> {
|
||||
return settings.modules[moduleId]?.settings ?? {};
|
||||
}
|
||||
|
||||
export class ZeroQSettingTab extends PluginSettingTab {
|
||||
plugin: ZeroQoLModulesPlugin;
|
||||
|
||||
constructor(app: App, plugin: ZeroQoLModulesPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h1', { text: 'ZeroQ QoL Modules' });
|
||||
containerEl.createEl('p', {
|
||||
text: 'Включайте и отключайте модули по своему усмотрению.',
|
||||
attr: { style: 'color: var(--text-muted); margin-bottom: 20px;' },
|
||||
});
|
||||
|
||||
for (const module of MODULES) {
|
||||
const moduleSettings = this.plugin.settings.modules[module.id];
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(module.name)
|
||||
.setDesc(module.description)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(moduleSettings?.enabled ?? true)
|
||||
.onChange(async (value) => {
|
||||
if (!this.plugin.settings.modules[module.id]) {
|
||||
this.plugin.settings.modules[module.id] = {
|
||||
enabled: value,
|
||||
settings: { ...module.defaultSettings },
|
||||
};
|
||||
} else {
|
||||
this.plugin.settings.modules[module.id].enabled = value;
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
if (value) {
|
||||
this.plugin.loadModule(module);
|
||||
} else {
|
||||
this.plugin.unloadModule(module);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Plugin } from 'obsidian';
|
||||
|
||||
export interface ModuleSettings {
|
||||
enabled: boolean;
|
||||
settings: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ZeroQSettings {
|
||||
modules: Record<string, ModuleSettings>;
|
||||
}
|
||||
|
||||
export interface QoLModule {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
defaultSettings: Record<string, unknown>;
|
||||
onload(plugin: Plugin, moduleSettings: Record<string, unknown>): void;
|
||||
onunload(plugin: Plugin): void;
|
||||
}
|
||||
Reference in New Issue
Block a user