Compare commits
6
Commits
v0.0.1
...
4cf5e9da4a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf5e9da4a | ||
|
|
d546947e61 | ||
|
|
801fdec71e | ||
|
|
e5f1061db7 | ||
|
|
23cf742569 | ||
|
|
2dec5660f6 |
@@ -1,63 +0,0 @@
|
|||||||
name: Draft Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
jobs:
|
|
||||||
draft:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build
|
|
||||||
- run: zip -j zeroq-qol-modules.zip main.js manifest.json styles.css
|
|
||||||
- name: Create or update draft release
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
|
||||||
TAG="latest-build"
|
|
||||||
SHA="${GITHUB_SHA}"
|
|
||||||
|
|
||||||
EXISTING=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/releases" \
|
|
||||||
| jq '.[] | select(.tag_name == "'"${TAG}"'" and .draft == true) | .id')
|
|
||||||
|
|
||||||
if [ -z "$EXISTING" ]; then
|
|
||||||
echo "Creating new draft release..."
|
|
||||||
RESP=$(curl -s -X POST -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"tag_name": "'"${TAG}"'",
|
|
||||||
"name": "Latest Build from master",
|
|
||||||
"body": "Автоматическая сборка из ветки `master`.\n\nКоммит: '"${SHA}"'",
|
|
||||||
"draft": true,
|
|
||||||
"prerelease": true
|
|
||||||
}' "${API}/releases")
|
|
||||||
RELEASE_ID=$(echo "$RESP" | jq -r '.id')
|
|
||||||
else
|
|
||||||
echo "Updating existing draft release (id: ${EXISTING})..."
|
|
||||||
RELEASE_ID=$EXISTING
|
|
||||||
|
|
||||||
curl -s -X PATCH -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"body": "Автоматическая сборка из ветки `master`.\n\nКоммит: '"${SHA}"'"
|
|
||||||
}' "${API}/releases/${RELEASE_ID}"
|
|
||||||
|
|
||||||
ASSETS=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/releases/${RELEASE_ID}/assets" | jq -r '.[].id')
|
|
||||||
for AID in $ASSETS; do
|
|
||||||
curl -s -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/releases/${RELEASE_ID}/assets/${AID}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Uploading artifact..."
|
|
||||||
curl -s -X POST -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-F "attachment=@zeroq-qol-modules.zip" \
|
|
||||||
"${API}/releases/${RELEASE_ID}/assets"
|
|
||||||
echo "Done."
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Verify version
|
||||||
|
run: |
|
||||||
|
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
MANIFEST_VERSION=$(node -p "require('./manifest.json').version")
|
||||||
|
if [ "$TAG_VERSION" != "$MANIFEST_VERSION" ]; then
|
||||||
|
echo "❌ Tag ($TAG_VERSION) != manifest ($MANIFEST_VERSION)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: ${{ github.ref_name }}
|
||||||
|
files: |
|
||||||
|
main.js
|
||||||
|
manifest.json
|
||||||
|
styles.css
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -2,35 +2,71 @@
|
|||||||
|
|
||||||
Модульный плагин для **Obsidian** с набором QoL-функций, которые можно включать и отключать в настройках.
|
Модульный плагин для **Obsidian** с набором QoL-функций, которые можно включать и отключать в настройках.
|
||||||
|
|
||||||
## Установка
|
Modular **Obsidian** plugin with toggleable QoL modules in settings.
|
||||||
|
|
||||||
|
## Установка / Installation
|
||||||
|
|
||||||
1. Скачайте `main.js`, `manifest.json` и `styles.css` из [релизов](https://github.com/your/repo/releases)
|
1. Скачайте `main.js`, `manifest.json` и `styles.css` из [релизов](https://github.com/your/repo/releases)
|
||||||
2. Поместите их в папку `.obsidian/plugins/zeroq-qol-modules/` вашего хранилища
|
2. Поместите их в папку `.obsidian/plugins/zeroq-qol-modules/` вашего хранилища
|
||||||
3. В Obsidian: **Настройки** → **Community plugins** → **Включить**
|
3. В Obsidian: **Настройки** → **Community plugins** → **Включить**
|
||||||
4. Активируйте плагин **ZeroQ QoL Modules**
|
4. Активируйте плагин **ZeroQ QoL Modules**
|
||||||
|
|
||||||
## Модули
|
## Язык / Language
|
||||||
|
|
||||||
| Модуль | Описание |
|
Язык плагина синхронизируется с **языком интерфейса Obsidian** (Settings → About → Language).
|
||||||
|--------|----------|
|
|
||||||
| **Attachment Clean Paste** | Заменяет `![[вложение]]` на `[[вложение|имя]]` при вставке/перетаскивании файлов с указанными расширениями |
|
- Obsidian на русском → плагин на русском
|
||||||
|
- Obsidian на другом языке → плагин на английском
|
||||||
|
|
||||||
|
The plugin language syncs with **Obsidian's UI language** setting.
|
||||||
|
|
||||||
|
- Obsidian in English → plugin in English
|
||||||
|
- Obsidian in any other language → falls back to English
|
||||||
|
|
||||||
|
## Модули / Modules
|
||||||
|
|
||||||
|
- **Attachment Clean Paste** — Заменяет `![[embed]]` на `[[path/file|name]]` для файлов с указанными расширениями при вставке/перетаскивании
|
||||||
|
|
||||||
Каждый модуль включается/отключается в настройках плагина.
|
Каждый модуль включается/отключается в настройках плагина.
|
||||||
|
|
||||||
## Разработка
|
## Разработка / Development
|
||||||
|
|
||||||
### Добавление нового модуля
|
### Архитектура / Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── main.ts # точка входа, Plugin class
|
||||||
|
├── settings.ts # общая панель настроек
|
||||||
|
├── types.ts # базовые типы
|
||||||
|
├── locales/
|
||||||
|
│ ├── en.ts # тип Locale + EN-словарь
|
||||||
|
│ ├── ru.ts # RU-словарь
|
||||||
|
│ └── index.ts # авто-определение языка
|
||||||
|
└── modules/
|
||||||
|
├── index.ts # реестр модулей (статический импорт)
|
||||||
|
├── base-module.ts # абстрактный класс модуля
|
||||||
|
└── attachment-clean-paste/
|
||||||
|
└── index.ts # реализация модуля
|
||||||
|
```
|
||||||
|
|
||||||
|
**Принцип работы:**
|
||||||
|
- `main.ts` загружает настройки, итерирует `MODULES` и вызывает `onload()` для включённых
|
||||||
|
- Каждый модуль через `registerCleanup()` регистрирует функции очистки, которые вызываются при выключении модуля или выгрузке плагина
|
||||||
|
- В настройках отображаются все модули с чекбоксами вкл/выкл
|
||||||
|
|
||||||
|
### Добавление нового модуля / Adding a module
|
||||||
|
|
||||||
1. Создайте папку `src/modules/my-module/`
|
1. Создайте папку `src/modules/my-module/`
|
||||||
2. Создайте класс, унаследованный от `BaseModule`:
|
2. Создайте класс, унаследованный от `BaseModule`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { BaseModule } from '../base-module';
|
import { BaseModule } from '../base-module';
|
||||||
|
import { locale } from '../../locales';
|
||||||
|
|
||||||
export class MyModule extends BaseModule {
|
export class MyModule extends BaseModule {
|
||||||
id = 'my-module';
|
id = 'my-module';
|
||||||
name = 'My Module';
|
name = locale.modules['my-module'].name;
|
||||||
description = 'Описание модуля';
|
description = locale.modules['my-module'].description;
|
||||||
|
|
||||||
get defaultSettings() {
|
get defaultSettings() {
|
||||||
return { /* настройки по умолчанию */ };
|
return { /* настройки по умолчанию */ };
|
||||||
@@ -46,7 +82,8 @@ export class MyModule extends BaseModule {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Зарегистрируйте модуль в `src/modules/index.ts`:
|
3. Добавьте переводы в `src/locales/en.ts` и `src/locales/ru.ts`
|
||||||
|
4. Зарегистрируйте модуль в `src/modules/index.ts`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { MyModule } from './my-module';
|
import { MyModule } from './my-module';
|
||||||
@@ -57,34 +94,22 @@ export const MODULES: QoLModule[] = [
|
|||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Если модулю нужна своя вкладка настроек — создайте `PluginSettingTab` и добавьте её через `plugin.addSettingTab()` в `onload`
|
### Добавление нового языка / Adding a language
|
||||||
|
|
||||||
### Сборка
|
1. Создайте `src/locales/de.ts` со структурой `Locale`
|
||||||
|
2. Импортируйте и добавьте в словарь в `src/locales/index.ts`:
|
||||||
|
```typescript
|
||||||
|
import { de } from './de';
|
||||||
|
const locales: Record<string, Locale> = { en, ru, de };
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сборка / Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build # production-сборка
|
npm run build # production-сборка
|
||||||
npm run dev # dev-сборка с sourcemap
|
npm run dev # dev-сборка с sourcemap
|
||||||
```
|
```
|
||||||
|
|
||||||
### Архитектура
|
## Лицензия / License
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── main.ts # точка входа, Plugin class
|
|
||||||
├── settings.ts # общая панель настроек
|
|
||||||
├── types.ts # базовые типы
|
|
||||||
└── modules/
|
|
||||||
├── index.ts # реестр модулей (статический импорт)
|
|
||||||
├── base-module.ts # абстрактный класс модуля
|
|
||||||
└── attachment-clean-paste/
|
|
||||||
└── index.ts # реализация модуля
|
|
||||||
```
|
|
||||||
|
|
||||||
**Принцип работы:**
|
|
||||||
- `main.ts` загружает настройки, итерирует `MODULES` и вызывает `onload()` для включённых
|
|
||||||
- Каждый модуль через `registerCleanup()` регистрирует функции отчистки, которые вызываются при выключении модуля или выгрузке плагина
|
|
||||||
- В настройках отображаются все модули с чекбоксами вкл/выкл
|
|
||||||
|
|
||||||
## Лицензия
|
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import esbuild from 'esbuild';
|
import esbuild from 'esbuild';
|
||||||
import process from 'process';
|
import process from 'process';
|
||||||
|
import fs from 'fs';
|
||||||
import builtins from 'builtin-modules';
|
import builtins from 'builtin-modules';
|
||||||
|
|
||||||
const banner = `/*
|
const banner = `/*
|
||||||
@@ -9,6 +10,11 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
|||||||
|
|
||||||
const prod = process.argv[2] === 'production';
|
const prod = process.argv[2] === 'production';
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
||||||
|
const manifest = JSON.parse(fs.readFileSync('./manifest.json', 'utf8'));
|
||||||
|
manifest.version = pkg.version;
|
||||||
|
fs.writeFileSync('./manifest.json', JSON.stringify(manifest, null, '\t') + '\n');
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.build({
|
.build({
|
||||||
banner: { js: banner },
|
banner: { js: banner },
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"id": "zeroq-qol-modules",
|
"id": "zeroq-qol-modules",
|
||||||
"name": "ZeroQ QoL Modules",
|
"name": "ZeroQ QoL Modules",
|
||||||
"version": "1.0.0",
|
"version": "1.1.1",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Набор QoL-модулей для Obsidian: clean paste, авто-линковка и другое",
|
"description": "QoL Modules",
|
||||||
"author": "Custom",
|
"author": "oqyude",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zeroq-qol-modules",
|
"name": "zeroq-qol-modules",
|
||||||
"version": "1.0.0",
|
"version": "1.1.1",
|
||||||
"description": "Modular Obsidian plugin with toggleable QoL modules",
|
"description": "Modular Obsidian plugin with toggleable QoL modules",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
export interface Locale {
|
||||||
|
settings: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
modules: {
|
||||||
|
'attachment-clean-paste': {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
descriptionShort: string;
|
||||||
|
extensionsLabel: string;
|
||||||
|
extensionsDesc: string;
|
||||||
|
extensionsPlaceholder: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const en: Locale = {
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { en, Locale } from './en';
|
||||||
|
import { ru } from './ru';
|
||||||
|
|
||||||
|
const locales: Record<string, Locale> = { en, ru };
|
||||||
|
|
||||||
|
function detectLocale(): Locale {
|
||||||
|
const lang = (document.documentElement.lang || 'en').slice(0, 2);
|
||||||
|
return locales[lang] || en;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const locale = detectLocale();
|
||||||
|
export type { Locale };
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Locale } from './en';
|
||||||
|
|
||||||
|
export const ru: Locale = {
|
||||||
|
settings: {
|
||||||
|
title: 'ZeroQ QoL Modules',
|
||||||
|
description: 'Включайте и отключайте модули по своему усмотрению.',
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
'attachment-clean-paste': {
|
||||||
|
name: 'Attachment Clean Paste',
|
||||||
|
description:
|
||||||
|
'Заменяет ![[вложение]] на [[вложение|имя]] для указанных расширений при вставке/перетаскивании',
|
||||||
|
descriptionShort:
|
||||||
|
'Заменяет ![[вложение]] на [[вложение|имя]] для указанных расширений.',
|
||||||
|
extensionsLabel: 'Расширения файлов',
|
||||||
|
extensionsDesc:
|
||||||
|
'Расширения через запятую (например: png, jpg, pdf, mp3). Файлы с этими расширениями будут вставляться как [[path/file|name]] вместо ![[path/file]].',
|
||||||
|
extensionsPlaceholder: 'png, jpg, jpeg, gif, svg, webp, pdf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -4,12 +4,12 @@ import {
|
|||||||
MarkdownFileInfo,
|
MarkdownFileInfo,
|
||||||
MarkdownView,
|
MarkdownView,
|
||||||
Notice,
|
Notice,
|
||||||
PluginSettingTab,
|
|
||||||
Setting,
|
Setting,
|
||||||
normalizePath,
|
normalizePath,
|
||||||
} from 'obsidian';
|
} from 'obsidian';
|
||||||
import type ZeroQoLModulesPlugin from '../../main';
|
import type ZeroQoLModulesPlugin from '../../main';
|
||||||
import { BaseModule } from '../base-module';
|
import { BaseModule } from '../base-module';
|
||||||
|
import { locale } from '../../locales';
|
||||||
|
|
||||||
const MODULE_ID = 'attachment-clean-paste';
|
const MODULE_ID = 'attachment-clean-paste';
|
||||||
|
|
||||||
@@ -19,9 +19,8 @@ interface AttachmentCleanPasteSettings {
|
|||||||
|
|
||||||
export class AttachmentCleanPasteModule extends BaseModule {
|
export class AttachmentCleanPasteModule extends BaseModule {
|
||||||
id = MODULE_ID;
|
id = MODULE_ID;
|
||||||
name = 'Attachment Clean Paste';
|
name = locale.modules['attachment-clean-paste'].name;
|
||||||
description =
|
description = locale.modules['attachment-clean-paste'].description;
|
||||||
'Заменяет ![[вложение]] на [[вложение|имя]] для указанных расширений при вставке/перетаскивании';
|
|
||||||
|
|
||||||
private app: App;
|
private app: App;
|
||||||
|
|
||||||
@@ -33,25 +32,65 @@ export class AttachmentCleanPasteModule extends BaseModule {
|
|||||||
|
|
||||||
onload(plugin: ZeroQoLModulesPlugin, moduleSettings: Record<string, unknown>): void {
|
onload(plugin: ZeroQoLModulesPlugin, moduleSettings: Record<string, unknown>): void {
|
||||||
this.app = plugin.app;
|
this.app = plugin.app;
|
||||||
const settings = moduleSettings as unknown as AttachmentCleanPasteSettings;
|
const rawSettings = moduleSettings as AttachmentCleanPasteSettings;
|
||||||
|
|
||||||
plugin.addSettingTab(
|
|
||||||
new AttachmentCleanPasteSettingTab(plugin.app, plugin, MODULE_ID),
|
|
||||||
);
|
|
||||||
|
|
||||||
const pasteRef = plugin.app.workspace.on(
|
const pasteRef = plugin.app.workspace.on(
|
||||||
'editor-paste',
|
'editor-paste',
|
||||||
this.createPasteHandler(settings),
|
this.createPasteHandler(rawSettings),
|
||||||
);
|
);
|
||||||
this.registerCleanup(() => plugin.app.workspace.offref(pasteRef));
|
this.registerCleanup(() => plugin.app.workspace.offref(pasteRef));
|
||||||
|
|
||||||
const dropRef = plugin.app.workspace.on(
|
const dropRef = plugin.app.workspace.on(
|
||||||
'editor-drop',
|
'editor-drop',
|
||||||
this.createDropHandler(settings),
|
this.createDropHandler(rawSettings),
|
||||||
);
|
);
|
||||||
this.registerCleanup(() => plugin.app.workspace.offref(dropRef));
|
this.registerCleanup(() => plugin.app.workspace.offref(dropRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderInlineSettings(
|
||||||
|
containerEl: HTMLElement,
|
||||||
|
settings: Record<string, unknown>,
|
||||||
|
saveSettings: () => Promise<void>,
|
||||||
|
): void {
|
||||||
|
const loc = locale.modules['attachment-clean-paste'];
|
||||||
|
const extSettings = settings as unknown as AttachmentCleanPasteSettings;
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName(loc.extensionsLabel)
|
||||||
|
.setDesc(loc.extensionsDesc)
|
||||||
|
.addTextArea((textarea) => {
|
||||||
|
textarea
|
||||||
|
.setPlaceholder(loc.extensionsPlaceholder)
|
||||||
|
.setValue(extSettings?.extensions ?? '')
|
||||||
|
.onChange(async (value) => {
|
||||||
|
settings.extensions = value;
|
||||||
|
await saveSettings();
|
||||||
|
});
|
||||||
|
textarea.inputEl.rows = 4;
|
||||||
|
textarea.inputEl.style.width = '100%';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processAll(
|
||||||
|
files: FileList,
|
||||||
|
editor: Editor,
|
||||||
|
settings: AttachmentCleanPasteSettings,
|
||||||
|
): Promise<void> {
|
||||||
|
const matchingFiles = Array.from(files).filter((f) =>
|
||||||
|
this.shouldProcess(f.name, settings),
|
||||||
|
);
|
||||||
|
if (matchingFiles.length === 0) return;
|
||||||
|
|
||||||
|
const links: string[] = [];
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
|
||||||
private createPasteHandler(settings: AttachmentCleanPasteSettings) {
|
private createPasteHandler(settings: AttachmentCleanPasteSettings) {
|
||||||
return async (
|
return async (
|
||||||
evt: ClipboardEvent,
|
evt: ClipboardEvent,
|
||||||
@@ -61,17 +100,10 @@ export class AttachmentCleanPasteModule extends BaseModule {
|
|||||||
const files = evt.clipboardData?.files;
|
const files = evt.clipboardData?.files;
|
||||||
if (!files || files.length === 0) return;
|
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.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
for (const file of matchingFiles) {
|
await this.processAll(files, editor, settings);
|
||||||
await this.processFile(file, editor, settings);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +116,10 @@ export class AttachmentCleanPasteModule extends BaseModule {
|
|||||||
const files = evt.dataTransfer?.files;
|
const files = evt.dataTransfer?.files;
|
||||||
if (!files || files.length === 0) return;
|
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.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
for (const file of matchingFiles) {
|
await this.processAll(files, editor, settings);
|
||||||
await this.processFile(file, editor, settings);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,9 +144,8 @@ export class AttachmentCleanPasteModule extends BaseModule {
|
|||||||
|
|
||||||
private async processFile(
|
private async processFile(
|
||||||
file: File,
|
file: File,
|
||||||
editor: Editor,
|
|
||||||
_settings: AttachmentCleanPasteSettings,
|
_settings: AttachmentCleanPasteSettings,
|
||||||
): Promise<void> {
|
): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const arrayBuffer = await file.arrayBuffer();
|
const arrayBuffer = await file.arrayBuffer();
|
||||||
const filename = file.name;
|
const filename = file.name;
|
||||||
@@ -147,58 +171,11 @@ export class AttachmentCleanPasteModule extends BaseModule {
|
|||||||
|
|
||||||
await this.app.vault.createBinary(availablePath, arrayBuffer);
|
await this.app.vault.createBinary(availablePath, arrayBuffer);
|
||||||
|
|
||||||
const link = `[[${availablePath}|${nameWithoutExt}]]`;
|
return `[[${availablePath}|${nameWithoutExt}]]`;
|
||||||
editor.replaceSelection(link);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
new Notice(`Clean Paste error: ${e.message}`);
|
new Notice(`Clean Paste error: ${e.message}`);
|
||||||
console.error('Attachment Clean Paste:', e);
|
console.error('Attachment Clean Paste:', e);
|
||||||
}
|
}
|
||||||
}
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
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%';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ export abstract class BaseModule implements QoLModule {
|
|||||||
this.cleanupFns = [];
|
this.cleanupFns = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderInlineSettings(
|
||||||
|
_containerEl: HTMLElement,
|
||||||
|
_settings: Record<string, unknown>,
|
||||||
|
_saveSettings: () => Promise<void>,
|
||||||
|
): void {}
|
||||||
|
|
||||||
protected registerCleanup(fn: () => void): void {
|
protected registerCleanup(fn: () => void): void {
|
||||||
this.cleanupFns.push(fn);
|
this.cleanupFns.push(fn);
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-4
@@ -2,6 +2,7 @@ import { App, PluginSettingTab, Setting } from 'obsidian';
|
|||||||
import type ZeroQoLModulesPlugin from './main';
|
import type ZeroQoLModulesPlugin from './main';
|
||||||
import { MODULES } from './modules';
|
import { MODULES } from './modules';
|
||||||
import { ZeroQSettings } from './types';
|
import { ZeroQSettings } from './types';
|
||||||
|
import { locale } from './locales';
|
||||||
|
|
||||||
const MODULE_DEFAULTS = MODULES.reduce(
|
const MODULE_DEFAULTS = MODULES.reduce(
|
||||||
(acc, mod) => {
|
(acc, mod) => {
|
||||||
@@ -37,21 +38,21 @@ export class ZeroQSettingTab extends PluginSettingTab {
|
|||||||
const { containerEl } = this;
|
const { containerEl } = this;
|
||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
|
|
||||||
containerEl.createEl('h1', { text: 'ZeroQ QoL Modules' });
|
containerEl.createEl('h1', { text: locale.settings.title });
|
||||||
containerEl.createEl('p', {
|
containerEl.createEl('p', {
|
||||||
text: 'Включайте и отключайте модули по своему усмотрению.',
|
text: locale.settings.description,
|
||||||
attr: { style: 'color: var(--text-muted); margin-bottom: 20px;' },
|
attr: { style: 'color: var(--text-muted); margin-bottom: 20px;' },
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const module of MODULES) {
|
for (const module of MODULES) {
|
||||||
const moduleSettings = this.plugin.settings.modules[module.id];
|
const moduleCfg = this.plugin.settings.modules[module.id];
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(module.name)
|
.setName(module.name)
|
||||||
.setDesc(module.description)
|
.setDesc(module.description)
|
||||||
.addToggle((toggle) =>
|
.addToggle((toggle) =>
|
||||||
toggle
|
toggle
|
||||||
.setValue(moduleSettings?.enabled ?? true)
|
.setValue(moduleCfg?.enabled ?? true)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
if (!this.plugin.settings.modules[module.id]) {
|
if (!this.plugin.settings.modules[module.id]) {
|
||||||
this.plugin.settings.modules[module.id] = {
|
this.plugin.settings.modules[module.id] = {
|
||||||
@@ -69,6 +70,14 @@ export class ZeroQSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (moduleCfg) {
|
||||||
|
module.renderInlineSettings(
|
||||||
|
containerEl,
|
||||||
|
moduleCfg.settings,
|
||||||
|
() => this.plugin.saveSettings(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,9 @@ export interface QoLModule {
|
|||||||
defaultSettings: Record<string, unknown>;
|
defaultSettings: Record<string, unknown>;
|
||||||
onload(plugin: Plugin, moduleSettings: Record<string, unknown>): void;
|
onload(plugin: Plugin, moduleSettings: Record<string, unknown>): void;
|
||||||
onunload(plugin: Plugin): void;
|
onunload(plugin: Plugin): void;
|
||||||
|
renderInlineSettings(
|
||||||
|
containerEl: HTMLElement,
|
||||||
|
settings: Record<string, unknown>,
|
||||||
|
saveSettings: () => Promise<void>,
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "bundler",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"lib": ["DOM", "ES5", "ES6", "ES7"],
|
"lib": ["DOM", "ES5", "ES6", "ES7"],
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user