Исправлена сборка плагина и удалён нерабочий код
- Исправлен маппинг статусов Logseq → PPDB (теперь отправляет TODO/DOING/DONE вместо new/in_progress/completed) - Удалён нерабочий код setupConfig (использовал несуществующий API) - Исправлена сборка: теперь создаётся index.html + assets/ вместо одного index.js - Исправлено чтение свойств блоков (проверка source-id/sourceId/source_id) - Добавлен скрипт deploy.sh для деплоя в ~/.logseq/plugins/
This commit is contained in:
47
CLAUDE.md
Normal file
47
CLAUDE.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Описание проекта
|
||||
|
||||
TODO Everywhere — Logseq плагин для синхронизации задач из внешних источников (PPDB, Gitea) в Logseq.
|
||||
|
||||
## Команды сборки
|
||||
|
||||
```bash
|
||||
npm install # Установка зависимостей
|
||||
npm run build # Сборка для продакшена (dist/index.js)
|
||||
npm run dev # Режим разработки с автопересборкой
|
||||
```
|
||||
|
||||
## Архитектура
|
||||
|
||||
Плагин состоит из одного файла `src/index.ts`, который:
|
||||
|
||||
1. **Настройка config.edn** (`setupConfig`) — при первой установке автоматически добавляет Datalog-запросы для PPDB и Gitea в конфиг Logseq
|
||||
2. **Синхронизация PPDB** (`syncPPDBTasks`) — получает задачи через `/api/logseq/tasks` и создаёт страницу `PPDB - TODO`
|
||||
3. **Синхронизация Gitea** (`syncGiteaTasks`) — получает issues через Gitea API и создаёт страницы `Gitea - {name} - TODO`
|
||||
4. **Запись в страницу** (`writeToPage`) — конвертирует данные в формат Logseq блоков с TODO/DOING/DONE статусами
|
||||
|
||||
### Ключевые интерфейсы
|
||||
|
||||
- `PPDBTask` — структура задачи из PPDB API (включает `logseq_priority` и `logseq_status`)
|
||||
- `GiteaIssue` — структура issue из Gitea API
|
||||
- `GiteaSource` — настройки подключения к Gitea репозиторию
|
||||
|
||||
### Конвертация данных
|
||||
|
||||
- Приоритеты: critical/high → [#A], normal → [#B], low → [#C]
|
||||
- Статусы: new → TODO, in_progress → DOING, completed/rejected → DONE
|
||||
- Gitea labels определяют приоритет и тип (bug/feature)
|
||||
|
||||
## Настройки плагина
|
||||
|
||||
Настройки хранятся через `logseq.settings` и включают:
|
||||
- `ppdbUrl`, `ppdbApiKey` — подключение к PPDB
|
||||
- `giteaSources` — JSON массив Gitea репозиториев
|
||||
- `configVersion` — версия конфига для отслеживания миграций
|
||||
|
||||
## Связанный API
|
||||
|
||||
PPDB endpoint `/api/logseq/tasks` (в проекте ppdb-site) требует `LOGSEQ_API_KEY` в переменных окружения.
|
||||
17
deploy.sh
Executable file
17
deploy.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Скрипт деплоя плагина в Logseq
|
||||
|
||||
DEST="$HOME/.logseq/plugins/logseq-todo-everywhere"
|
||||
|
||||
# Удаляем старую версию
|
||||
rm -rf "$DEST"
|
||||
|
||||
# Создаём папки
|
||||
mkdir -p "$DEST/dist"
|
||||
|
||||
# Копируем файлы
|
||||
cp package.json "$DEST/"
|
||||
cp icon.svg "$DEST/"
|
||||
cp -r dist/* "$DEST/dist/"
|
||||
|
||||
echo "Плагин скопирован в $DEST"
|
||||
259
dist/assets/index-jnn8zDEO.js
vendored
Normal file
259
dist/assets/index-jnn8zDEO.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
dist/index.html
vendored
Normal file
11
dist/index.html
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>TODO Everywhere</title>
|
||||
<script type="module" crossorigin src="./assets/index-jnn8zDEO.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
42
dist/index.js
vendored
42
dist/index.js
vendored
@@ -1,42 +0,0 @@
|
||||
(function(){"use strict";const w=`{:title "PPDB"
|
||||
:query [:find (pull ?h [:db/id :block/uuid :block/content :block/marker :block/priority :block/created-at :block/updated-at {:block/page [:block/journal-day]}])
|
||||
:where
|
||||
[?p :block/original-name "PPDB - TODO"]
|
||||
[?h :block/marker ?marker]
|
||||
[(contains? #{"NOW" "DOING" "TODO"} ?marker)]
|
||||
[?h :block/page ?p]
|
||||
[?p :block/journal? false]]
|
||||
:result-transform (fn [result]
|
||||
(sort-by (fn [h]
|
||||
[(get h :block/priority "Z")
|
||||
(or (get h :block/created-at) 999999999999999)]) result))
|
||||
:collapsed? true}`,P=`{:title "Gitea"
|
||||
:query [:find (pull ?h [:db/id :block/uuid :block/content :block/marker :block/priority :block/created-at :block/updated-at {:block/page [:block/journal-day :block/original-name]}])
|
||||
:where
|
||||
[?p :block/original-name ?name]
|
||||
[(clojure.string/starts-with? ?name "Gitea -")]
|
||||
[?h :block/marker ?marker]
|
||||
[(contains? #{"NOW" "DOING" "TODO"} ?marker)]
|
||||
[?h :block/page ?p]
|
||||
[?p :block/journal? false]]
|
||||
:result-transform (fn [result]
|
||||
(sort-by (fn [h]
|
||||
[(get h :block/priority "Z")
|
||||
(or (get h :block/created-at) 999999999999999)]) result))
|
||||
:collapsed? true}`,O=[{key:"ppdbEnabled",type:"boolean",default:!1,title:"PPDB - Включить синхронизацию",description:"Синхронизировать задачи из PPDB"},{key:"ppdbUrl",type:"string",default:"https://ppdb.linux-gaming.ru",title:"PPDB - URL сервера",description:"Базовый URL PPDB API"},{key:"ppdbApiKey",type:"string",default:"",title:"PPDB - API ключ",description:"Ключ для доступа к API"},{key:"ppdbPageName",type:"string",default:"PPDB - TODO",title:"PPDB - Имя страницы",description:"Название страницы для задач PPDB"},{key:"ppdbCategories",type:"string",default:"",title:"PPDB - Категории (через запятую)",description:"Фильтр по категориям: ppdb, linux-gaming, portproton, portprotonqt. Пусто = все"},{key:"giteaEnabled",type:"boolean",default:!1,title:"Gitea - Включить синхронизацию",description:"Синхронизировать issues из Gitea"},{key:"giteaSources",type:"string",default:"[]",title:"Gitea - Источники (JSON)",description:'JSON массив источников: [{"name":"MyRepo","url":"https://gitea.example.com","token":"xxx","owner":"user","repo":"repo","enabled":true}]'},{key:"syncOnStartup",type:"boolean",default:!0,title:"Синхронизация при запуске",description:"Автоматически синхронизировать при запуске Logseq"},{key:"syncInterval",type:"number",default:0,title:"Интервал синхронизации (минуты)",description:"0 = только вручную"},{key:"configVersion",type:"number",default:0,title:"Версия конфигурации (не изменять)",description:"Внутренняя переменная для отслеживания настройки config.edn"}],f=1;async function $(){try{const n=await logseq.App.getCurrentGraph();if(!n?.path)return console.error("Cannot get graph path"),!1;const s=`${n.path}/logseq/config.edn`,t=await window.logseq.FileStorage.getItem(s);if(!t)return console.error("Cannot read config.edn"),!1;let a=!1,e=t;if(!t.includes(':title "PPDB"')){const o=h(e);o>0&&(e=e.slice(0,o)+`
|
||||
`+w+e.slice(o),a=!0)}if(!e.includes(':title "Gitea"')){const o=h(e);o>0&&(e=e.slice(0,o)+`
|
||||
`+P+e.slice(o),a=!0)}return e.includes('"PPDB - TODO"')||(e=e.replace(/\(not \[\(contains\? #\{([^}]*)"РЕД СОФТ - TODO"([^}]*)\} \?name\)\]\)/g,'(not [(contains? #{$1"РЕД СОФТ - TODO" "PPDB - TODO"$2} ?name)])'),a=!0),e.includes('clojure.string/starts-with? ?name "Gitea -"')||(e=e.replace(/(\(not \[\(contains\? #\{[^}]*"PPDB - TODO"[^}]*\} \?name\)\]\))(\s*)(\[?\?h :block\/marker)/g,`$1
|
||||
(not [(clojure.string/starts-with? ?name "Gitea -")])$2$3`),a=!0),e.includes('"PPDB - TODO"')||(e=e.replace(/:favorites \[([^\]]*)"РЕД СОФТ - TODO"([^\]]*)\]/,':favorites [$1"РЕД СОФТ - TODO" "PPDB - TODO"$2]'),a=!0),a?(await window.logseq.FileStorage.setItem(s,e),console.log("config.edn updated successfully"),!0):!1}catch(n){return console.error("Error setting up config:",n),!1}}function h(n){const s=[/:title "Остальные дела"/,/:title "Остальные"/];for(const a of s){const e=n.match(a);if(e&&e.index){let o=e.index;for(;o>0&&n[o]!=="{";)o--;return o}}const t=n.search(/\]\s*\}\s*;;\s*Add custom commands/);return t>0?t:-1}async function D(){logseq.useSettingsSchema(O),(logseq.settings?.configVersion||0)<f&&(logseq.UI.showMsg("TODO Everywhere: настройка config.edn...","info"),await $()&&(logseq.updateSettings({configVersion:f}),logseq.UI.showMsg("config.edn обновлён. Перезапустите Logseq для применения изменений.","success"))),logseq.Editor.registerSlashCommand("sync-tasks",async()=>{await p()}),logseq.Editor.registerSlashCommand("sync-ppdb",async()=>{await y()}),logseq.Editor.registerSlashCommand("sync-gitea",async()=>{await m()}),logseq.App.registerUIItem("toolbar",{key:"todo-everywhere-sync",template:`
|
||||
<a class="button" data-on-click="syncAll" title="Sync External Tasks">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
|
||||
</svg>
|
||||
</a>
|
||||
`}),logseq.provideModel({async syncAll(){await p()}}),logseq.settings?.syncOnStartup&&setTimeout(()=>p(),3e3);const s=logseq.settings?.syncInterval;s>0&&setInterval(()=>p(),s*60*1e3),console.log("TODO Everywhere plugin loaded")}async function p(){logseq.UI.showMsg("Синхронизация задач...","info");try{const n=[];if(logseq.settings?.ppdbEnabled){const s=await y();n.push(s)}if(logseq.settings?.giteaEnabled){const s=await m();n.push(s)}n.length===0?logseq.UI.showMsg("Нет включенных источников","warning"):logseq.UI.showMsg(n.join(`
|
||||
`),"success")}catch(n){console.error("Sync error:",n),logseq.UI.showMsg(`Ошибка синхронизации: ${n}`,"error")}}async function y(){const n=logseq.settings?.ppdbUrl,s=logseq.settings?.ppdbApiKey,t=logseq.settings?.ppdbPageName||"PPDB - TODO",a=(logseq.settings?.ppdbCategories||"").split(",").map(u=>u.trim()).filter(Boolean);if(!n||!s)throw new Error("PPDB: не настроены URL или API ключ");let e=`${n}/api/logseq/tasks?api_key=${s}`;a.length===1&&(e+=`&category=${a[0]}`);const o=await fetch(e);if(!o.ok)throw new Error(`PPDB API error: ${o.status}`);let l=(await o.json()).tasks;a.length>1&&(l=l.filter(u=>a.includes(u.category)));const i=B(l);return await b(t,i),`PPDB: ${l.length} задач`}async function m(){const n=logseq.settings?.giteaSources||"[]";let s;try{s=JSON.parse(n)}catch{throw new Error("Gitea: неверный формат JSON источников")}const t=s.filter(e=>e.enabled);if(t.length===0)return"Gitea: нет активных источников";const a=[];for(const e of t)try{const o=await q(e),r=`Gitea - ${e.name} - TODO`,l=T(o,e);await b(r,l),a.push(`${e.name}: ${o.length}`)}catch(o){console.error(`Gitea ${e.name} error:`,o),a.push(`${e.name}: ошибка`)}return`Gitea: ${a.join(", ")}`}async function q(n){const s=`${n.url}/api/v1/repos/${n.owner}/${n.repo}/issues?state=open&type=issues`,t=await fetch(s,{headers:{Authorization:`token ${n.token}`,"Content-Type":"application/json"}});if(!t.ok)throw new Error(`Gitea API error: ${t.status}`);return await t.json()}function B(n){const s=[];s.push("source:: PPDB"),s.push(`synced:: ${new Date().toISOString()}`),s.push("");const t={TODO:[],DOING:[],DONE:[]};for(const e of n){const o=e.logseq_status;t[o]&&t[o].push(e)}const a={A:0,B:1,C:2};for(const e of Object.keys(t))t[e].sort((o,r)=>(a[o.logseq_priority]||1)-(a[r.logseq_priority]||1));for(const e of[...t.DOING,...t.TODO,...t.DONE]){const o=e.logseq_priority?`[#${e.logseq_priority}] `:"",r=e.task_type?`#${e.task_type} `:"",l=`#${e.category} `;let i=`- ${e.logseq_status} ${o}${r}${l}${e.title}`;if(s.push(i),s.push(` id:: ppdb-${e.id}`),e.url&&s.push(` url:: ${e.url}`),e.description){const u=e.description.split(`
|
||||
`).filter(c=>c.trim());for(const c of u)s.push(` - ${c}`)}e.admin_comment&&s.push(` - **Комментарий:** ${e.admin_comment}`),e.assigned_to_username&&s.push(` assignee:: ${e.assigned_to_username}`),s.push(` author:: ${e.author_username}`),s.push(` created:: ${e.created_at.split("T")[0]}`)}return s.join(`
|
||||
`)}function T(n,s){const t=[];t.push("source:: Gitea"),t.push(`repo:: ${s.owner}/${s.repo}`),t.push(`synced:: ${new Date().toISOString()}`),t.push("");const a=r=>{const l=r.labels.map(i=>i.name.toLowerCase());return l.some(i=>i.includes("critical")||i.includes("urgent")||i.includes("p0"))||l.some(i=>i.includes("high")||i.includes("important")||i.includes("p1"))?"A":l.some(i=>i.includes("low")||i.includes("minor")||i.includes("p3"))?"C":"B"},e=r=>{const l=r.labels.map(i=>i.name.toLowerCase());return l.some(i=>i.includes("bug")||i.includes("fix"))?"bug":l.some(i=>i.includes("feature")||i.includes("enhancement"))?"feature":""},o={A:0,B:1,C:2};n.sort((r,l)=>(o[a(r)]||1)-(o[a(l)]||1));for(const r of n){const l=a(r),i=e(r),u=i?`#${i} `:"",c=r.labels.map(g=>`#${g.name.replace(/\s+/g,"-")}`).join(" ");let k=`- TODO [#${l}] ${u}#${r.number} ${r.title}`;if(c&&(k+=` ${c}`),t.push(k),t.push(` id:: gitea-${s.name}-${r.number}`),t.push(` url:: ${r.html_url}`),r.assignee&&t.push(` assignee:: ${r.assignee.login}`),r.milestone&&t.push(` milestone:: ${r.milestone.title}`),t.push(` created:: ${r.created_at.split("T")[0]}`),r.body){const g=r.body.split(`
|
||||
`).filter(d=>d.trim()).slice(0,5);for(const d of g)t.push(` - ${d.substring(0,200)}`);r.body.split(`
|
||||
`).length>5&&t.push(" - ...")}}return t.join(`
|
||||
`)}async function b(n,s){let t=await logseq.Editor.getPage(n);if(t||(t=await logseq.Editor.createPage(n,{},{redirect:!1})),!t)throw new Error(`Не удалось создать страницу ${n}`);const a=await logseq.Editor.getPageBlocksTree(n);for(const o of a)await logseq.Editor.removeBlock(o.uuid);const e=s.split(`
|
||||
`);if(e.length>0){const o=await logseq.Editor.insertBlock(t.uuid,e[0],{sibling:!1});if(o){let r=o.uuid;for(let l=1;l<e.length;l++){const i=e[l];if(!i.trim())continue;if(i.search(/\S/)<=0||i.startsWith("-")){const c=await logseq.Editor.insertBlock(r,i.replace(/^-\s*/,""),{sibling:!0});c&&(r=c.uuid)}else{const c=i.trim();c.includes("::")?await logseq.Editor.upsertBlockProperty(r,c.split("::")[0].trim(),c.split("::")[1].trim()):c.startsWith("-")&&await logseq.Editor.insertBlock(r,c.replace(/^-\s*/,""),{sibling:!1})}}}}}logseq.ready(D).catch(console.error)})();
|
||||
11
index.html
Normal file
11
index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>TODO Everywhere</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/index.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "logseq-todo-everywhere",
|
||||
"version": "1.0.0",
|
||||
"description": "Sync tasks from external sources (PPDB, Gitea) to Logseq",
|
||||
"main": "dist/index.js",
|
||||
"main": "dist/index.html",
|
||||
"scripts": {
|
||||
"dev": "npx vite build --watch",
|
||||
"build": "npx vite build"
|
||||
|
||||
1054
src/index.ts
1054
src/index.ts
File diff suppressed because it is too large
Load Diff
@@ -5,20 +5,6 @@ export default defineConfig({
|
||||
plugins: [logseqPlugin()],
|
||||
build: {
|
||||
target: 'esnext',
|
||||
minify: 'esbuild',
|
||||
lib: {
|
||||
entry: 'src/index.ts',
|
||||
formats: ['iife'],
|
||||
name: 'TodoEverywhere',
|
||||
fileName: () => 'index.js'
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ['@logseq/libs'],
|
||||
output: {
|
||||
globals: {
|
||||
'@logseq/libs': 'logseq'
|
||||
}
|
||||
}
|
||||
}
|
||||
minify: 'esbuild'
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user