From d59ad3c677c1759a526517a34810fc4b482b693c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=A5=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=BE=D0=B2?= Date: Fri, 16 Jan 2026 00:27:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=BB=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=20=D0=B8=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D1=91=D0=BD=20=D0=BD=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=87=D0=B8=D0=B9=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Исправлен маппинг статусов 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/ --- CLAUDE.md | 47 ++ deploy.sh | 17 + dist/assets/index-jnn8zDEO.js | 259 ++++++++ dist/index.html | 11 + dist/index.js | 42 -- index.html | 11 + package.json | 2 +- src/index.ts | 1080 +++++++++++++++++++++++++-------- vite.config.ts | 16 +- 9 files changed, 1176 insertions(+), 309 deletions(-) create mode 100644 CLAUDE.md create mode 100755 deploy.sh create mode 100644 dist/assets/index-jnn8zDEO.js create mode 100644 dist/index.html delete mode 100644 dist/index.js create mode 100644 index.html diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ad5b681 --- /dev/null +++ b/CLAUDE.md @@ -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` в переменных окружения. diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..23e87db --- /dev/null +++ b/deploy.sh @@ -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" diff --git a/dist/assets/index-jnn8zDEO.js b/dist/assets/index-jnn8zDEO.js new file mode 100644 index 0000000..b5995e2 --- /dev/null +++ b/dist/assets/index-jnn8zDEO.js @@ -0,0 +1,259 @@ +(function(){const M=document.createElement("link").relList;if(M&&M.supports&&M.supports("modulepreload"))return;for(const y of document.querySelectorAll('link[rel="modulepreload"]'))G(y);new MutationObserver(y=>{for(const z of y)if(z.type==="childList")for(const v of z.addedNodes)v.tagName==="LINK"&&v.rel==="modulepreload"&&G(v)}).observe(document,{childList:!0,subtree:!0});function $(y){const z={};return y.integrity&&(z.integrity=y.integrity),y.referrerPolicy&&(z.referrerPolicy=y.referrerPolicy),y.crossOrigin==="use-credentials"?z.credentials="include":y.crossOrigin==="anonymous"?z.credentials="omit":z.credentials="same-origin",z}function G(y){if(y.ep)return;y.ep=!0;const z=$(y);fetch(y.href,z)}})();var sn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},Ie={exports:{}};Ie.exports;var wo;function Ca(){return wo||(wo=1,(function(E,M){(function($,G){E.exports=G()})(self,(()=>(()=>{var $={833:(v,u,f)=>{var k=f(606);u.formatArgs=function(R){if(R[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+R[0]+(this.useColors?"%c ":" ")+"+"+v.exports.humanize(this.diff),!this.useColors)return;const T="color: "+this.color;R.splice(1,0,T,"color: inherit");let p=0,c=0;R[0].replace(/%[a-zA-Z%]/g,(_=>{_!=="%%"&&(p++,_==="%c"&&(c=p))})),R.splice(c,0,T)},u.save=function(R){try{R?u.storage.setItem("debug",R):u.storage.removeItem("debug")}catch{}},u.load=function(){let R;try{R=u.storage.getItem("debug")}catch{}return!R&&k!==void 0&&"env"in k&&(R=k.env.DEBUG),R},u.useColors=function(){return typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs)?!0:typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)?!1:typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},u.storage=(function(){try{return localStorage}catch{}})(),u.destroy=(()=>{let R=!1;return()=>{R||(R=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),u.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],u.log=console.debug||console.log||(()=>{}),v.exports=f(736)(u);const{formatters:w}=v.exports;w.j=function(R){try{return JSON.stringify(R)}catch(T){return"[UnexpectedJSONParseError]: "+T.message}}},736:(v,u,f)=>{v.exports=function(k){function w(p){let c,_,h,S=null;function x(...N){if(!x.enabled)return;const F=x,j=Number(new Date),q=j-(c||j);F.diff=q,F.prev=c,F.curr=j,c=j,N[0]=w.coerce(N[0]),typeof N[0]!="string"&&N.unshift("%O");let i=0;N[0]=N[0].replace(/%([a-zA-Z%])/g,((d,b)=>{if(d==="%%")return"%";i++;const P=w.formatters[b];if(typeof P=="function"){const X=N[i];d=P.call(F,X),N.splice(i,1),i--}return d})),w.formatArgs.call(F,N),(F.log||w.log).apply(F,N)}return x.namespace=p,x.useColors=w.useColors(),x.color=w.selectColor(p),x.extend=R,x.destroy=w.destroy,Object.defineProperty(x,"enabled",{enumerable:!0,configurable:!1,get:()=>S!==null?S:(_!==w.namespaces&&(_=w.namespaces,h=w.enabled(p)),h),set:N=>{S=N}}),typeof w.init=="function"&&w.init(x),x}function R(p,c){const _=w(this.namespace+(c===void 0?":":c)+p);return _.log=this.log,_}function T(p){return p.toString().substring(2,p.toString().length-2).replace(/\.\*\?$/,"*")}return w.debug=w,w.default=w,w.coerce=function(p){return p instanceof Error?p.stack||p.message:p},w.disable=function(){const p=[...w.names.map(T),...w.skips.map(T).map((c=>"-"+c))].join(",");return w.enable(""),p},w.enable=function(p){let c;w.save(p),w.namespaces=p,w.names=[],w.skips=[];const _=(typeof p=="string"?p:"").split(/[\s,]+/),h=_.length;for(c=0;c{w[p]=k[p]})),w.names=[],w.skips=[],w.formatters={},w.selectColor=function(p){let c=0;for(let _=0;_{var u=function(h){return(function(S){return!!S&&typeof S=="object"})(h)&&!(function(S){var x=Object.prototype.toString.call(S);return x==="[object RegExp]"||x==="[object Date]"||(function(N){return N.$$typeof===f})(S)})(h)},f=typeof Symbol=="function"&&Symbol.for?Symbol.for("react.element"):60103;function k(h,S){return S.clone!==!1&&S.isMergeableObject(h)?c((x=h,Array.isArray(x)?[]:{}),h,S):h;var x}function w(h,S,x){return h.concat(S).map((function(N){return k(N,x)}))}function R(h){return Object.keys(h).concat((function(S){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(S).filter((function(x){return Object.propertyIsEnumerable.call(S,x)})):[]})(h))}function T(h,S){try{return S in h}catch{return!1}}function p(h,S,x){var N={};return x.isMergeableObject(h)&&R(h).forEach((function(F){N[F]=k(h[F],x)})),R(S).forEach((function(F){(function(j,q){return T(j,q)&&!(Object.hasOwnProperty.call(j,q)&&Object.propertyIsEnumerable.call(j,q))})(h,F)||(T(h,F)&&x.isMergeableObject(S[F])?N[F]=(function(j,q){if(!q.customMerge)return c;var i=q.customMerge(j);return typeof i=="function"?i:c})(F,x)(h[F],S[F],x):N[F]=k(S[F],x))})),N}function c(h,S,x){(x=x||{}).arrayMerge=x.arrayMerge||w,x.isMergeableObject=x.isMergeableObject||u,x.cloneUnlessOtherwiseSpecified=k;var N=Array.isArray(S);return N===Array.isArray(h)?N?x.arrayMerge(h,S,x):p(h,S,x):k(S,x)}c.all=function(h,S){if(!Array.isArray(h))throw new Error("first argument should be an array");return h.reduce((function(x,N){return c(x,N,S)}),{})};var _=c;v.exports=_},228:v=>{var u=Object.prototype.hasOwnProperty,f="~";function k(){}function w(c,_,h){this.fn=c,this.context=_,this.once=h||!1}function R(c,_,h,S,x){if(typeof h!="function")throw new TypeError("The listener must be a function");var N=new w(h,S||c,x),F=f?f+_:_;return c._events[F]?c._events[F].fn?c._events[F]=[c._events[F],N]:c._events[F].push(N):(c._events[F]=N,c._eventsCount++),c}function T(c,_){--c._eventsCount==0?c._events=new k:delete c._events[_]}function p(){this._events=new k,this._eventsCount=0}Object.create&&(k.prototype=Object.create(null),new k().__proto__||(f=!1)),p.prototype.eventNames=function(){var c,_,h=[];if(this._eventsCount===0)return h;for(_ in c=this._events)u.call(c,_)&&h.push(f?_.slice(1):_);return Object.getOwnPropertySymbols?h.concat(Object.getOwnPropertySymbols(c)):h},p.prototype.listeners=function(c){var _=f?f+c:c,h=this._events[_];if(!h)return[];if(h.fn)return[h.fn];for(var S=0,x=h.length,N=new Array(x);S{typeof Object.create=="function"?v.exports=function(u,f){u.super_=f,u.prototype=Object.create(f.prototype,{constructor:{value:u,enumerable:!1,writable:!0,configurable:!0}})}:v.exports=function(u,f){u.super_=f;var k=function(){};k.prototype=f.prototype,u.prototype=new k,u.prototype.constructor=u}},585:v=>{var u=1e3,f=60*u,k=60*f,w=24*k,R=7*w,T=365.25*w;function p(c,_,h,S){var x=_>=1.5*h;return Math.round(c/h)+" "+S+(x?"s":"")}v.exports=function(c,_){_=_||{};var h=typeof c;if(h==="string"&&c.length>0)return(function(S){if(!((S=String(S)).length>100)){var x=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(S);if(x){var N=parseFloat(x[1]);switch((x[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return N*T;case"weeks":case"week":case"w":return N*R;case"days":case"day":case"d":return N*w;case"hours":case"hour":case"hrs":case"hr":case"h":return N*k;case"minutes":case"minute":case"mins":case"min":case"m":return N*f;case"seconds":case"second":case"secs":case"sec":case"s":return N*u;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return N;default:return}}}})(c);if(h==="number"&&isFinite(c))return _.long?(function(S){var x=Math.abs(S);return x>=w?p(S,x,w,"day"):x>=k?p(S,x,k,"hour"):x>=f?p(S,x,f,"minute"):x>=u?p(S,x,u,"second"):S+" ms"})(c):(function(S){var x=Math.abs(S);return x>=w?Math.round(S/w)+"d":x>=k?Math.round(S/k)+"h":x>=f?Math.round(S/f)+"m":x>=u?Math.round(S/u)+"s":S+"ms"})(c);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(c))}},627:(v,u,f)=>{var k=f(606),w=k.platform==="win32",R=f(537);function T(i,d){for(var b=[],P=0;P=0&&!i[P];P--);return b===0&&P===d?i:b>P?[]:i.slice(b,P+1)}var c=/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/,_=/^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/,h={};function S(i){var d=c.exec(i),b=(d[1]||"")+(d[2]||""),P=d[3]||"",X=_.exec(P);return[b,X[1],X[2],X[3]]}function x(i){var d=c.exec(i),b=d[1]||"",P=!!b&&b[1]!==":";return{device:b,isUnc:P,isAbsolute:P||!!d[2],tail:d[3]}}function N(i){return"\\\\"+i.replace(/^[\\\/]+/,"").replace(/[\\\/]+/g,"\\")}h.resolve=function(){for(var i="",d="",b=!1,P=arguments.length-1;P>=-1;P--){var X;if(P>=0?X=arguments[P]:i?(X=k.env["="+i])&&X.substr(0,3).toLowerCase()===i.toLowerCase()+"\\"||(X=i+"\\"):X=k.cwd(),!R.isString(X))throw new TypeError("Arguments to path.resolve must be strings");if(X){var K=x(X),Y=K.device,ft=K.isUnc,Et=K.isAbsolute,tt=K.tail;if((!Y||!i||Y.toLowerCase()===i.toLowerCase())&&(i||(i=Y),b||(d=tt+"\\"+d,b=Et),i&&b))break}}return ft&&(i=N(i)),i+(b?"\\":"")+(d=T(d.split(/[\\\/]+/),!b).join("\\"))||"."},h.normalize=function(i){var d=x(i),b=d.device,P=d.isUnc,X=d.isAbsolute,K=d.tail,Y=/[\\\/]$/.test(K);return(K=T(K.split(/[\\\/]+/),!X).join("\\"))||X||(K="."),K&&Y&&(K+="\\"),P&&(b=N(b)),b+(X?"\\":"")+K},h.isAbsolute=function(i){return x(i).isAbsolute},h.join=function(){for(var i=[],d=0;d=-1&&!d;b--){var P=b>=0?arguments[b]:k.cwd();if(!R.isString(P))throw new TypeError("Arguments to path.resolve must be strings");P&&(i=P+"/"+i,d=P[0]==="/")}return(d?"/":"")+(i=T(i.split("/"),!d).join("/"))||"."},j.normalize=function(i){var d=j.isAbsolute(i),b=i&&i[i.length-1]==="/";return(i=T(i.split("/"),!d).join("/"))||d||(i="."),i&&b&&(i+="/"),(d?"/":"")+i},j.isAbsolute=function(i){return i.charAt(0)==="/"},j.join=function(){for(var i="",d=0;d{var u,f,k=v.exports={};function w(){throw new Error("setTimeout has not been defined")}function R(){throw new Error("clearTimeout has not been defined")}function T(j){if(u===setTimeout)return setTimeout(j,0);if((u===w||!u)&&setTimeout)return u=setTimeout,setTimeout(j,0);try{return u(j,0)}catch{try{return u.call(null,j,0)}catch{return u.call(this,j,0)}}}(function(){try{u=typeof setTimeout=="function"?setTimeout:w}catch{u=w}try{f=typeof clearTimeout=="function"?clearTimeout:R}catch{f=R}})();var p,c=[],_=!1,h=-1;function S(){_&&p&&(_=!1,p.length?c=p.concat(c):h=-1,c.length&&x())}function x(){if(!_){var j=T(S);_=!0;for(var q=c.length;q;){for(p=c,c=[];++h1)for(var i=1;i{v.exports=function(u){return u&&typeof u=="object"&&typeof u.copy=="function"&&typeof u.fill=="function"&&typeof u.readUInt8=="function"}},537:(v,u,f)=>{var k=f(606),w=/%[sdj%]/g;u.format=function(g){if(!i(g)){for(var A=[],B=0;B=vt)return dt;switch(dt){case"%s":return String(lt[B++]);case"%d":return Number(lt[B++]);case"%j":try{return JSON.stringify(lt[B++])}catch{return"[Circular]"}default:return dt}})),rt=lt[B];B=3&&(B.depth=arguments[2]),arguments.length>=4&&(B.colors=arguments[3]),F(A)?B.showHidden=A:A&&u._extend(B,A),d(B.showHidden)&&(B.showHidden=!1),d(B.depth)&&(B.depth=2),d(B.colors)&&(B.colors=!1),d(B.customInspect)&&(B.customInspect=!0),B.colors&&(B.stylize=c),h(B,g,B.depth)}function c(g,A){var B=p.styles[A];return B?"\x1B["+p.colors[B][0]+"m"+g+"\x1B["+p.colors[B][1]+"m":g}function _(g,A){return g}function h(g,A,B){if(g.customInspect&&A&&Y(A.inspect)&&A.inspect!==u.inspect&&(!A.constructor||A.constructor.prototype!==A)){var lt=A.inspect(B,g);return i(lt)||(lt=h(g,lt,B)),lt}var vt=(function(ct,it){if(d(it))return ct.stylize("undefined","undefined");if(i(it)){var Tt="'"+JSON.stringify(it).replace(/^"|"$/g,"").replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return ct.stylize(Tt,"string")}if(q(it))return ct.stylize(""+it,"number");if(F(it))return ct.stylize(""+it,"boolean");if(j(it))return ct.stylize("null","null")})(g,A);if(vt)return vt;var gt=Object.keys(A),rt=(function(ct){var it={};return ct.forEach((function(Tt,Jt){it[Tt]=!0})),it})(gt);if(g.showHidden&&(gt=Object.getOwnPropertyNames(A)),K(A)&&(gt.indexOf("message")>=0||gt.indexOf("description")>=0))return S(A);if(gt.length===0){if(Y(A)){var dt=A.name?": "+A.name:"";return g.stylize("[Function"+dt+"]","special")}if(b(A))return g.stylize(RegExp.prototype.toString.call(A),"regexp");if(X(A))return g.stylize(Date.prototype.toString.call(A),"date");if(K(A))return S(A)}var St,kt="",Ft=!1,Wt=["{","}"];return N(A)&&(Ft=!0,Wt=["[","]"]),Y(A)&&(kt=" [Function"+(A.name?": "+A.name:"")+"]"),b(A)&&(kt=" "+RegExp.prototype.toString.call(A)),X(A)&&(kt=" "+Date.prototype.toUTCString.call(A)),K(A)&&(kt=" "+S(A)),gt.length!==0||Ft&&A.length!=0?B<0?b(A)?g.stylize(RegExp.prototype.toString.call(A),"regexp"):g.stylize("[Object]","special"):(g.seen.push(A),St=Ft?(function(ct,it,Tt,Jt,te){for(var ee=[],Yt=0,ln=it.length;Yt=0,Jt+te.replace(/\u001b\[\d\d?m/g,"").length+1}),0)>60?Tt[0]+(it===""?"":it+` + `)+" "+ct.join(`, + `)+" "+Tt[1]:Tt[0]+it+" "+ct.join(", ")+" "+Tt[1]})(St,kt,Wt)):Wt[0]+kt+Wt[1]}function S(g){return"["+Error.prototype.toString.call(g)+"]"}function x(g,A,B,lt,vt,gt){var rt,dt,St;if((St=Object.getOwnPropertyDescriptor(A,vt)||{value:A[vt]}).get?dt=St.set?g.stylize("[Getter/Setter]","special"):g.stylize("[Getter]","special"):St.set&&(dt=g.stylize("[Setter]","special")),Pe(lt,vt)||(rt="["+vt+"]"),dt||(g.seen.indexOf(St.value)<0?(dt=j(B)?h(g,St.value,null):h(g,St.value,B-1)).indexOf(` +`)>-1&&(dt=gt?dt.split(` +`).map((function(kt){return" "+kt})).join(` +`).substr(2):` +`+dt.split(` +`).map((function(kt){return" "+kt})).join(` +`)):dt=g.stylize("[Circular]","special")),d(rt)){if(gt&&vt.match(/^\d+$/))return dt;(rt=JSON.stringify(""+vt)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(rt=rt.substr(1,rt.length-2),rt=g.stylize(rt,"name")):(rt=rt.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),rt=g.stylize(rt,"string"))}return rt+": "+dt}function N(g){return Array.isArray(g)}function F(g){return typeof g=="boolean"}function j(g){return g===null}function q(g){return typeof g=="number"}function i(g){return typeof g=="string"}function d(g){return g===void 0}function b(g){return P(g)&&ft(g)==="[object RegExp]"}function P(g){return typeof g=="object"&&g!==null}function X(g){return P(g)&&ft(g)==="[object Date]"}function K(g){return P(g)&&(ft(g)==="[object Error]"||g instanceof Error)}function Y(g){return typeof g=="function"}function ft(g){return Object.prototype.toString.call(g)}function Et(g){return g<10?"0"+g.toString(10):g.toString(10)}u.debuglog=function(g){if(d(R)&&(R=k.env.NODE_DEBUG||""),g=g.toUpperCase(),!T[g])if(new RegExp("\\b"+g+"\\b","i").test(R)){var A=k.pid;T[g]=function(){var B=u.format.apply(u,arguments);console.error("%s %d: %s",g,A,B)}}else T[g]=function(){};return T[g]},u.inspect=p,p.colors={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[30,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[31,39],yellow:[33,39]},p.styles={special:"cyan",number:"yellow",boolean:"yellow",undefined:"grey",null:"bold",string:"green",date:"magenta",regexp:"red"},u.isArray=N,u.isBoolean=F,u.isNull=j,u.isNullOrUndefined=function(g){return g==null},u.isNumber=q,u.isString=i,u.isSymbol=function(g){return typeof g=="symbol"},u.isUndefined=d,u.isRegExp=b,u.isObject=P,u.isDate=X,u.isError=K,u.isFunction=Y,u.isPrimitive=function(g){return g===null||typeof g=="boolean"||typeof g=="number"||typeof g=="string"||typeof g=="symbol"||g===void 0},u.isBuffer=f(135);var tt=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function J(){var g=new Date,A=[Et(g.getHours()),Et(g.getMinutes()),Et(g.getSeconds())].join(":");return[g.getDate(),tt[g.getMonth()],A].join(" ")}function Pe(g,A){return Object.prototype.hasOwnProperty.call(g,A)}u.log=function(){console.log("%s - %s",J(),u.format.apply(u,arguments))},u.inherits=f(698),u._extend=function(g,A){if(!A||!P(A))return g;for(var B=Object.keys(A),lt=B.length;lt--;)g[B[lt]]=A[B[lt]];return g}}},G={};function y(v){var u=G[v];if(u!==void 0)return u.exports;var f=G[v]={exports:{}};return $[v](f,f.exports,y),f.exports}y.n=v=>{var u=v&&v.__esModule?()=>v.default:()=>v;return y.d(u,{a:u}),u},y.d=(v,u)=>{for(var f in u)y.o(u,f)&&!y.o(v,f)&&Object.defineProperty(v,f,{enumerable:!0,get:u[f]})},y.g=(function(){if(typeof globalThis=="object")return globalThis;try{return this||new Function("return this")()}catch{if(typeof window=="object")return window}})(),y.o=(v,u)=>Object.prototype.hasOwnProperty.call(v,u),y.r=v=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(v,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(v,"__esModule",{value:!0})};var z={};return(()=>{y.r(z),y.d(z,{LSPluginUser:()=>An,setupPluginUserInstance:()=>Kr});var v=y(627);const{entries:u,setPrototypeOf:f,isFrozen:k,getPrototypeOf:w,getOwnPropertyDescriptor:R}=Object;let{freeze:T,seal:p,create:c}=Object,{apply:_,construct:h}=typeof Reflect<"u"&&Reflect;T||(T=function(e){return e}),p||(p=function(e){return e}),_||(_=function(e,t){for(var n=arguments.length,r=new Array(n>2?n-2:0),s=2;s1?t-1:0),r=1;r1?n-1:0),s=1;s2&&arguments[2]!==void 0?arguments[2]:q;f&&f(e,null);let r=t.length;for(;r--;){let s=t[r];if(typeof s=="string"){const a=n(s);a!==s&&(k(t)||(t[r]=a),s=a)}e[s]=!0}return e}function Pe(e){for(let t=0;t/gm),Jt=p(/\$\{[\w\W]*/gm),te=p(/^data-[\-\w.\u00B7-\uFFFF]+$/),ee=p(/^aria-[\-\w]+$/),Yt=p(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ln=p(/^(?:\w+script|data):/i),Me=p(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Jn=p(/^html$/i),Ao=p(/^[a-z][.\w]*(-[.\w]+)+$/i);var Yn=Object.freeze({__proto__:null,ARIA_ATTR:ee,ATTR_WHITESPACE:Me,CUSTOM_ELEMENT:Ao,DATA_ATTR:te,DOCTYPE_NAME:Jn,ERB_EXPR:Tt,IS_ALLOWED_URI:Yt,IS_SCRIPT_OR_DATA:ln,MUSTACHE_EXPR:it,TMPLIT_EXPR:Jt});const Io=1,jo=3,Po=7,Mo=8,Lo=9,Do=function(){return typeof window>"u"?null:window},No=function(e,t){if(typeof e!="object"||typeof e.createPolicy!="function")return null;let n=null;const r="data-tt-policy-suffix";t&&t.hasAttribute(r)&&(n=t.getAttribute(r));const s="dompurify"+(n?"#"+n:"");try{return e.createPolicy(s,{createHTML:a=>a,createScriptURL:a=>a})}catch{return console.warn("TrustedTypes policy "+s+" could not be created."),null}};(function e(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Do();const n=o=>e(o);if(n.version="3.3.1",n.removed=[],!t||!t.document||t.document.nodeType!==Lo||!t.Element)return n.isSupported=!1,n;let{document:r}=t;const s=r,a=s.currentScript,{DocumentFragment:l,HTMLTemplateElement:m,Node:O,Element:L,NodeFilter:U,NamedNodeMap:D=t.NamedNodeMap||t.MozNamedAttrMap,HTMLFormElement:H,DOMParser:Z,trustedTypes:W}=t,V=L.prototype,et=A(V,"cloneNode"),at=A(V,"remove"),xt=A(V,"nextSibling"),Nt=A(V,"childNodes"),Ve=A(V,"parentNode");if(typeof m=="function"){const o=r.createElement("template");o.content&&o.content.ownerDocument&&(r=o.content.ownerDocument)}let _t,Te="";const{implementation:In,createNodeIterator:da,createDocumentFragment:pa,getElementsByTagName:fa}=r,{importNode:ha}=s;let wt={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};n.isSupported=typeof u=="function"&&typeof Ve=="function"&&In&&In.createHTMLDocument!==void 0;const{MUSTACHE_EXPR:jn,ERB_EXPR:Pn,TMPLIT_EXPR:Mn,DATA_ATTR:ga,ARIA_ATTR:ma,IS_SCRIPT_OR_DATA:ya,ATTR_WHITESPACE:Zr,CUSTOM_ELEMENT:ba}=Yn;let{IS_ALLOWED_URI:Vr}=Yn,ht=null;const Xr=J({},[...B,...lt,...vt,...rt,...St]);let mt=null;const Qr=J({},[...kt,...Ft,...Wt,...ct]);let ut=Object.seal(c(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ce=null,Ln=null;const de=Object.seal(c(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let to=!0,Dn=!0,eo=!1,no=!0,pe=!1,Xe=!0,Xt=!1,Nn=!1,Rn=!1,fe=!1,Qe=!1,tn=!1,ro=!0,oo=!1;const va="user-content-";let $n=!0,Oe=!1,he={},Mt=null;const Un=J({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let so=null;const io=J({},["audio","video","img","source","image","track"]);let Fn=null;const ao=J({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),en="http://www.w3.org/1998/Math/MathML",nn="http://www.w3.org/2000/svg",Rt="http://www.w3.org/1999/xhtml";let ge=Rt,zn=!1,qn=null;const _a=J({},[en,nn,Rt],i);let rn=J({},["mi","mo","mn","ms","mtext"]),on=J({},["annotation-xml"]);const wa=J({},["title","style","font","a","script"]);let Ae=null;const xa=["application/xhtml+xml","text/html"],Ea="text/html";let pt=null,me=null;const Sa=r.createElement("form"),lo=function(o){return o instanceof RegExp||o instanceof Function},Bn=function(){let o=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!me||me!==o){if(o&&typeof o=="object"||(o={}),o=g(o),Ae=xa.indexOf(o.PARSER_MEDIA_TYPE)===-1?Ea:o.PARSER_MEDIA_TYPE,pt=Ae==="application/xhtml+xml"?i:q,ht=K(o,"ALLOWED_TAGS")?J({},o.ALLOWED_TAGS,pt):Xr,mt=K(o,"ALLOWED_ATTR")?J({},o.ALLOWED_ATTR,pt):Qr,qn=K(o,"ALLOWED_NAMESPACES")?J({},o.ALLOWED_NAMESPACES,i):_a,Fn=K(o,"ADD_URI_SAFE_ATTR")?J(g(ao),o.ADD_URI_SAFE_ATTR,pt):ao,so=K(o,"ADD_DATA_URI_TAGS")?J(g(io),o.ADD_DATA_URI_TAGS,pt):io,Mt=K(o,"FORBID_CONTENTS")?J({},o.FORBID_CONTENTS,pt):Un,Ce=K(o,"FORBID_TAGS")?J({},o.FORBID_TAGS,pt):g({}),Ln=K(o,"FORBID_ATTR")?J({},o.FORBID_ATTR,pt):g({}),he=!!K(o,"USE_PROFILES")&&o.USE_PROFILES,to=o.ALLOW_ARIA_ATTR!==!1,Dn=o.ALLOW_DATA_ATTR!==!1,eo=o.ALLOW_UNKNOWN_PROTOCOLS||!1,no=o.ALLOW_SELF_CLOSE_IN_ATTR!==!1,pe=o.SAFE_FOR_TEMPLATES||!1,Xe=o.SAFE_FOR_XML!==!1,Xt=o.WHOLE_DOCUMENT||!1,fe=o.RETURN_DOM||!1,Qe=o.RETURN_DOM_FRAGMENT||!1,tn=o.RETURN_TRUSTED_TYPE||!1,Rn=o.FORCE_BODY||!1,ro=o.SANITIZE_DOM!==!1,oo=o.SANITIZE_NAMED_PROPS||!1,$n=o.KEEP_CONTENT!==!1,Oe=o.IN_PLACE||!1,Vr=o.ALLOWED_URI_REGEXP||Yt,ge=o.NAMESPACE||Rt,rn=o.MATHML_TEXT_INTEGRATION_POINTS||rn,on=o.HTML_INTEGRATION_POINTS||on,ut=o.CUSTOM_ELEMENT_HANDLING||{},o.CUSTOM_ELEMENT_HANDLING&&lo(o.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(ut.tagNameCheck=o.CUSTOM_ELEMENT_HANDLING.tagNameCheck),o.CUSTOM_ELEMENT_HANDLING&&lo(o.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(ut.attributeNameCheck=o.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),o.CUSTOM_ELEMENT_HANDLING&&typeof o.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(ut.allowCustomizedBuiltInElements=o.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),pe&&(Dn=!1),Qe&&(fe=!0),he&&(ht=J({},St),mt=[],he.html===!0&&(J(ht,B),J(mt,kt)),he.svg===!0&&(J(ht,lt),J(mt,Ft),J(mt,ct)),he.svgFilters===!0&&(J(ht,vt),J(mt,Ft),J(mt,ct)),he.mathMl===!0&&(J(ht,rt),J(mt,Wt),J(mt,ct))),o.ADD_TAGS&&(typeof o.ADD_TAGS=="function"?de.tagCheck=o.ADD_TAGS:(ht===Xr&&(ht=g(ht)),J(ht,o.ADD_TAGS,pt))),o.ADD_ATTR&&(typeof o.ADD_ATTR=="function"?de.attributeCheck=o.ADD_ATTR:(mt===Qr&&(mt=g(mt)),J(mt,o.ADD_ATTR,pt))),o.ADD_URI_SAFE_ATTR&&J(Fn,o.ADD_URI_SAFE_ATTR,pt),o.FORBID_CONTENTS&&(Mt===Un&&(Mt=g(Mt)),J(Mt,o.FORBID_CONTENTS,pt)),o.ADD_FORBID_CONTENTS&&(Mt===Un&&(Mt=g(Mt)),J(Mt,o.ADD_FORBID_CONTENTS,pt)),$n&&(ht["#text"]=!0),Xt&&J(ht,["html","head","body"]),ht.table&&(J(ht,["tbody"]),delete Ce.tbody),o.TRUSTED_TYPES_POLICY){if(typeof o.TRUSTED_TYPES_POLICY.createHTML!="function")throw ft('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof o.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ft('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');_t=o.TRUSTED_TYPES_POLICY,Te=_t.createHTML("")}else _t===void 0&&(_t=No(W,a)),_t!==null&&typeof Te=="string"&&(Te=_t.createHTML(""));T&&T(o),me=o}},co=J({},[...lt,...vt,...gt]),uo=J({},[...rt,...dt]),ka=function(o){let I=Ve(o);I&&I.tagName||(I={namespaceURI:ge,tagName:"template"});const C=q(o.tagName),Q=q(I.tagName);return!!qn[o.namespaceURI]&&(o.namespaceURI===nn?I.namespaceURI===Rt?C==="svg":I.namespaceURI===en?C==="svg"&&(Q==="annotation-xml"||rn[Q]):!!co[C]:o.namespaceURI===en?I.namespaceURI===Rt?C==="math":I.namespaceURI===nn?C==="math"&&on[Q]:!!uo[C]:o.namespaceURI===Rt?!(I.namespaceURI===nn&&!on[Q])&&!(I.namespaceURI===en&&!rn[Q])&&!uo[C]&&(wa[C]||!co[C]):!(Ae!=="application/xhtml+xml"||!qn[o.namespaceURI]))},Lt=function(o){F(n.removed,{element:o});try{Ve(o).removeChild(o)}catch{at(o)}},Qt=function(o,I){try{F(n.removed,{attribute:I.getAttributeNode(o),from:I})}catch{F(n.removed,{attribute:null,from:I})}if(I.removeAttribute(o),o==="is")if(fe||Qe)try{Lt(I)}catch{}else try{I.setAttribute(o,"")}catch{}},po=function(o){let I=null,C=null;if(Rn)o=""+o;else{const nt=d(o,/^[\r\n\t ]+/);C=nt&&nt[0]}Ae==="application/xhtml+xml"&&ge===Rt&&(o=''+o+"");const Q=_t?_t.createHTML(o):o;if(ge===Rt)try{I=new Z().parseFromString(Q,Ae)}catch{}if(!I||!I.documentElement){I=In.createDocument(ge,"template",null);try{I.documentElement.innerHTML=zn?Te:Q}catch{}}const yt=I.body||I.documentElement;return o&&C&&yt.insertBefore(r.createTextNode(C),yt.childNodes[0]||null),ge===Rt?fa.call(I,Xt?"html":"body")[0]:Xt?I.documentElement:yt},fo=function(o){return da.call(o.ownerDocument||o,o,U.SHOW_ELEMENT|U.SHOW_COMMENT|U.SHOW_TEXT|U.SHOW_PROCESSING_INSTRUCTION|U.SHOW_CDATA_SECTION,null)},Hn=function(o){return o instanceof H&&(typeof o.nodeName!="string"||typeof o.textContent!="string"||typeof o.removeChild!="function"||!(o.attributes instanceof D)||typeof o.removeAttribute!="function"||typeof o.setAttribute!="function"||typeof o.namespaceURI!="string"||typeof o.insertBefore!="function"||typeof o.hasChildNodes!="function")},ho=function(o){return typeof O=="function"&&o instanceof O};function $t(o,I,C){S(o,(Q=>{Q.call(n,I,C,me)}))}const go=function(o){let I=null;if($t(wt.beforeSanitizeElements,o,null),Hn(o))return Lt(o),!0;const C=pt(o.nodeName);if($t(wt.uponSanitizeElement,o,{tagName:C,allowedTags:ht}),Xe&&o.hasChildNodes()&&!ho(o.firstElementChild)&&Y(/<[/\w!]/g,o.innerHTML)&&Y(/<[/\w!]/g,o.textContent)||o.nodeType===Po||Xe&&o.nodeType===Mo&&Y(/<[/\w]/g,o.data))return Lt(o),!0;if(!(de.tagCheck instanceof Function&&de.tagCheck(C))&&(!ht[C]||Ce[C])){if(!Ce[C]&&yo(C)&&(ut.tagNameCheck instanceof RegExp&&Y(ut.tagNameCheck,C)||ut.tagNameCheck instanceof Function&&ut.tagNameCheck(C)))return!1;if($n&&!Mt[C]){const Q=Ve(o)||o.parentNode,yt=Nt(o)||o.childNodes;if(yt&&Q)for(let nt=yt.length-1;nt>=0;--nt){const Ht=et(yt[nt],!0);Ht.__removalCount=(o.__removalCount||0)+1,Q.insertBefore(Ht,xt(o))}}return Lt(o),!0}return o instanceof L&&!ka(o)?(Lt(o),!0):C!=="noscript"&&C!=="noembed"&&C!=="noframes"||!Y(/<\/no(script|embed|frames)/i,o.innerHTML)?(pe&&o.nodeType===jo&&(I=o.textContent,S([jn,Pn,Mn],(Q=>{I=b(I,Q," ")})),o.textContent!==I&&(F(n.removed,{element:o.cloneNode()}),o.textContent=I)),$t(wt.afterSanitizeElements,o,null),!1):(Lt(o),!0)},mo=function(o,I,C){if(ro&&(I==="id"||I==="name")&&(C in r||C in Sa))return!1;if(!(Dn&&!Ln[I]&&Y(ga,I))){if(!(to&&Y(ma,I))){if(!(de.attributeCheck instanceof Function&&de.attributeCheck(I,o))){if(!mt[I]||Ln[I]){if(!(yo(o)&&(ut.tagNameCheck instanceof RegExp&&Y(ut.tagNameCheck,o)||ut.tagNameCheck instanceof Function&&ut.tagNameCheck(o))&&(ut.attributeNameCheck instanceof RegExp&&Y(ut.attributeNameCheck,I)||ut.attributeNameCheck instanceof Function&&ut.attributeNameCheck(I,o))||I==="is"&&ut.allowCustomizedBuiltInElements&&(ut.tagNameCheck instanceof RegExp&&Y(ut.tagNameCheck,C)||ut.tagNameCheck instanceof Function&&ut.tagNameCheck(C))))return!1}else if(!Fn[I]){if(!Y(Vr,b(C,Zr,""))){if((I!=="src"&&I!=="xlink:href"&&I!=="href"||o==="script"||P(C,"data:")!==0||!so[o])&&!(eo&&!Y(ya,b(C,Zr,"")))){if(C)return!1}}}}}}return!0},yo=function(o){return o!=="annotation-xml"&&d(o,ba)},bo=function(o){$t(wt.beforeSanitizeAttributes,o,null);const{attributes:I}=o;if(!I||Hn(o))return;const C={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:mt,forceKeepAttr:void 0};let Q=I.length;for(;Q--;){const yt=I[Q],{name:nt,namespaceURI:Ht,value:Gt}=yt,At=pt(nt),Gn=Gt;let bt=nt==="value"?Gn:X(Gn);if(C.attrName=At,C.attrValue=bt,C.keepAttr=!0,C.forceKeepAttr=void 0,$t(wt.uponSanitizeAttribute,o,C),bt=C.attrValue,!oo||At!=="id"&&At!=="name"||(Qt(nt,o),bt=va+bt),Xe&&Y(/((--!?|])>)|<\/(style|title|textarea)/i,bt)){Qt(nt,o);continue}if(At==="attributename"&&d(bt,"href")){Qt(nt,o);continue}if(C.forceKeepAttr)continue;if(!C.keepAttr){Qt(nt,o);continue}if(!no&&Y(/\/>/i,bt)){Qt(nt,o);continue}pe&&S([jn,Pn,Mn],(_o=>{bt=b(bt,_o," ")}));const vo=pt(o.nodeName);if(mo(vo,At,bt)){if(_t&&typeof W=="object"&&typeof W.getAttributeType=="function"&&!Ht)switch(W.getAttributeType(vo,At)){case"TrustedHTML":bt=_t.createHTML(bt);break;case"TrustedScriptURL":bt=_t.createScriptURL(bt)}if(bt!==Gn)try{Ht?o.setAttributeNS(Ht,nt,bt):o.setAttribute(nt,bt),Hn(o)?Lt(o):N(n.removed)}catch{Qt(nt,o)}}else Qt(nt,o)}$t(wt.afterSanitizeAttributes,o,null)},Ta=function o(I){let C=null;const Q=fo(I);for($t(wt.beforeSanitizeShadowDOM,I,null);C=Q.nextNode();)$t(wt.uponSanitizeShadowNode,C,null),go(C),bo(C),C.content instanceof l&&o(C.content);$t(wt.afterSanitizeShadowDOM,I,null)};return n.sanitize=function(o){let I=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},C=null,Q=null,yt=null,nt=null;if(zn=!o,zn&&(o=""),typeof o!="string"&&!ho(o)){if(typeof o.toString!="function")throw ft("toString is not a function");if(typeof(o=o.toString())!="string")throw ft("dirty is not a string, aborting")}if(!n.isSupported)return o;if(Nn||Bn(I),n.removed=[],typeof o=="string"&&(Oe=!1),Oe){if(o.nodeName){const At=pt(o.nodeName);if(!ht[At]||Ce[At])throw ft("root node is forbidden and cannot be sanitized in-place")}}else if(o instanceof O)C=po(""),Q=C.ownerDocument.importNode(o,!0),Q.nodeType===Io&&Q.nodeName==="BODY"||Q.nodeName==="HTML"?C=Q:C.appendChild(Q);else{if(!fe&&!pe&&!Xt&&o.indexOf("<")===-1)return _t&&tn?_t.createHTML(o):o;if(C=po(o),!C)return fe?null:tn?Te:""}C&&Rn&&Lt(C.firstChild);const Ht=fo(Oe?o:C);for(;yt=Ht.nextNode();)go(yt),bo(yt),yt.content instanceof l&&Ta(yt.content);if(Oe)return o;if(fe){if(Qe)for(nt=pa.call(C.ownerDocument);C.firstChild;)nt.appendChild(C.firstChild);else nt=C;return(mt.shadowroot||mt.shadowrootmode)&&(nt=ha.call(s,nt,!0)),nt}let Gt=Xt?C.outerHTML:C.innerHTML;return Xt&&ht["!doctype"]&&C.ownerDocument&&C.ownerDocument.doctype&&C.ownerDocument.doctype.name&&Y(Jn,C.ownerDocument.doctype.name)&&(Gt=" +`+Gt),pe&&S([jn,Pn,Mn],(At=>{Gt=b(Gt,At," ")})),_t&&tn?_t.createHTML(Gt):Gt},n.setConfig=function(){let o=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};Bn(o),Nn=!0},n.clearConfig=function(){me=null,Nn=!1},n.isValidAttribute=function(o,I,C){me||Bn({});const Q=pt(o),yt=pt(I);return mo(Q,yt,C)},n.addHook=function(o,I){typeof I=="function"&&F(wt[o],I)},n.removeHook=function(o,I){if(I!==void 0){const C=x(wt[o],I);return C===-1?void 0:j(wt[o],C,1)[0]}return N(wt[o])},n.removeHooks=function(o){wt[o]=[]},n.removeAllHooks=function(){wt={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},n})();var Ro=y(744),$o=y.n(Ro),Le=function(){return Le=Object.assign||function(e){for(var t,n=1,r=arguments.length;n(O&&O instanceof Error?m+=`${O.message} ${O.stack}`:m+=O.toString(),m)),`[${this._tag}][${new Date().toLocaleTimeString()}] `);var l;this._logs.push([t,a]),(r||(s=this._opts)!==null&&s!==void 0&&s.console)&&((l=console)===null||l===void 0||l[t==="ERROR"?"error":"debug"](`${t}: ${a}`)),this.emit("change")}clear(){this._logs=[],this.emit("change")}info(...t){this.write("INFO",t)}error(...t){this.write("ERROR",t)}warn(...t){this.write("WARN",t)}setTag(t){this._tag=t}toJSON(){return this._logs}}function Ho(e,...t){try{const n=new URL(e);if(!n.origin)throw new Error(null);const r=Vn.join(e.substr(n.origin.length),...t);return n.origin+r}catch{return Vn.join(e,...t)}}function un(e,t){let n,r,s=!1;const a=m=>O=>{e&&clearTimeout(e),m(O),s=!0},l=new Promise(((m,O)=>{n=a(m),r=a(O),e&&(e=setTimeout((()=>r(new Error(`[deferred timeout] ${t}`))),e))}));return{created:Date.now(),setTag:m=>t=m,resolve:n,reject:r,promise:l,get settled(){return s}}}const Go=new Map;window.__injectedUIEffects=Go;var Wo=y(833),Xn=y.n(Wo);function st(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const zt="application/x-postmate-v1+json";let Jo=0;const Yo={handshake:1,"handshake-reply":1,call:1,emit:1,reply:1,request:1},dn=(e,t)=>(typeof t!="string"||e.origin===t)&&!!e.data&&(typeof e.data!="object"||"postmate"in e.data)&&e.data.type===zt&&!!Yo[e.data.postmate];class Ko{constructor(t){st(this,"parent",void 0),st(this,"frame",void 0),st(this,"child",void 0),st(this,"events",{}),st(this,"childOrigin",void 0),st(this,"listener",void 0),this.parent=t.parent,this.frame=t.frame,this.child=t.child,this.childOrigin=t.childOrigin,this.listener=n=>{if(!dn(n,this.childOrigin))return!1;const{data:r,name:s}=((n||{}).data||{}).value||{};n.data.postmate==="emit"&&s in this.events&&this.events[s].forEach((a=>{a.call(this,r)}))},this.parent.addEventListener("message",this.listener,!1)}get(t,...n){return new Promise(((r,s)=>{const a=++Jo,l=m=>{m.data.uid===a&&m.data.postmate==="reply"&&(this.parent.removeEventListener("message",l,!1),m.data.error?s(m.data.error):r(m.data.value))};this.parent.addEventListener("message",l,!1),this.child.postMessage({postmate:"request",type:zt,property:t,args:n,uid:a},this.childOrigin)}))}call(t,n){this.child.postMessage({postmate:"call",type:zt,property:t,data:n},this.childOrigin)}on(t,n){this.events[t]||(this.events[t]=[]),this.events[t].push(n)}destroy(){window.removeEventListener("message",this.listener,!1),this.frame.parentNode.removeChild(this.frame)}}class Zo{constructor(t){st(this,"model",void 0),st(this,"parent",void 0),st(this,"parentOrigin",void 0),st(this,"child",void 0),this.model=t.model,this.parent=t.parent,this.parentOrigin=t.parentOrigin,this.child=t.child,this.child.addEventListener("message",(n=>{if(!dn(n,this.parentOrigin))return;const{property:r,uid:s,data:a,args:l}=n.data;n.data.postmate!=="call"?((m,O,L)=>{const U=typeof m[O]=="function"?m[O].apply(null,L):m[O];return Promise.resolve(U)})(this.model,r,l).then((m=>{n.source.postMessage({property:r,postmate:"reply",type:zt,uid:s,value:m},n.origin)})).catch((m=>{n.source.postMessage({property:r,postmate:"reply",type:zt,uid:s,error:m},n.origin)})):r in this.model&&typeof this.model[r]=="function"&&this.model[r](a)}))}emit(t,n){this.parent.postMessage({postmate:"emit",type:zt,value:{name:t,data:n}},this.parentOrigin)}}class pn{constructor(t){st(this,"container",void 0),st(this,"parent",void 0),st(this,"frame",void 0),st(this,"child",void 0),st(this,"childOrigin",void 0),st(this,"url",void 0),st(this,"model",void 0),this.container=t.container,this.url=t.url,this.parent=window,this.frame=document.createElement("iframe"),t.id&&(this.frame.id=t.id),t.name&&(this.frame.name=t.name),t.allow&&(this.frame.allow=t.allow),this.frame.classList.add.apply(this.frame.classList,t.classListArray||[]),this.container.appendChild(this.frame),this.child=this.frame.contentWindow,this.model=t.model||{}}sendHandshake(t){const n=(a=>{const l=document.createElement("a");l.href=a;const m=l.protocol.length>4?l.protocol:window.location.protocol,O=l.host.length?l.port==="80"||l.port==="443"?l.hostname:l.host:window.location.host;return l.origin||`${m}//${O}`})(t=t||this.url);let r,s=0;return new Promise(((a,l)=>{const m=L=>!!dn(L,n)&&(L.data.postmate==="handshake-reply"?(clearInterval(r),this.parent.removeEventListener("message",m,!1),this.childOrigin=L.origin,a(new Ko(this))):l("Failed handshake"));this.parent.addEventListener("message",m,!1);const O=()=>{s++,this.child.postMessage({postmate:"handshake",type:zt,model:this.model},n),s===5&&clearInterval(r)};this.frame.addEventListener("load",(()=>{O(),r=setInterval(O,500)})),this.frame.src=t}))}destroy(){this.frame.parentNode.removeChild(this.frame)}}st(pn,"debug",!1),st(pn,"Model",void 0);class Vo{constructor(t){st(this,"child",void 0),st(this,"model",void 0),st(this,"parent",void 0),st(this,"parentOrigin",void 0),this.child=window,this.model=t,this.parent=this.child.parent}sendHandshakeReply(){return new Promise(((t,n)=>{const r=s=>{if(s.data.postmate){if(s.data.postmate==="handshake"){this.child.removeEventListener("message",r,!1),s.source.postMessage({postmate:"handshake-reply",type:zt},s.origin),this.parentOrigin=s.origin;const a=s.data.model;return a&&Object.keys(a).forEach((l=>{this.model[l]=a[l]})),t(new Zo(this))}return n("Handshake Reply Failed")}};this.child.addEventListener("message",r,!1)}))}}function be(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const{importHTML:Xo,createSandboxContainer:Qo}=window.QSandbox||{};function ts(e,t){return e.startsWith("http")?fetch(e,t):(e=e.replace("file://",""),new Promise((async(n,r)=>{try{const s=await window.apis.doAction(["readFile",e]);n({text:()=>s})}catch(s){console.error(s),r(s)}})))}class es extends De(){constructor(t){super(),be(this,"_pluginLocal",void 0),be(this,"_frame",void 0),be(this,"_root",void 0),be(this,"_loaded",!1),be(this,"_unmountFns",[]),this._pluginLocal=t,t._dispose((()=>{this._unmount()}))}async load(){const{name:t,entry:n}=this._pluginLocal.options;if(this.loaded||!n)return;const{template:r,execScripts:s}=await Xo(n,{fetch:ts});this._mount(r,document.body);const a=Qo(t,{elementGetter:()=>{var m;return(m=this._root)===null||m===void 0?void 0:m.firstChild}}).instance.proxy;a.__shadow_mode__=!0,a.LSPluginLocal=this._pluginLocal,a.LSPluginShadow=this,a.LSPluginUser=a.logseq=new An(this._pluginLocal.toJSON(),this._pluginLocal.caller);const l=await s(a,!0);this._unmountFns.push(l.unmount),this._loaded=!0}_mount(t,n){const r=this._frame=document.createElement("div");r.classList.add("lsp-shadow-sandbox"),r.id=this._pluginLocal.id,this._root=r.attachShadow({mode:"open"}),this._root.innerHTML=`
${t}
`,n.appendChild(r),this.emit("mounted")}_unmount(){for(const t of this._unmountFns)t&&t.call(null)}destroy(){var t,n;(t=this.frame)===null||t===void 0||(n=t.parentNode)===null||n===void 0||n.removeChild(this.frame)}get loaded(){return this._loaded}get document(){var t;return(t=this._root)===null||t===void 0?void 0:t.firstChild}get frame(){return this._frame}}function It(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const qt=Xn()("LSPlugin:caller"),ve="#await#response#",ns="#lspmsg#",Qn="#lspmsg#error#",Ne=e=>`#lspmsg#${e}`;class rs extends De(){constructor(t){super(),It(this,"_pluginLocal",void 0),It(this,"_connected",!1),It(this,"_parent",void 0),It(this,"_child",void 0),It(this,"_shadow",void 0),It(this,"_status",void 0),It(this,"_userModel",{}),It(this,"_call",void 0),It(this,"_callUserModel",void 0),It(this,"_debugTag",""),this._pluginLocal=t,t&&(this._debugTag=t.debugTag)}async connectToChild(){if(this._connected)return;const{shadow:t}=this._pluginLocal;t?await this._setupShadowSandbox():await this._setupIframeSandbox()}async connectToParent(t={}){if(this._connected)return;const n=this,r=this._pluginLocal!=null;let s=0;const a=new Map,l=un(6e4),m=this._extendUserModel({"#lspmsg#ready#":async U=>{m[Ne(U?.pid)]=({type:D,payload:H})=>{qt(`[host (_call) -> *user] ${this._debugTag}`,D,H),n.emit(D,H)},await l.resolve()},"#lspmsg#beforeunload#":async U=>{const D=un(1e4);n.emit("beforeunload",Object.assign({actor:D},U)),await D.promise},"#lspmsg#settings#":async({type:U,payload:D})=>{n.emit("settings:changed",D)},[ns]:async({ns:U,type:D,payload:H})=>{qt(`[host (async) -> *user] ${this._debugTag} ns=${U} type=${D}`,H),U&&U.startsWith("hook")?n.emit(`${U}:${D}`,H):n.emit(D,H)},"#lspmsg#reply#":({_sync:U,result:D})=>{if(qt(`[sync host -> *user] #${U}`,D),a.has(U)){const H=a.get(U);H&&(D!=null&&D.hasOwnProperty(Qn)?H.reject(D[Qn]):H.resolve(D),a.delete(U))}},...t});var O;if(r)return await l.promise,JSON.parse(JSON.stringify((O=this._pluginLocal)===null||O===void 0?void 0:O.toJSON()));const L=new Vo(m).sendHandshakeReply();return this._status="pending",await L.then((U=>{this._child=U,this._connected=!0,this._call=async(D,H={},Z)=>{if(Z){const W=++s;a.set(W,Z),H._sync=W,Z.setTag(`async call #${W}`),qt(`async call #${W}`)}return U.emit(Ne(m.baseInfo.id),{type:D,payload:H}),Z?.promise},this._callUserModel=async(D,H)=>{try{m[D](H)}catch{qt(`[model method] #${D} not existed`)}},setInterval((()=>{if(a.size>100)for(const[D,H]of a)H.settled&&a.delete(D)}),18e5)})).finally((()=>{this._status=void 0})),await l.promise,m.baseInfo}async call(t,n={}){var r;return(r=this._call)===null||r===void 0?void 0:r.call(this,t,n)}async callAsync(t,n={}){var r;const s=un(1e4);return(r=this._call)===null||r===void 0?void 0:r.call(this,t,n,s)}async callUserModel(t,...n){var r;return(r=this._callUserModel)===null||r===void 0?void 0:r.apply(this,[t,...n])}async callUserModelAsync(t,...n){var r;return t=`${ve}${t}`,(r=this._callUserModel)===null||r===void 0?void 0:r.apply(this,[t,...n])}async _setupIframeSandbox(){const t=this._pluginLocal,n=t.id,r=`${n}_lsp_main`,s=new URL(t.options.entry);s.searchParams.set("__v__",t.options.version);const a=document.querySelector(`#${r}`);a&&a.parentElement.removeChild(a);const l=document.createElement("div");l.classList.add("lsp-iframe-sandbox-container"),l.id=r,l.dataset.pid=n;try{var m;const D=(m=await this._pluginLocal._loadLayoutsData())===null||m===void 0?void 0:m.$$0;if(D){l.dataset.inited_layout="true";let{width:H,height:Z,left:W,top:V,vw:et,vh:at}=D;W=Math.max(W,0),W=typeof et=="number"?`${Math.min(100*W/et,99)}%`:`${W}px`,V=Math.max(V,45),V=typeof at=="number"?`${Math.min(100*V/at,99)}%`:`${V}px`,Object.assign(l.style,{width:H+"px",height:Z+"px",left:W,top:V})}}catch(D){console.error("[Restore Layout Error]",D)}document.body.appendChild(l);const O=new pn({id:n+"_iframe",container:l,url:s.href,classListArray:["lsp-iframe-sandbox"],model:{baseInfo:JSON.parse(JSON.stringify(t.toJSON()))},allow:t.options.allow});let L,U=O.sendHandshake();return this._status="pending",new Promise(((D,H)=>{L=setTimeout((()=>{H(new Error("handshake Timeout")),O.destroy()}),8e3),U.then((Z=>{this._parent=Z,this._connected=!0,this.emit("connected"),Z.on(Ne(t.id),(({type:W,payload:V})=>{var et,at;qt("[user -> *host] ",W,V),(et=this._pluginLocal)===null||et===void 0||et.emit(W,V||{}),(at=this._pluginLocal)===null||at===void 0||at.caller.emit(W,V||{})})),this._call=async(...W)=>{Z.call(Ne(t.id),{type:W[0],payload:Object.assign(W[1]||{},{$$pid:t.id})})},this._callUserModel=async(W,...V)=>{if(W.startsWith(ve))return await Z.get(W.replace(ve,""),...V);Z.call(W,V?.[0])},D(null)})).catch((Z=>{H(Z)})).finally((()=>{clearTimeout(L)}))})).catch((D=>{throw qt("[iframe sandbox] error",D),D})).finally((()=>{this._status=void 0}))}async _setupShadowSandbox(){const t=this._pluginLocal,n=this._shadow=new es(t);try{this._status="pending",await n.load(),this._connected=!0,this.emit("connected"),this._call=async(r,s={},a)=>{var l;return a&&(s.actor=a),(l=this._pluginLocal)===null||l===void 0||l.emit(r,Object.assign(s,{$$pid:t.id})),a?.promise},this._callUserModel=async(...r)=>{var s;let a=r[0];(s=a)!==null&&s!==void 0&&s.startsWith(ve)&&(a=a.replace(ve,""));const l=r[1]||{},m=this._userModel[a];typeof m=="function"&&await m.call(null,l)}}catch(r){throw qt("[shadow sandbox] error",r),r}finally{this._status=void 0}}_extendUserModel(t){return Object.assign(this._userModel,t)}_getSandboxIframeContainer(){var t;return(t=this._parent)===null||t===void 0?void 0:t.frame.parentNode}_getSandboxShadowContainer(){var t;return(t=this._shadow)===null||t===void 0?void 0:t.frame.parentNode}_getSandboxIframeRoot(){var t;return(t=this._parent)===null||t===void 0?void 0:t.frame}_getSandboxShadowRoot(){var t;return(t=this._shadow)===null||t===void 0?void 0:t.frame}set debugTag(t){this._debugTag=t}async destroy(){var t;let n=null;this._parent&&(n=this._getSandboxIframeContainer(),await this._parent.destroy()),this._shadow&&(n=this._getSandboxShadowContainer(),this._shadow.destroy()),(t=n)===null||t===void 0||t.parentNode.removeChild(n)}}function tr(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}class er{constructor(t,n){tr(this,"ctx",void 0),tr(this,"opts",void 0),this.ctx=t,this.opts=n}get ctxId(){return this.ctx.baseInfo.id}setItem(t,n){var r;return this.ctx.caller.callAsync("api:call",{method:"write-plugin-storage-file",args:[this.ctxId,t,n,(r=this.opts)===null||r===void 0?void 0:r.assets]})}getItem(t){var n;return this.ctx.caller.callAsync("api:call",{method:"read-plugin-storage-file",args:[this.ctxId,t,(n=this.opts)===null||n===void 0?void 0:n.assets]})}removeItem(t){var n;return this.ctx.caller.call("api:call",{method:"unlink-plugin-storage-file",args:[this.ctxId,t,(n=this.opts)===null||n===void 0?void 0:n.assets]})}allKeys(){var t;return this.ctx.caller.callAsync("api:call",{method:"list-plugin-storage-files",args:[this.ctxId,(t=this.opts)===null||t===void 0?void 0:t.assets]})}clear(){var t;return this.ctx.caller.call("api:call",{method:"clear-plugin-storage-files",args:[this.ctxId,(t=this.opts)===null||t===void 0?void 0:t.assets]})}hasItem(t){var n;return this.ctx.caller.callAsync("api:call",{method:"exist-plugin-storage-file",args:[this.ctxId,t,(n=this.opts)===null||n===void 0?void 0:n.assets]})}}class os{constructor(t){var n,r,s;s=void 0,(r="ctx")in(n=this)?Object.defineProperty(n,r,{value:s,enumerable:!0,configurable:!0,writable:!0}):n[r]=s,this.ctx=t}get React(){return this.ensureHostScope().React}get ReactDOM(){return this.ensureHostScope().ReactDOM}get Components(){return{Editor:this.ensureHostScope().logseq.sdk.experiments.cp_page_editor}}get Utils(){const t=this.ensureHostScope().logseq.sdk.utils,n=r=>t[ne(r)];return{toClj:n("toClj"),jsxToClj:n("jsxToClj"),toJs:n("toJs"),toKeyword:n("toKeyword"),toSymbol:n("toSymbol")}}get pluginLocal(){return this.ensureHostScope().LSPluginCore.ensurePlugin(this.ctx.baseInfo.id)}invokeExperMethod(t,...n){var r;const s=this.ensureHostScope();t=(r=ne(t))===null||r===void 0?void 0:r.toLowerCase();const a=s.logseq.api["exper_"+t]||s.logseq.sdk.experiments[t];return a?.apply(s,n)}async loadScripts(...t){(t=t.map((n=>n!=null&&n.startsWith("http")?n:this.ctx.resolveResourceFullUrl(n)))).unshift(this.ctx.baseInfo.id),await this.invokeExperMethod("loadScripts",...t)}registerFencedCodeRenderer(t,n){return this.invokeExperMethod("registerFencedCodeRenderer",this.ctx.baseInfo.id,t,n)}registerDaemonRenderer(t,n){return this.invokeExperMethod("registerDaemonRenderer",this.ctx.baseInfo.id,t,n)}registerRouteRenderer(t,n){return this.invokeExperMethod("registerRouteRenderer",this.ctx.baseInfo.id,t,n)}registerExtensionsEnhancer(t,n){const r=this.ensureHostScope();return t==="katex"&&r.katex&&n(r.katex).catch(console.error),this.invokeExperMethod("registerExtensionsEnhancer",this.ctx.baseInfo.id,t,n)}ensureHostScope(){try{var t;(t=window.top)===null||t===void 0||t.document}catch{console.error("Can not access host scope!")}return window.top}}function re(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const nr=e=>`task_callback_${e}`;class ss{constructor(t,n,r={}){re(this,"_client",void 0),re(this,"_requestId",void 0),re(this,"_requestOptions",void 0),re(this,"_promise",void 0),re(this,"_aborted",!1),this._client=t,this._requestId=n,this._requestOptions=r,this._promise=new Promise(((m,O)=>{if(!this._requestId)return O(null);this._client.once(nr(this._requestId),(L=>{L&&L instanceof Error?O(L):m(L)}))}));const{success:s,fail:a,final:l}=this._requestOptions;this._promise.then((m=>{s?.(m)})).catch((m=>{a?.(m)})).finally((()=>{l?.()}))}abort(){this._requestOptions.abortable&&!this._aborted&&(this._client.ctx._execCallableAPI("http_request_abort",this._requestId),this._aborted=!0)}get promise(){return this._promise}get client(){return this._client}get requestId(){return this._requestId}}class fn extends Zn.EventEmitter{constructor(t){super(),re(this,"_ctx",void 0),this._ctx=t,this.ctx.caller.on("#lsp#request#callback",(n=>{const r=n?.requestId;r&&this.emit(nr(r),n?.payload)}))}static createRequestTask(t,n,r){return new ss(t,n,r)}async _request(t){const n=this.ctx.baseInfo.id,{success:r,fail:s,final:a,...l}=t,m=this.ctx.Experiments.invokeExperMethod("request",n,l),O=fn.createRequestTask(this.ctx.Request,m,t);return l.abortable?O:O.promise}get ctx(){return this._ctx}}const jt=Array.isArray,rr=typeof sn=="object"&&sn&&sn.Object===Object&&sn;var is=typeof self=="object"&&self&&self.Object===Object&&self;const Dt=rr||is||Function("return this")(),Bt=Dt.Symbol;var or=Object.prototype,as=or.hasOwnProperty,ls=or.toString,_e=Bt?Bt.toStringTag:void 0;const cs=function(e){var t=as.call(e,_e),n=e[_e];try{e[_e]=void 0;var r=!0}catch{}var s=ls.call(e);return r&&(t?e[_e]=n:delete e[_e]),s};var us=Object.prototype.toString;const ds=function(e){return us.call(e)};var sr=Bt?Bt.toStringTag:void 0;const oe=function(e){return e==null?e===void 0?"[object Undefined]":"[object Null]":sr&&sr in Object(e)?cs(e):ds(e)},hn=function(e){var t=typeof e;return e!=null&&(t=="object"||t=="function")},gn=function(e){if(!hn(e))return!1;var t=oe(e);return t=="[object Function]"||t=="[object GeneratorFunction]"||t=="[object AsyncFunction]"||t=="[object Proxy]"},mn=Dt["__core-js_shared__"];var ir,ar=(ir=/[^.]+$/.exec(mn&&mn.keys&&mn.keys.IE_PROTO||""))?"Symbol(src)_1."+ir:"";const ps=function(e){return!!ar&&ar in e};var fs=Function.prototype.toString;const Kt=function(e){if(e!=null){try{return fs.call(e)}catch{}try{return e+""}catch{}}return""};var hs=/^\[object .+?Constructor\]$/,gs=Function.prototype,ms=Object.prototype,ys=gs.toString,bs=ms.hasOwnProperty,vs=RegExp("^"+ys.call(bs).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");const _s=function(e){return!(!hn(e)||ps(e))&&(gn(e)?vs:hs).test(Kt(e))},ws=function(e,t){return e?.[t]},Zt=function(e,t){var n=ws(e,t);return _s(n)?n:void 0},lr=(function(){try{var e=Zt(Object,"defineProperty");return e({},"",{}),e}catch{}})(),xs=function(e,t,n){t=="__proto__"&&lr?lr(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n},Es=(function(e){return function(t,n,r){for(var s=-1,a=Object(t),l=r(t),m=l.length;m--;){var O=l[++s];if(n(a[O],O,a)===!1)break}return t}})(),Ss=function(e,t){for(var n=-1,r=Array(e);++n-1&&e%1==0&&e-1&&e%1==0&&e<=9007199254740991};var ot={};ot["[object Float32Array]"]=ot["[object Float64Array]"]=ot["[object Int8Array]"]=ot["[object Int16Array]"]=ot["[object Int32Array]"]=ot["[object Uint8Array]"]=ot["[object Uint8ClampedArray]"]=ot["[object Uint16Array]"]=ot["[object Uint32Array]"]=!0,ot["[object Arguments]"]=ot["[object Array]"]=ot["[object ArrayBuffer]"]=ot["[object Boolean]"]=ot["[object DataView]"]=ot["[object Date]"]=ot["[object Error]"]=ot["[object Function]"]=ot["[object Map]"]=ot["[object Number]"]=ot["[object Object]"]=ot["[object RegExp]"]=ot["[object Set]"]=ot["[object String]"]=ot["[object WeakMap]"]=!1;const As=function(e){return se(e)&&bn(e.length)&&!!ot[oe(e)]},Is=function(e){return function(t){return e(t)}};var mr=M&&!M.nodeType&&M,we=mr&&!0&&E&&!E.nodeType&&E,vn=we&&we.exports===mr&&rr.process,yr=(function(){try{var e=we&&we.require&&we.require("util").types;return e||vn&&vn.binding&&vn.binding("util")}catch{}})(),br=yr&&yr.isTypedArray;const vr=br?Is(br):As;var js=Object.prototype.hasOwnProperty;const Ps=function(e,t){var n=jt(e),r=!n&&dr(e),s=!n&&!r&&yn(e),a=!n&&!r&&!s&&vr(e),l=n||r||s||a,m=l?Ss(e.length,String):[],O=m.length;for(var L in e)!js.call(e,L)||l&&(L=="length"||s&&(L=="offset"||L=="parent")||a&&(L=="buffer"||L=="byteLength"||L=="byteOffset")||gr(L,O))||m.push(L);return m};var Ms=Object.prototype;const Ls=function(e){var t=e&&e.constructor;return e===(typeof t=="function"&&t.prototype||Ms)},Ds=(function(e,t){return function(n){return e(t(n))}})(Object.keys,Object);var Ns=Object.prototype.hasOwnProperty;const Rs=function(e){if(!Ls(e))return Ds(e);var t=[];for(var n in Object(e))Ns.call(e,n)&&n!="constructor"&&t.push(n);return t},$s=function(e){return e!=null&&bn(e.length)&&!gn(e)},_n=function(e){return $s(e)?Ps(e):Rs(e)},Us=function(e,t){return e&&Es(e,t,_n)},Fs=function(){this.__data__=[],this.size=0},_r=function(e,t){return e===t||e!=e&&t!=t},Re=function(e,t){for(var n=e.length;n--;)if(_r(e[n][0],t))return n;return-1};var zs=Array.prototype.splice;const qs=function(e){var t=this.__data__,n=Re(t,e);return!(n<0)&&(n==t.length-1?t.pop():zs.call(t,n,1),--this.size,!0)},Bs=function(e){var t=this.__data__,n=Re(t,e);return n<0?void 0:t[n][1]},Hs=function(e){return Re(this.__data__,e)>-1},Gs=function(e,t){var n=this.__data__,r=Re(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this};function ie(e){var t=-1,n=e==null?0:e.length;for(this.clear();++tm))return!1;var L=a.get(e),U=a.get(t);if(L&&U)return L==t&&U==e;var D=-1,H=!0,Z=2&n?new pi:void 0;for(a.set(e,t),a.set(t,e);++D(jt(r?.blocks)&&(r.blocks=r.blocks.map((s=>s&&ta(s,((a,l)=>`block/${l}`))))),r)},rebuildBlocksIndice:{f:"onIndiceInit",args:["graph","blocks"]},transactBlocks:{f:"onBlocksChanged",args:["graph","data"]},truncateBlocks:{f:"onIndiceReset",args:["graph"]},removeDb:{f:"onGraph",args:["graph"]}}).forEach((([r,s])=>{const a=(l=>`service:search:${l}:${n.name}`)(r);t.caller.on(a,(async l=>{if(gn(n?.[s.f])){let m=null;try{m=await n[s.f].apply(n,(s.args||[]).map((O=>{if(l){if(O===!0)return l;if(l.hasOwnProperty(O)){const L=l[O];return delete l[O],L}}}))),s.transformOutput&&(m=s.transformOutput(m))}catch(O){console.error("[SearchService] ",O),m=O}finally{s.reply&&t.caller.call(`${a}:reply`,m)}}}))}))}}function Se(e,t,n){(function(r,s){if(s.has(r))throw new TypeError("Cannot initialize the same private elements twice on an object")})(e,t),t.set(e,n)}function Ot(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function ke(e,t,n){return(function(r,s,a){if(s.set)s.set.call(r,a);else{if(!s.writable)throw new TypeError("attempted to set read only private field");s.value=a}})(e,Hr(e,t,"set"),n),n}function Pt(e,t){return(function(n,r){return r.get?r.get.call(n):r.value})(e,Hr(e,t,"get"))}function Hr(e,t,n){if(!t.has(e))throw new TypeError("attempted to "+n+" private field on non-instance");return t.get(e)}const na=Symbol.for("proxy-continue"),ra=Xn()("LSPlugin:user"),Gr=new Bo("",{console:!0});function ue(e,t,n){var r;const{key:s,label:a,desc:l,palette:m,keybinding:O,extras:L}=t;if(typeof n!="function")return this.logger.error(`${s||a}: command action should be function.`),!1;const U=(function(H){if(typeof H=="string")return H.trim().replace(/\s/g,"_").toLowerCase()})(s);if(!U)return this.logger.error(`${a}: command key is required.`),!1;const D=`SimpleCommandHook${U}${++Yr}`;this.Editor["on"+D](n),(r=this.caller)===null||r===void 0||r.call("api:call",{method:"register-plugin-simple-command",args:[this.baseInfo.id,[{key:U,label:a,type:e,desc:l,keybinding:O,extras:L},["editor/hook",D]],m]})}function Wr(e){return!(typeof(t=e)!="string"||t.length!==36||!/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi.test(t))||(Gr.error(`#${e} is not a valid UUID string.`),!1);var t}let Ge=null,Jr=new Map;const oa={async getInfo(e){return Ge||(Ge=await this._execCallableAPIAsync("get-app-info")),typeof e=="string"?Ge[e]:Ge},registerCommand:ue,registerSearchService(e){if(Jr.has(e.name))throw new Error(`SearchService: #${e.name} has registered!`);Jr.set(e.name,new ea(this,e))},registerCommandPalette(e,t){const{key:n,label:r,keybinding:s}=e;return ue.call(this,"$palette$",{key:n,label:r,palette:!0,keybinding:s},t)},registerCommandShortcut(e,t,n={}){typeof e=="string"&&(e={mode:"global",binding:e});const{binding:r}=e,s="$shortcut$",a=n.key||s+ne(r?.toString());return ue.call(this,s,{...n,key:a,palette:!1,keybinding:e},t)},registerUIItem(e,t){var n;const r=this.baseInfo.id;(n=this.caller)===null||n===void 0||n.call("api:call",{method:"register-plugin-ui-item",args:[r,e,t]})},registerPageMenuItem(e,t){if(typeof t!="function")return!1;const n=e+"_"+this.baseInfo.id,r=e;ue.call(this,"page-menu-item",{key:n,label:r},t)},onBlockRendererSlotted(e,t){if(!Wr(e))return;const n=this.baseInfo.id,r=`hook:editor:${ne(`slot:${e}`)}`;return this.caller.on(r,t),this.App._installPluginHook(n,r),()=>{this.caller.off(r,t),this.App._uninstallPluginHook(n,r)}},invokeExternalPlugin(e,...t){var n;if(!(e=(n=e)===null||n===void 0?void 0:n.trim()))return;let[r,s]=e.split(".");if(!["models","commands"].includes(s?.toLowerCase()))throw new Error("Type only support '.models' or '.commands' currently.");const a=e.replace(`${r}.${s}.`,"");if(!r||!s||!a)throw new Error(`Illegal type of #${e} to invoke external plugin.`);return this._execCallableAPIAsync("invoke_external_plugin_cmd",r,s.toLowerCase(),a,t)},setFullScreen(e){const t=(...n)=>this._callWin("setFullScreen",...n);e==="toggle"?this._callWin("isFullScreen").then((n=>{n?t():t(!0)})):e?t(!0):t()}};let Yr=0;const sa={newBlockUUID(){return this._execCallableAPIAsync("new_block_uuid")},isPageBlock:e=>e.uuid&&e.hasOwnProperty("name"),registerSlashCommand(e,t){var n;ra("Register slash command #",this.baseInfo.id,e,t),typeof t=="function"&&(t=[["editor/clear-current-slash",!1],["editor/restore-saved-cursor"],["editor/hook",t]]),t=t.map((r=>{const[s,...a]=r;if(s==="editor/hook"){let l=a[0],m=()=>{var L;(L=this.caller)===null||L===void 0||L.callUserModel(l)};typeof l=="function"&&(m=l);const O=`SlashCommandHook${s}${++Yr}`;r[1]=O,this.Editor["on"+O](m)}return r})),(n=this.caller)===null||n===void 0||n.call("api:call",{method:"register-plugin-slash-command",args:[this.baseInfo.id,[e,t]]})},registerBlockContextMenuItem(e,t){if(typeof t!="function")return!1;const n=e+"_"+this.baseInfo.id;ue.call(this,"block-context-menu-item",{key:n,label:e},t)},registerHighlightContextMenuItem(e,t,n){if(typeof t!="function")return!1;const r=e+"_"+this.baseInfo.id;ue.call(this,"highlight-context-menu-item",{key:r,label:e,extras:n},t)},scrollToBlockInPage(e,t,n){const r="block-content-"+t;n!=null&&n.replaceState?this.App.replaceState("page",{name:e},{anchor:r}):this.App.pushState("page",{name:e},{anchor:r})}},ia={onBlockChanged(e,t){if(!Wr(e))return;const n=this.baseInfo.id,r=`hook:db:${ne(`block:${e}`)}`,s=({block:a,txData:l,txMeta:m})=>{a.uuid===e&&t(a,l,m)};return this.caller.on(r,s),this.App._installPluginHook(n,r),()=>{this.caller.off(r,s),this.App._uninstallPluginHook(n,r)}},datascriptQuery(e,...t){return t.pop(),t!=null&&t.some((n=>typeof n=="function"))?this.Experiments.ensureHostScope().logseq.api.datascript_query(e,...t):this._execCallableAPIAsync("datascript_query",e,...t)}},aa={},la={},ca={},ua={makeSandboxStorage(){return new er(this,{assets:!0})}};var We=new WeakMap,Je=new WeakMap,Ye=new WeakMap,Ke=new WeakMap,Ze=new WeakMap;class An extends De(){constructor(t,n){super(),Ot(this,"_baseInfo",void 0),Ot(this,"_caller",void 0),Ot(this,"_version","0.2.11"),Ot(this,"_debugTag",""),Ot(this,"_settingsSchema",void 0),Ot(this,"_connected",!1),Ot(this,"_ui",new Map),Ot(this,"_mFileStorage",void 0),Ot(this,"_mRequest",void 0),Ot(this,"_mExperiments",void 0),Ot(this,"_beforeunloadCallback",void 0),Se(this,We,{writable:!0,value:void 0}),Se(this,Je,{writable:!0,value:void 0}),Se(this,Ye,{writable:!0,value:void 0}),Se(this,Ke,{writable:!0,value:void 0}),Se(this,Ze,{writable:!0,value:void 0}),this._baseInfo=t,this._caller=n,n.on("sys:ui:visible",(r=>{r!=null&&r.toggle&&this.toggleMainUI()})),n.on("settings:changed",(r=>{const s=Object.assign({},this.settings),a=Object.assign(this._baseInfo.settings,r);this.emit("settings:changed",{...a},s)})),n.on("beforeunload",(async r=>{const{actor:s,...a}=r,l=this._beforeunloadCallback;try{l&&await l(a),s?.resolve(null)}catch(m){this.logger.error("[beforeunload] ",m),s?.reject(m)}}))}async ready(t,n){var r,s;if(!this._connected)try{var a;typeof t=="function"&&(n=t,t={});let l=await this._caller.connectToParent(t);this._connected=!0,r=this._baseInfo,s=l,l=$o()(r,s,{arrayMerge:(m,O)=>O}),this._baseInfo=l,(a=l)!==null&&a!==void 0&&a.id&&(this._debugTag=this._caller.debugTag=`#${l.id} [${l.name}]`,this.logger.setTag(this._debugTag)),this._settingsSchema&&(l.settings=(function(m,O){const L=(O||[]).reduce(((U,D)=>("default"in D&&(U[D.key]=D.default),U)),{});return Object.assign(L,m)})(l.settings,this._settingsSchema),await this.useSettingsSchema(this._settingsSchema));try{await this._execCallableAPIAsync("setSDKMetadata",{version:this._version,runtime:"js"})}catch(m){console.warn(m)}n&&n.call(this,l)}catch(l){console.error(`${this._debugTag} [Ready Error]`,l)}}ensureConnected(){if(!this._connected)throw new Error("not connected")}beforeunload(t){typeof t=="function"&&(this._beforeunloadCallback=t)}provideModel(t){return this.caller._extendUserModel(t),this}provideTheme(t){return this.caller.call("provider:theme",t),this}provideStyle(t){return this.caller.call("provider:style",t),this}provideUI(t){return this.caller.call("provider:ui",t),this}useSettingsSchema(t){return this.connected&&this.caller.call("settings:schema",{schema:t,isSync:!0}),this._settingsSchema=t,this}updateSettings(t){this.caller.call("settings:update",t)}onSettingsChanged(t){const n="settings:changed";return this.on(n,t),()=>this.off(n,t)}showSettingsUI(){this.caller.call("settings:visible:changed",{visible:!0})}hideSettingsUI(){this.caller.call("settings:visible:changed",{visible:!1})}setMainUIAttrs(t){this.caller.call("main-ui:attrs",t)}setMainUIInlineStyle(t){this.caller.call("main-ui:style",t)}hideMainUI(t){const n={key:0,visible:!1,cursor:t?.restoreEditingCursor};this.caller.call("main-ui:visible",n),this.emit("ui:visible:changed",n),this._ui.set(n.key,n)}showMainUI(t){const n={key:0,visible:!0,autoFocus:t?.autoFocus};this.caller.call("main-ui:visible",n),this.emit("ui:visible:changed",n),this._ui.set(n.key,n)}toggleMainUI(){const n=this._ui.get(0);n&&n.visible?this.hideMainUI():this.showMainUI()}get version(){return this._version}get isMainUIVisible(){const t=this._ui.get(0);return!!(t&&t.visible)}get connected(){return this._connected}get baseInfo(){return this._baseInfo}get effect(){return(t=this)&&(((n=t.baseInfo)===null||n===void 0?void 0:n.effect)||!((r=t.baseInfo)!==null&&r!==void 0&&r.iir));var t,n,r}get logger(){return Gr}get settings(){var t;return(t=this.baseInfo)===null||t===void 0?void 0:t.settings}get caller(){return this._caller}resolveResourceFullUrl(t){if(this.ensureConnected(),t)return t=t.replace(/^[.\\/]+/,""),Ho(this._baseInfo.lsr,t)}_makeUserProxy(t,n){const r=this,s=this.caller;return new Proxy(t,{get(a,l,m){const O=a[l];return function(...L){if(O){L?.length!==0&&L.concat(n);const D=O.apply(r,L);if(D!==na)return D}if(n){const D=l.toString().match(/^(once|off|on)/i);if(D!=null){const H=D[0].toLowerCase(),Z=D.input,W=H==="off",V=r.baseInfo.id;let et=Z.slice(H.length),at=L[0],xt=L[1];typeof at=="string"&&typeof xt=="function"&&(at=at.replace(/^logseq./,":"),et=`${et}${at}`,at=xt,xt=L[2]),et=`hook:${n}:${ne(et)}`,s[H](et,at);const Nt=()=>{s.off(et,at),s.listenerCount(et)||r.App._uninstallPluginHook(V,et)};return W?void Nt():(r.App._installPluginHook(V,et,xt),Nt)}}let U=l;return["git","ui","assets","utils"].includes(n)&&(U=n+"_"+U),s.callAsync("api:call",{tag:n,method:U,args:L})}}})}_execCallableAPIAsync(t,...n){return this._caller.callAsync("api:call",{method:t,args:n})}_execCallableAPI(t,...n){this._caller.call("api:call",{method:t,args:n})}_callWin(...t){return this._execCallableAPIAsync("_callMainWin",...t)}get App(){return Pt(this,We)?Pt(this,We):ke(this,We,this._makeUserProxy(oa,"app"))}get Editor(){return Pt(this,Je)?Pt(this,Je):ke(this,Je,this._makeUserProxy(sa,"editor"))}get DB(){return Pt(this,Ye)?Pt(this,Ye):ke(this,Ye,this._makeUserProxy(ia,"db"))}get UI(){return Pt(this,Ke)?Pt(this,Ke):ke(this,Ke,this._makeUserProxy(la,"ui"))}get Utils(){return Pt(this,Ze)?Pt(this,Ze):ke(this,Ze,this._makeUserProxy(ca,"utils"))}get Git(){return this._makeUserProxy(aa,"git")}get Assets(){return this._makeUserProxy(ua,"assets")}get FileStorage(){let t=this._mFileStorage;return t||(t=this._mFileStorage=new er(this)),t}get Request(){let t=this._mRequest;return t||(t=this._mRequest=new fn(this)),t}get Experiments(){let t=this._mExperiments;return t||(t=this._mExperiments=new os(this)),t}}function Kr(e,t){return new An(e,t)}if(window.__LSP__HOST__==null){const e=new rs(null);window.logseq=Kr({},e)}})(),z})()))})(Ie,Ie.exports)),Ie.exports}Ca();const Oa={TODO:"TODO",LATER:"LATER",NOW:"NOW",DOING:"DOING",DONE:"DONE",CANCELLED:"CANCELLED",CANCELED:"CANCELED"},xo=new Map;async function Aa(E,M){const $=logseq.settings?.ppdbUrl,G=logseq.settings?.ppdbApiKey;if(!$||!G)return console.warn("[TE] PPDB не настроен, пропускаем обновление статуса"),!1;const y=Oa[M];if(!y)return console.warn("[TE] Неизвестный статус:",M),!1;try{const z=`${$}/api/logseq/tasks/${E}/status?api_key=${encodeURIComponent(G)}&status=${encodeURIComponent(y)}`,v=await fetch(z,{method:"PUT"});if(v.ok)return console.log(`[TE] Статус задачи ppdb-${E} обновлён на ${y}`),!0;{const u=await v.text();return console.error(`[TE] Ошибка обновления статуса: ${v.status}`,u),!1}}catch(z){return console.error("[TE] Ошибка отправки статуса на PPDB:",z),!1}}async function Ia(E){console.log("[TE] handleBlockChange вызван, блок:",E.uuid,"marker:",E.marker);const M=E.properties||E.propertiesTextValues||{},$=M["source-id"]||M.sourceId||M.source_id;if(!$){console.log("[TE] Блок без source-id, пропускаем");return}if(console.log("[TE] source-id найден:",$),!$.startsWith("ppdb-")){console.log("[TE] Не PPDB задача, пропускаем");return}const G=$.match(/^ppdb-(\d+)$/);if(!G){console.log("[TE] Не удалось извлечь ID из:",$);return}const y=parseInt(G[1]),z=E.marker;console.log("[TE] Задача ppdb-"+y+", текущий статус:",z);const v=xo.get($);if(v===z){console.log("[TE] Статус не изменился (кэш), пропускаем");return}console.log("[TE] Статус изменился:",v,"->",z),xo.set($,z),logseq.settings?.ppdbSyncBack?(console.log("[TE] Отправляю статус на PPDB..."),await Aa(y,z)&&logseq.UI.showMsg(`Статус задачи #${y} обновлён`,"success")):console.log("[TE] ppdbSyncBack выключен")}const ja=[{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:"ppdbSyncBack",type:"boolean",default:!0,title:"PPDB - Синхронизировать статусы обратно",description:"Отправлять изменения статуса задач из Logseq в 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 - Источники (внутреннее хранилище)",description:"Данные хранятся автоматически. Не редактируйте вручную."},{key:"giteaManage",type:"enum",enumChoices:["Открыть"],enumPicker:"radio",default:"",title:"Gitea - Управление источниками",description:"Нажмите для открытия окна управления источниками Gitea"},{key:"syncOnStartup",type:"boolean",default:!0,title:"Синхронизация при запуске",description:"Автоматически синхронизировать при запуске Logseq"},{key:"syncInterval",type:"number",default:0,title:"Интервал синхронизации (минуты)",description:"0 = только вручную"}],Pa=` + .te-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 999; + } + .te-modal { + background: var(--ls-primary-background-color, #fff); + border-radius: 8px; + padding: 20px; + min-width: 500px; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + } + .te-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + } + .te-modal h2 { + margin: 0; + color: var(--ls-primary-text-color, #333); + font-size: 18px; + } + .te-modal h3 { + margin: 16px 0 8px 0; + color: var(--ls-primary-text-color, #333); + font-size: 14px; + } + .te-source-list { + margin-bottom: 16px; + } + .te-source-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + margin: 8px 0; + background: var(--ls-secondary-background-color, #f5f5f5); + border-radius: 6px; + border: 1px solid var(--ls-border-color, #ddd); + } + .te-source-item.disabled { + opacity: 0.5; + } + .te-source-info { + flex: 1; + } + .te-source-name { + font-weight: 600; + color: var(--ls-primary-text-color, #333); + } + .te-source-url { + font-size: 12px; + color: var(--ls-secondary-text-color, #666); + } + .te-source-actions { + display: flex; + gap: 8px; + } + .te-btn { + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + transition: background 0.2s; + } + .te-btn-primary { + background: var(--ls-link-text-color, #0066cc); + color: white; + } + .te-btn-primary:hover { + opacity: 0.9; + } + .te-btn-secondary { + background: var(--ls-secondary-background-color, #e0e0e0); + color: var(--ls-primary-text-color, #333); + } + .te-btn-secondary:hover { + background: var(--ls-tertiary-background-color, #d0d0d0); + } + .te-btn-danger { + background: #dc3545; + color: white; + } + .te-btn-danger:hover { + background: #c82333; + } + .te-btn-small { + padding: 4px 8px; + font-size: 12px; + } + .te-form-group { + margin-bottom: 12px; + } + .te-form-group label { + display: block; + margin-bottom: 4px; + font-weight: 500; + color: var(--ls-primary-text-color, #333); + font-size: 13px; + } + .te-form-group input { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--ls-border-color, #ccc); + border-radius: 4px; + font-size: 13px; + background: var(--ls-primary-background-color, #fff); + color: var(--ls-primary-text-color, #333); + box-sizing: border-box; + } + .te-form-group input:focus { + outline: none; + border-color: var(--ls-link-text-color, #0066cc); + } + .te-form-group .te-hint { + font-size: 11px; + color: var(--ls-secondary-text-color, #666); + margin-top: 4px; + } + .te-form-group .te-hint a { + color: var(--ls-link-text-color, #0066cc); + text-decoration: none; + } + .te-form-group .te-hint a:hover { + text-decoration: underline; + } + .te-checkbox-group { + display: flex; + align-items: center; + gap: 8px; + } + .te-checkbox-group input[type="checkbox"] { + width: auto; + } + .te-form-row { + display: flex; + gap: 12px; + } + .te-form-row .te-form-group { + flex: 1; + } + .te-modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--ls-border-color, #ddd); + } + .te-empty-state { + text-align: center; + padding: 20px; + color: var(--ls-secondary-text-color, #666); + } + .te-divider { + border-top: 1px solid var(--ls-border-color, #ddd); + margin: 16px 0; + } +`;function Ma(E){try{return`${new URL(E).origin}/user/settings/applications`}catch{return""}}let Ct=null;function ko(E,M=null){const $=M!==null&&M>=0?E[M]:null,G=M===-1,y=G||$!==null,z=y?Ma($?.url||""):"";let v="";E.length===0?v='
Нет настроенных источников. Нажмите "Добавить" чтобы создать первый.
':v=E.map((f,k)=>` +
+
+
${Ut(f.name)}
+
${Ut(f.url)} → ${Ut(f.owner)}/${Ut(f.repo)}
+
+
+ + + +
+
+ `).join("");let u="";if(y){const f=$||{name:"",url:"https://",owner:"",repo:"",token:"",enabled:!0};u=` +
+

${G?"Добавить источник":"Редактировать источник"}

+
+ + +
+
+ + + ${z?`
Токен можно получить здесь: ${z}
`:'
Введите URL для получения ссылки на создание токена
'} +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+ `}return` +
+
+
+

Управление источниками Gitea

+ +
+
+ ${v} +
+ ${y?"":''} + ${u} + +
+
+ `}function Ut(E){const M=document.createElement("div");return M.textContent=E,M.innerHTML}function je(){try{return JSON.parse(logseq.settings?.giteaSources||"[]")}catch{return[]}}function Wn(E){logseq.updateSettings({giteaSources:JSON.stringify(E)})}function Eo(){Ct=null;const E=je();logseq.provideUI({key:"gitea-modal",template:ko(E,null),style:{zIndex:999}}),logseq.showMainUI()}function ye(E=null){const M=je();logseq.provideUI({key:"gitea-modal",template:ko(M,E),style:{zIndex:999}})}function La(){Ct=null,logseq.hideMainUI()}function Da(){try{const E=top?.document;if(E&&E.getElementById("te-source-name"))return E}catch{}try{const E=parent?.document;if(E&&E.getElementById("te-source-name"))return E}catch{}return document}function Na(){const E=Da(),M=E.getElementById("te-source-name"),$=E.getElementById("te-source-url"),G=E.getElementById("te-source-owner"),y=E.getElementById("te-source-repo"),z=E.getElementById("te-source-token"),v=E.getElementById("te-source-enabled");if(!M||!$||!G||!y||!z){logseq.UI.showMsg("Ошибка: не найдены поля формы","error");return}const u=M.value.trim(),f=$.value.trim().replace(/\/$/,""),k=G.value.trim(),w=y.value.trim(),R=z.value.trim(),T=v?.checked??!0;if(!u){logseq.UI.showMsg("Введите название источника","warning");return}if(!f||!f.startsWith("http")){logseq.UI.showMsg("Введите корректный URL (начинается с http)","warning");return}if(!k){logseq.UI.showMsg("Введите владельца репозитория","warning");return}if(!w){logseq.UI.showMsg("Введите название репозитория","warning");return}if(!R){logseq.UI.showMsg("Введите API токен","warning");return}const p={name:u,url:f,owner:k,repo:w,token:R,enabled:T},c=je();Ct===-1?c.push(p):Ct!==null&&Ct>=0&&(c[Ct]=p),Wn(c),Ct=null,ye(null),logseq.UI.showMsg("Источник сохранён","success")}async function Ra(){logseq.useSettingsSchema(ja),logseq.provideStyle(Pa),logseq.Editor.registerSlashCommand("sync-tasks",async()=>{await an()}),logseq.Editor.registerSlashCommand("sync-ppdb",async()=>{await To()}),logseq.Editor.registerSlashCommand("sync-gitea",async()=>{await Co()}),logseq.Editor.registerSlashCommand("manage-gitea",()=>{Eo()}),logseq.App.registerUIItem("toolbar",{key:"todo-everywhere-sync",template:` + + + + + + `}),logseq.provideModel({async syncAll(){await an()},closeModal(){La()},addSource(){Ct=-1,ye(-1)},editSource(M){const $=parseInt(M.dataset.index);Ct=$,ye($)},toggleSource(M){const $=parseInt(M.dataset.index),G=je();G[$]&&(G[$].enabled=!G[$].enabled,Wn(G),ye(Ct))},deleteSource(M){const $=parseInt(M.dataset.index),G=je();G.splice($,1),Wn(G),Ct=null,ye(null)},cancelEdit(){Ct=null,ye(null)},saveSource(){Na()}}),logseq.onSettingsChanged((M,$)=>{M.giteaManage==="Открыть"&&$.giteaManage!=="Открыть"&&setTimeout(()=>{logseq.updateSettings({giteaManage:""}),Eo()},100)}),logseq.settings?.syncOnStartup&&setTimeout(()=>an(),3e3);const E=logseq.settings?.syncInterval;E>0&&setInterval(()=>an(),E*60*1e3),logseq.DB.onChanged(async({blocks:M,txData:$,txMeta:G})=>{if(console.log("[TE] DB.onChanged: блоков изменено:",M.length),!logseq.settings?.ppdbSyncBack){console.log("[TE] ppdbSyncBack выключен, пропускаем");return}if(!logseq.settings?.ppdbEnabled){console.log("[TE] ppdbEnabled выключен, пропускаем");return}for(const y of M)console.log("[TE] Обрабатываю блок:",y.uuid,"marker:",y.marker,"props:",JSON.stringify(y.properties||{})),y.marker&&await Ia(y)}),console.log("TODO Everywhere plugin loaded")}async function an(){logseq.UI.showMsg("Синхронизация задач...","info"),console.log("[TE] Начало синхронизации"),console.log("[TE] Настройки:",{ppdbEnabled:logseq.settings?.ppdbEnabled,ppdbUrl:logseq.settings?.ppdbUrl,ppdbApiKey:logseq.settings?.ppdbApiKey?"***":"(пусто)",giteaEnabled:logseq.settings?.giteaEnabled,giteaSources:logseq.settings?.giteaSources});try{const E=[];if(logseq.settings?.ppdbEnabled){console.log("[TE] PPDB включен, синхронизирую...");const M=await To();E.push(M)}else console.log("[TE] PPDB выключен");if(logseq.settings?.giteaEnabled){console.log("[TE] Gitea включен, синхронизирую...");const M=await Co();E.push(M)}else console.log("[TE] Gitea выключен");E.length===0?logseq.UI.showMsg("Нет включенных источников","warning"):logseq.UI.showMsg(E.join(` +`),"success")}catch(E){console.error("[TE] Sync error:",E),logseq.UI.showMsg(`Ошибка синхронизации: ${E}`,"error")}}async function To(){const E=logseq.settings?.ppdbUrl,M=logseq.settings?.ppdbApiKey,$=logseq.settings?.ppdbPageName||"PPDB - TODO",G=(logseq.settings?.ppdbCategories||"").split(",").map(w=>w.trim()).filter(Boolean);if(!E||!M)throw new Error("PPDB: не настроены URL или API ключ");let y=`${E}/api/logseq/tasks?api_key=${M}`;G.length===1&&(y+=`&category=${G[0]}`);const z=await fetch(y);if(!z.ok)throw new Error(`PPDB API error: ${z.status}`);let u=(await z.json()).tasks;G.length>1&&(u=u.filter(w=>G.includes(w.category)));const f=Ua(u),k=await Oo($,f);return`PPDB: ${u.length} (+${k.added} ~${k.updated} -${k.deleted})`}async function Co(){const E=logseq.settings?.giteaSources||"[]";let M;try{M=JSON.parse(E)}catch{throw new Error("Gitea: неверный формат JSON источников")}const $=M.filter(y=>y.enabled);if($.length===0)return"Gitea: нет активных источников";const G=[];for(const y of $)try{const z=await $a(y),v=`Gitea - ${y.name} - TODO`,u=Fa(z,y),f=await Oo(v,u);G.push(`${y.name}: ${z.length} (+${f.added} ~${f.updated})`)}catch(z){console.error(`Gitea ${y.name} error:`,z),G.push(`${y.name}: ошибка`)}return`Gitea: ${G.join(", ")}`}async function $a(E){const M=`${E.url}/api/v1/repos/${E.owner}/${E.repo}/issues?state=open&type=issues`,$=await fetch(M,{headers:{Authorization:`token ${E.token}`,"Content-Type":"application/json"}});if(!$.ok)throw new Error(`Gitea API error: ${$.status}`);return await $.json()}function Ua(E){const M=[],$={TODO:[],DOING:[],DONE:[]};for(const y of E){const z=y.logseq_status;$[z]&&$[z].push(y)}const G={A:0,B:1,C:2};for(const y of Object.keys($))$[y].sort((z,v)=>(G[z.logseq_priority]||1)-(G[v.logseq_priority]||1));for(const y of[...$.DOING,...$.TODO,...$.DONE]){const z=y.logseq_priority?`[#${y.logseq_priority}] `:"",v=y.task_type?`#${y.task_type} `:"",u=`#${y.category} `,f=`${y.logseq_status} ${z}${v}${u}${y.title}`,k={"source-id":`ppdb-${y.id}`,author:y.author_username,created:y.created_at.split("T")[0]};y.url&&(k.url=y.url),y.assigned_to_username&&(k.assignee=y.assigned_to_username);const w=[];if(y.description){const R=y.description.split(` +`).filter(T=>T.trim());w.push(...R)}y.admin_comment&&w.push(`**Комментарий:** ${y.admin_comment}`),M.push({sourceId:`ppdb-${y.id}`,content:f,properties:k,children:w.length>0?w:void 0})}return M}function Fa(E,M){const $=[],G=v=>{const u=v.labels.map(f=>f.name.toLowerCase());return u.some(f=>f.includes("critical")||f.includes("urgent")||f.includes("p0"))||u.some(f=>f.includes("high")||f.includes("important")||f.includes("p1"))?"A":u.some(f=>f.includes("low")||f.includes("minor")||f.includes("p3"))?"C":"B"},y=v=>{const u=v.labels.map(f=>f.name.toLowerCase());return u.some(f=>f.includes("bug")||f.includes("fix"))?"bug":u.some(f=>f.includes("feature")||f.includes("enhancement"))?"feature":""},z={A:0,B:1,C:2};E.sort((v,u)=>(z[G(v)]||1)-(z[G(u)]||1));for(const v of E){const u=G(v),f=y(v),k=f?`#${f} `:"",w=v.labels.map(c=>`#${c.name.replace(/\s+/g,"-")}`).join(" ");let R=`TODO [#${u}] ${k}#${v.number} ${v.title}`;w&&(R+=` ${w}`);const T={"source-id":`gitea-${M.name}-${v.number}`,url:v.html_url,created:v.created_at.split("T")[0]};v.assignee&&(T.assignee=v.assignee.login),v.milestone&&(T.milestone=v.milestone.title);const p=[];if(v.body){const c=v.body.split(` +`).filter(_=>_.trim()).slice(0,5);p.push(...c.map(_=>_.substring(0,200))),v.body.split(` +`).length>5&&p.push("...")}$.push({sourceId:`gitea-${M.name}-${v.number}`,content:R,properties:T,children:p.length>0?p:void 0})}return $}function za(E){return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(E)}async function Oo(E,M){console.log("[TE] writeToPageIncremental:",E,"новых блоков:",M.length);let $=await logseq.Editor.getPage(E);if($||(console.log("[TE] Создаю страницу:",E),$=await logseq.Editor.createPage(E,{},{redirect:!1})),!$)throw new Error(`Не удалось создать страницу ${E}`);const G=await logseq.Editor.getPageBlocksTree(E)||[],y=new Map;let z=0;for(const T of G){if(!za(T.uuid)){z++;continue}try{const p=T.properties||{},c=p["source-id"]||p.sourceId||p.source_id;c&&(y.set(c,{uuid:T.uuid,content:T.content||"",properties:p}),console.log("[TE] Найден блок с source-id:",c,"uuid:",T.uuid))}catch(p){console.warn("[TE] Ошибка обработки блока, пропускаем:",T.uuid,p)}}z>0&&console.log("[TE] Пропущено блоков с невалидным UUID (кэш):",z),console.log("[TE] Существующих блоков с source-id:",y.size);const v=new Set(M.map(T=>T.sourceId));let u=0,f=0,k=0,w=0;for(const[T,p]of y)if(!v.has(T))try{await logseq.Editor.removeBlock(p.uuid),k++,console.log("[TE] Удалён блок:",T)}catch(c){console.warn("[TE] Не удалось удалить блок:",p.uuid,c)}let R=null;for(const T of M){const p=y.get(T.sourceId);if(p){const c=So(p.content)!==So(T.content),_=!qa(p.properties,T.properties);if(c||_){c&&await logseq.Editor.updateBlock(p.uuid,T.content);for(const[h,S]of Object.entries(T.properties))p.properties[h]!==S&&await logseq.Editor.upsertBlockProperty(p.uuid,h,S);f++,console.log("[TE] Обновлён блок:",T.sourceId)}else w++;R=p.uuid}else{let c;if(R?c=await logseq.Editor.insertBlock(R,T.content,{sibling:!0}):c=await logseq.Editor.insertBlock($.uuid,T.content,{sibling:!1}),c){for(const[_,h]of Object.entries(T.properties))await logseq.Editor.upsertBlockProperty(c.uuid,_,h);if(T.children&&T.children.length>0)for(const _ of T.children)await logseq.Editor.insertBlock(c.uuid,_,{sibling:!1});R=c.uuid,u++,console.log("[TE] Добавлен блок:",T.sourceId)}}}return console.log(`[TE] writeToPageIncremental завершён: +${u} ~${f} -${k} =${w}`),{added:u,updated:f,deleted:k,unchanged:w}}function So(E){return E.replace(/^\w+-?\w*::[^\n]*\n?/gm,"").trim()}function qa(E,M){for(const $ of Object.keys(M))if(String(E[$]||"")!==String(M[$]))return!1;return!0}logseq.ready(Ra).catch(console.error); diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..efbe12f --- /dev/null +++ b/dist/index.html @@ -0,0 +1,11 @@ + + + + + TODO Everywhere + + + +
+ + diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index e4de27e..0000000 --- a/dist/index.js +++ /dev/null @@ -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){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:` - - - - - - `}),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 + + + + TODO Everywhere + + +
+ + + diff --git a/package.json b/package.json index e826f9c..8e545f8 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/index.ts b/src/index.ts index 3610a5c..69eb9c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,37 +1,14 @@ import '@logseq/libs' import { SettingSchemaDesc } from '@logseq/libs/dist/LSPlugin' -// Шаблоны для config.edn -const PPDB_QUERY = `{: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}` - -const GITEA_QUERY = `{: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}` - // Интерфейсы +interface TaskBlock { + sourceId: string + content: string + properties: Record + children?: string[] +} + interface PPDBTask { id: number title: string @@ -77,6 +54,115 @@ interface GiteaSource { enabled: boolean } +// Маппинг статусов Logseq → PPDB +// API принимает: TODO, LATER, NOW, DOING, IN-PROGRESS, DONE, COMPLETED, CANCELLED, CANCELED +const LOGSEQ_TO_PPDB_STATUS: Record = { + 'TODO': 'TODO', + 'LATER': 'LATER', + 'NOW': 'NOW', + 'DOING': 'DOING', + 'DONE': 'DONE', + 'CANCELLED': 'CANCELLED', + 'CANCELED': 'CANCELED' +} + +// Кэш последних статусов блоков для предотвращения дублирования запросов +const blockStatusCache = new Map() + +// Отправка статуса на PPDB +async function sendStatusToPPDB(taskId: number, newStatus: string): Promise { + const url = logseq.settings?.ppdbUrl as string + const apiKey = logseq.settings?.ppdbApiKey as string + + if (!url || !apiKey) { + console.warn('[TE] PPDB не настроен, пропускаем обновление статуса') + return false + } + + const ppdbStatus = LOGSEQ_TO_PPDB_STATUS[newStatus] + if (!ppdbStatus) { + console.warn('[TE] Неизвестный статус:', newStatus) + return false + } + + try { + // Формат: PUT /api/logseq/tasks/{task_id}/status?api_key=XXX&status=YYY + const apiUrl = `${url}/api/logseq/tasks/${taskId}/status?api_key=${encodeURIComponent(apiKey)}&status=${encodeURIComponent(ppdbStatus)}` + + const response = await fetch(apiUrl, { + method: 'PUT' + }) + + if (response.ok) { + console.log(`[TE] Статус задачи ppdb-${taskId} обновлён на ${ppdbStatus}`) + return true + } else { + const errorText = await response.text() + console.error(`[TE] Ошибка обновления статуса: ${response.status}`, errorText) + return false + } + } catch (error) { + console.error('[TE] Ошибка отправки статуса на PPDB:', error) + return false + } +} + +// Обработка изменения блока +async function handleBlockChange(block: any) { + console.log('[TE] handleBlockChange вызван, блок:', block.uuid, 'marker:', block.marker) + + // Проверяем что это блок с PPDB задачей + // Свойства могут быть в разных форматах + const props = block.properties || block.propertiesTextValues || {} + const sourceId = props['source-id'] || props['sourceId'] || props['source_id'] + + if (!sourceId) { + console.log('[TE] Блок без source-id, пропускаем') + return + } + + console.log('[TE] source-id найден:', sourceId) + + if (!sourceId.startsWith('ppdb-')) { + console.log('[TE] Не PPDB задача, пропускаем') + return + } + + // Получаем ID задачи + const taskIdMatch = sourceId.match(/^ppdb-(\d+)$/) + if (!taskIdMatch) { + console.log('[TE] Не удалось извлечь ID из:', sourceId) + return + } + + const taskId = parseInt(taskIdMatch[1]) + const currentMarker = block.marker + + console.log('[TE] Задача ppdb-' + taskId + ', текущий статус:', currentMarker) + + // Проверяем изменился ли статус + const cachedStatus = blockStatusCache.get(sourceId) + if (cachedStatus === currentMarker) { + console.log('[TE] Статус не изменился (кэш), пропускаем') + return + } + + console.log('[TE] Статус изменился:', cachedStatus, '->', currentMarker) + + // Обновляем кэш и отправляем на PPDB + blockStatusCache.set(sourceId, currentMarker) + + if (logseq.settings?.ppdbSyncBack) { + console.log('[TE] Отправляю статус на PPDB...') + const success = await sendStatusToPPDB(taskId, currentMarker) + if (success) { + logseq.UI.showMsg(`Статус задачи #${taskId} обновлён`, 'success') + } + } else { + console.log('[TE] ppdbSyncBack выключен') + } +} + // Настройки плагина const settingsSchema: SettingSchemaDesc[] = [ { @@ -107,6 +193,13 @@ const settingsSchema: SettingSchemaDesc[] = [ title: 'PPDB - Имя страницы', description: 'Название страницы для задач PPDB' }, + { + key: 'ppdbSyncBack', + type: 'boolean', + default: true, + title: 'PPDB - Синхронизировать статусы обратно', + description: 'Отправлять изменения статуса задач из Logseq в PPDB' + }, { key: 'ppdbCategories', type: 'string', @@ -125,8 +218,17 @@ const settingsSchema: SettingSchemaDesc[] = [ 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}]' + title: 'Gitea - Источники (внутреннее хранилище)', + description: 'Данные хранятся автоматически. Не редактируйте вручную.' + }, + { + key: 'giteaManage', + type: 'enum', + enumChoices: ['Открыть'], + enumPicker: 'radio', + default: '', + title: 'Gitea - Управление источниками', + description: 'Нажмите для открытия окна управления источниками Gitea' }, { key: 'syncOnStartup', @@ -141,144 +243,430 @@ const settingsSchema: SettingSchemaDesc[] = [ default: 0, title: 'Интервал синхронизации (минуты)', description: '0 = только вручную' - }, - { - key: 'configVersion', - type: 'number', - default: 0, - title: 'Версия конфигурации (не изменять)', - description: 'Внутренняя переменная для отслеживания настройки config.edn' } ] -const CURRENT_CONFIG_VERSION = 1 +// CSS стили для модального окна +const modalStyles = ` + .te-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 999; + } + .te-modal { + background: var(--ls-primary-background-color, #fff); + border-radius: 8px; + padding: 20px; + min-width: 500px; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + } + .te-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + } + .te-modal h2 { + margin: 0; + color: var(--ls-primary-text-color, #333); + font-size: 18px; + } + .te-modal h3 { + margin: 16px 0 8px 0; + color: var(--ls-primary-text-color, #333); + font-size: 14px; + } + .te-source-list { + margin-bottom: 16px; + } + .te-source-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + margin: 8px 0; + background: var(--ls-secondary-background-color, #f5f5f5); + border-radius: 6px; + border: 1px solid var(--ls-border-color, #ddd); + } + .te-source-item.disabled { + opacity: 0.5; + } + .te-source-info { + flex: 1; + } + .te-source-name { + font-weight: 600; + color: var(--ls-primary-text-color, #333); + } + .te-source-url { + font-size: 12px; + color: var(--ls-secondary-text-color, #666); + } + .te-source-actions { + display: flex; + gap: 8px; + } + .te-btn { + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + transition: background 0.2s; + } + .te-btn-primary { + background: var(--ls-link-text-color, #0066cc); + color: white; + } + .te-btn-primary:hover { + opacity: 0.9; + } + .te-btn-secondary { + background: var(--ls-secondary-background-color, #e0e0e0); + color: var(--ls-primary-text-color, #333); + } + .te-btn-secondary:hover { + background: var(--ls-tertiary-background-color, #d0d0d0); + } + .te-btn-danger { + background: #dc3545; + color: white; + } + .te-btn-danger:hover { + background: #c82333; + } + .te-btn-small { + padding: 4px 8px; + font-size: 12px; + } + .te-form-group { + margin-bottom: 12px; + } + .te-form-group label { + display: block; + margin-bottom: 4px; + font-weight: 500; + color: var(--ls-primary-text-color, #333); + font-size: 13px; + } + .te-form-group input { + width: 100%; + padding: 8px 10px; + border: 1px solid var(--ls-border-color, #ccc); + border-radius: 4px; + font-size: 13px; + background: var(--ls-primary-background-color, #fff); + color: var(--ls-primary-text-color, #333); + box-sizing: border-box; + } + .te-form-group input:focus { + outline: none; + border-color: var(--ls-link-text-color, #0066cc); + } + .te-form-group .te-hint { + font-size: 11px; + color: var(--ls-secondary-text-color, #666); + margin-top: 4px; + } + .te-form-group .te-hint a { + color: var(--ls-link-text-color, #0066cc); + text-decoration: none; + } + .te-form-group .te-hint a:hover { + text-decoration: underline; + } + .te-checkbox-group { + display: flex; + align-items: center; + gap: 8px; + } + .te-checkbox-group input[type="checkbox"] { + width: auto; + } + .te-form-row { + display: flex; + gap: 12px; + } + .te-form-row .te-form-group { + flex: 1; + } + .te-modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--ls-border-color, #ddd); + } + .te-empty-state { + text-align: center; + padding: 20px; + color: var(--ls-secondary-text-color, #666); + } + .te-divider { + border-top: 1px solid var(--ls-border-color, #ddd); + margin: 16px 0; + } +` -// Настройка config.edn -async function setupConfig(): Promise { +// Функция получения URL для токена +function getTokenUrl(giteaUrl: string): string { try { - const graph = await logseq.App.getCurrentGraph() - if (!graph?.path) { - console.error('Cannot get graph path') - return false - } - - const configPath = `${graph.path}/logseq/config.edn` - - // Читаем текущий конфиг - const configContent = await (window as any).logseq.FileStorage.getItem(configPath) - if (!configContent) { - console.error('Cannot read config.edn') - return false - } - - let modified = false - let newContent = configContent - - // Проверяем и добавляем PPDB query - if (!configContent.includes(':title "PPDB"')) { - const insertPos = findQueryInsertPosition(newContent) - if (insertPos > 0) { - newContent = newContent.slice(0, insertPos) + '\n ' + PPDB_QUERY + newContent.slice(insertPos) - modified = true - } - } - - // Проверяем и добавляем Gitea query - if (!newContent.includes(':title "Gitea"')) { - const insertPos = findQueryInsertPosition(newContent) - if (insertPos > 0) { - newContent = newContent.slice(0, insertPos) + '\n ' + GITEA_QUERY + newContent.slice(insertPos) - modified = true - } - } - - // Обновляем исключения в существующих queries - // Добавляем PPDB - TODO в исключения - if (!newContent.includes('"PPDB - TODO"')) { - // Ищем паттерн (not [(contains? #{"...", "..."} ?name)]) и добавляем PPDB - TODO - newContent = newContent.replace( - /\(not \[\(contains\? #\{([^}]*)"РЕД СОФТ - TODO"([^}]*)\} \?name\)\]\)/g, - '(not [(contains? #{$1"РЕД СОФТ - TODO" "PPDB - TODO"$2} ?name)])' - ) - modified = true - } - - // Добавляем исключение для Gitea страниц - if (!newContent.includes('clojure.string/starts-with? ?name "Gitea -"')) { - // После (not [(contains? ... добавляем проверку на Gitea - newContent = newContent.replace( - /(\(not \[\(contains\? #\{[^}]*"PPDB - TODO"[^}]*\} \?name\)\]\))(\s*)(\[?\?h :block\/marker)/g, - '$1\n (not [(clojure.string/starts-with? ?name "Gitea -")])$2$3' - ) - modified = true - } - - // Добавляем PPDB - TODO в favorites - if (!newContent.includes('"PPDB - TODO"')) { - newContent = newContent.replace( - /:favorites \[([^\]]*)"РЕД СОФТ - TODO"([^\]]*)\]/, - ':favorites [$1"РЕД СОФТ - TODO" "PPDB - TODO"$2]' - ) - modified = true - } - - if (modified) { - await (window as any).logseq.FileStorage.setItem(configPath, newContent) - console.log('config.edn updated successfully') - return true - } - - return false - } catch (error) { - console.error('Error setting up config:', error) - return false + const url = new URL(giteaUrl) + return `${url.origin}/user/settings/applications` + } catch { + return '' } } -// Находит позицию для вставки нового query (перед последним query в :journals) -function findQueryInsertPosition(content: string): number { - // Ищем паттерн {:title "Остальные дела" или последний query перед закрывающим ]} - const patterns = [ - /:title "Остальные дела"/, - /:title "Остальные"/, - ] +// Глобальное состояние для редактирования +let editingSourceIndex: number | null = null - for (const pattern of patterns) { - const match = content.match(pattern) - if (match && match.index) { - // Возвращаем позицию перед найденным query - // Ищем начало блока { перед :title - let pos = match.index - while (pos > 0 && content[pos] !== '{') { - pos-- - } - return pos +// Рендер модального окна +function renderGiteaModal(sources: GiteaSource[], editIndex: number | null = null): string { + const editingSource = editIndex !== null && editIndex >= 0 ? sources[editIndex] : null + const isAdding = editIndex === -1 + const showForm = isAdding || editingSource !== null + + const tokenHintUrl = showForm ? getTokenUrl(editingSource?.url || '') : '' + + let sourcesListHtml = '' + if (sources.length === 0) { + sourcesListHtml = '
Нет настроенных источников. Нажмите "Добавить" чтобы создать первый.
' + } else { + sourcesListHtml = sources.map((s, i) => ` +
+
+
${escapeHtml(s.name)}
+
${escapeHtml(s.url)} → ${escapeHtml(s.owner)}/${escapeHtml(s.repo)}
+
+
+ + + +
+
+ `).join('') + } + + let formHtml = '' + if (showForm) { + const s = editingSource || { name: '', url: 'https://', owner: '', repo: '', token: '', enabled: true } + formHtml = ` +
+

${isAdding ? 'Добавить источник' : 'Редактировать источник'}

+
+ + +
+
+ + + ${tokenHintUrl ? `
Токен можно получить здесь: ${tokenHintUrl}
` : '
Введите URL для получения ссылки на создание токена
'} +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+
+ + +
+ ` + } + + return ` +
+
+
+

Управление источниками Gitea

+ +
+
+ ${sourcesListHtml} +
+ ${!showForm ? '' : ''} + ${formHtml} + +
+
+ ` +} + +// Экранирование HTML +function escapeHtml(text: string): string { + const div = document.createElement('div') + div.textContent = text + return div.innerHTML +} + +// Получить источники из настроек +function getGiteaSources(): GiteaSource[] { + try { + return JSON.parse(logseq.settings?.giteaSources as string || '[]') + } catch { + return [] + } +} + +// Сохранить источники в настройки +function saveGiteaSources(sources: GiteaSource[]) { + logseq.updateSettings({ giteaSources: JSON.stringify(sources) }) +} + +// Показать модальное окно +function showGiteaModal() { + editingSourceIndex = null + const sources = getGiteaSources() + logseq.provideUI({ + key: 'gitea-modal', + template: renderGiteaModal(sources, null), + style: { zIndex: 999 } + }) + logseq.showMainUI() +} + +// Обновить модальное окно +function updateGiteaModal(editIndex: number | null = null) { + const sources = getGiteaSources() + logseq.provideUI({ + key: 'gitea-modal', + template: renderGiteaModal(sources, editIndex), + style: { zIndex: 999 } + }) +} + +// Закрыть модальное окно +function hideGiteaModal() { + editingSourceIndex = null + logseq.hideMainUI() +} + +// Получить document плагина (может быть в iframe) +function getPluginDocument(): Document { + // В Logseq плагины работают в iframe, элементы UI в top.document + try { + const topDoc = top?.document + if (topDoc && topDoc.getElementById('te-source-name')) { + return topDoc } + } catch (e) { + // Игнорируем ошибки доступа } - // Если не нашли, ищем конец journals array - const journalsEnd = content.search(/\]\s*\}\s*;;\s*Add custom commands/) - if (journalsEnd > 0) { - return journalsEnd + // Пробуем parent + try { + const parentDoc = parent?.document + if (parentDoc && parentDoc.getElementById('te-source-name')) { + return parentDoc + } + } catch (e) { + // Игнорируем ошибки доступа } - return -1 + // Fallback на текущий document + return document +} + +// Сохранить источник из формы +function saveSourceFromForm() { + const doc = getPluginDocument() + const nameEl = doc.getElementById('te-source-name') as HTMLInputElement + const urlEl = doc.getElementById('te-source-url') as HTMLInputElement + const ownerEl = doc.getElementById('te-source-owner') as HTMLInputElement + const repoEl = doc.getElementById('te-source-repo') as HTMLInputElement + const tokenEl = doc.getElementById('te-source-token') as HTMLInputElement + const enabledEl = doc.getElementById('te-source-enabled') as HTMLInputElement + + if (!nameEl || !urlEl || !ownerEl || !repoEl || !tokenEl) { + logseq.UI.showMsg('Ошибка: не найдены поля формы', 'error') + return + } + + const name = nameEl.value.trim() + const url = urlEl.value.trim().replace(/\/$/, '') // убираем trailing slash + const owner = ownerEl.value.trim() + const repo = repoEl.value.trim() + const token = tokenEl.value.trim() + const enabled = enabledEl?.checked ?? true + + // Валидация + if (!name) { + logseq.UI.showMsg('Введите название источника', 'warning') + return + } + if (!url || !url.startsWith('http')) { + logseq.UI.showMsg('Введите корректный URL (начинается с http)', 'warning') + return + } + if (!owner) { + logseq.UI.showMsg('Введите владельца репозитория', 'warning') + return + } + if (!repo) { + logseq.UI.showMsg('Введите название репозитория', 'warning') + return + } + if (!token) { + logseq.UI.showMsg('Введите API токен', 'warning') + return + } + + const newSource: GiteaSource = { name, url, owner, repo, token, enabled } + const sources = getGiteaSources() + + if (editingSourceIndex === -1) { + // Добавление нового + sources.push(newSource) + } else if (editingSourceIndex !== null && editingSourceIndex >= 0) { + // Редактирование существующего + sources[editingSourceIndex] = newSource + } + + saveGiteaSources(sources) + editingSourceIndex = null + updateGiteaModal(null) + logseq.UI.showMsg('Источник сохранён', 'success') } // Главная функция async function main() { logseq.useSettingsSchema(settingsSchema) - // Проверяем нужно ли настроить config.edn - const configVersion = logseq.settings?.configVersion as number || 0 - if (configVersion < CURRENT_CONFIG_VERSION) { - logseq.UI.showMsg('TODO Everywhere: настройка config.edn...', 'info') - const success = await setupConfig() - if (success) { - logseq.updateSettings({ configVersion: CURRENT_CONFIG_VERSION }) - logseq.UI.showMsg('config.edn обновлён. Перезапустите Logseq для применения изменений.', 'success') - } - } + // Добавляем стили для модального окна + logseq.provideStyle(modalStyles) + // Регистрируем команды logseq.Editor.registerSlashCommand('sync-tasks', async () => { @@ -293,11 +681,15 @@ async function main() { await syncGiteaTasks() }) - // Кнопка в тулбаре + logseq.Editor.registerSlashCommand('manage-gitea', () => { + showGiteaModal() + }) + + // Кнопка синхронизации в тулбаре logseq.App.registerUIItem('toolbar', { key: 'todo-everywhere-sync', template: ` - + @@ -305,9 +697,57 @@ async function main() { ` }) + // Модель для обработки кликов logseq.provideModel({ async syncAll() { await syncAllTasks() + }, + closeModal() { + hideGiteaModal() + }, + addSource() { + editingSourceIndex = -1 + updateGiteaModal(-1) + }, + editSource(e: any) { + const index = parseInt(e.dataset.index) + editingSourceIndex = index + updateGiteaModal(index) + }, + toggleSource(e: any) { + const index = parseInt(e.dataset.index) + const sources = getGiteaSources() + if (sources[index]) { + sources[index].enabled = !sources[index].enabled + saveGiteaSources(sources) + updateGiteaModal(editingSourceIndex) + } + }, + deleteSource(e: any) { + const index = parseInt(e.dataset.index) + const sources = getGiteaSources() + sources.splice(index, 1) + saveGiteaSources(sources) + editingSourceIndex = null + updateGiteaModal(null) + }, + cancelEdit() { + editingSourceIndex = null + updateGiteaModal(null) + }, + saveSource() { + saveSourceFromForm() + } + }) + + // Открытие модального окна при выборе настройки "Управление источниками" + logseq.onSettingsChanged((newSettings, oldSettings) => { + if (newSettings.giteaManage === 'Открыть' && oldSettings.giteaManage !== 'Открыть') { + // Сбрасываем значение и открываем окно + setTimeout(() => { + logseq.updateSettings({ giteaManage: '' }) + showGiteaModal() + }, 100) } }) @@ -322,24 +762,61 @@ async function main() { setInterval(() => syncAllTasks(), interval * 60 * 1000) } + // Отслеживание изменений блоков для обратной синхронизации + logseq.DB.onChanged(async ({ blocks, txData, txMeta }) => { + console.log('[TE] DB.onChanged: блоков изменено:', blocks.length) + + if (!logseq.settings?.ppdbSyncBack) { + console.log('[TE] ppdbSyncBack выключен, пропускаем') + return + } + + if (!logseq.settings?.ppdbEnabled) { + console.log('[TE] ppdbEnabled выключен, пропускаем') + return + } + + for (const block of blocks) { + console.log('[TE] Обрабатываю блок:', block.uuid, 'marker:', block.marker, 'props:', JSON.stringify(block.properties || {})) + // Проверяем что блок имеет маркер (TODO/DOING/DONE) + if (block.marker) { + await handleBlockChange(block) + } + } + }) + console.log('TODO Everywhere plugin loaded') } // Синхронизация всех источников async function syncAllTasks() { logseq.UI.showMsg('Синхронизация задач...', 'info') + console.log('[TE] Начало синхронизации') + console.log('[TE] Настройки:', { + ppdbEnabled: logseq.settings?.ppdbEnabled, + ppdbUrl: logseq.settings?.ppdbUrl, + ppdbApiKey: logseq.settings?.ppdbApiKey ? '***' : '(пусто)', + giteaEnabled: logseq.settings?.giteaEnabled, + giteaSources: logseq.settings?.giteaSources + }) try { const results: string[] = [] if (logseq.settings?.ppdbEnabled) { + console.log('[TE] PPDB включен, синхронизирую...') const ppdbResult = await syncPPDBTasks() results.push(ppdbResult) + } else { + console.log('[TE] PPDB выключен') } if (logseq.settings?.giteaEnabled) { + console.log('[TE] Gitea включен, синхронизирую...') const giteaResult = await syncGiteaTasks() results.push(giteaResult) + } else { + console.log('[TE] Gitea выключен') } if (results.length === 0) { @@ -348,7 +825,7 @@ async function syncAllTasks() { logseq.UI.showMsg(results.join('\n'), 'success') } } catch (error) { - console.error('Sync error:', error) + console.error('[TE] Sync error:', error) logseq.UI.showMsg(`Ошибка синхронизации: ${error}`, 'error') } } @@ -383,13 +860,13 @@ async function syncPPDBTasks(): Promise { tasks = tasks.filter(t => categories.includes(t.category)) } - // Генерируем контент страницы - const content = generatePPDBPageContent(tasks) + // Генерируем блоки + const blocks = generatePPDBBlocks(tasks) - // Записываем в страницу - await writeToPage(pageName, content) + // Записываем инкрементально + const stats = await writeToPageIncremental(pageName, blocks) - return `PPDB: ${tasks.length} задач` + return `PPDB: ${tasks.length} (+${stats.added} ~${stats.updated} -${stats.deleted})` } // Синхронизация Gitea @@ -414,9 +891,9 @@ async function syncGiteaTasks(): Promise { try { const issues = await fetchGiteaIssues(source) const pageName = `Gitea - ${source.name} - TODO` - const content = generateGiteaPageContent(issues, source) - await writeToPage(pageName, content) - results.push(`${source.name}: ${issues.length}`) + const blocks = generateGiteaBlocks(issues, source) + const stats = await writeToPageIncremental(pageName, blocks) + results.push(`${source.name}: ${issues.length} (+${stats.added} ~${stats.updated})`) } catch (error) { console.error(`Gitea ${source.name} error:`, error) results.push(`${source.name}: ошибка`) @@ -444,12 +921,9 @@ async function fetchGiteaIssues(source: GiteaSource): Promise { return await response.json() } -// Генерация контента для PPDB страницы -function generatePPDBPageContent(tasks: PPDBTask[]): string { - const lines: string[] = [] - lines.push(`source:: PPDB`) - lines.push(`synced:: ${new Date().toISOString()}`) - lines.push('') +// Генерация структурированных блоков для PPDB +function generatePPDBBlocks(tasks: PPDBTask[]): TaskBlock[] { + const blocks: TaskBlock[] = [] // Группируем по статусам const byStatus: Record = { @@ -473,52 +947,51 @@ function generatePPDBPageContent(tasks: PPDBTask[]): string { ) } - // Генерируем TODO блоки + // Генерируем блоки задач for (const task of [...byStatus['DOING'], ...byStatus['TODO'], ...byStatus['DONE']]) { const priorityStr = task.logseq_priority ? `[#${task.logseq_priority}] ` : '' const typeTag = task.task_type ? `#${task.task_type} ` : '' const categoryTag = `#${task.category} ` - let line = `- ${task.logseq_status} ${priorityStr}${typeTag}${categoryTag}${task.title}` + const content = `${task.logseq_status} ${priorityStr}${typeTag}${categoryTag}${task.title}` - // Добавляем свойства - lines.push(line) - lines.push(` id:: ppdb-${task.id}`) + const properties: Record = { + 'source-id': `ppdb-${task.id}`, + 'author': task.author_username, + 'created': task.created_at.split('T')[0] + } if (task.url) { - lines.push(` url:: ${task.url}`) - } - - if (task.description) { - // Описание как вложенный блок - const descLines = task.description.split('\n').filter(l => l.trim()) - for (const descLine of descLines) { - lines.push(` - ${descLine}`) - } - } - - if (task.admin_comment) { - lines.push(` - **Комментарий:** ${task.admin_comment}`) + properties['url'] = task.url } if (task.assigned_to_username) { - lines.push(` assignee:: ${task.assigned_to_username}`) + properties['assignee'] = task.assigned_to_username } - lines.push(` author:: ${task.author_username}`) - lines.push(` created:: ${task.created_at.split('T')[0]}`) + const children: string[] = [] + if (task.description) { + const descLines = task.description.split('\n').filter(l => l.trim()) + children.push(...descLines) + } + if (task.admin_comment) { + children.push(`**Комментарий:** ${task.admin_comment}`) + } + + blocks.push({ + sourceId: `ppdb-${task.id}`, + content, + properties, + children: children.length > 0 ? children : undefined + }) } - return lines.join('\n') + return blocks } -// Генерация контента для Gitea страницы -function generateGiteaPageContent(issues: GiteaIssue[], source: GiteaSource): string { - const lines: string[] = [] - lines.push(`source:: Gitea`) - lines.push(`repo:: ${source.owner}/${source.repo}`) - lines.push(`synced:: ${new Date().toISOString()}`) - lines.push('') +// Генерация структурированных блоков для Gitea +function generateGiteaBlocks(issues: GiteaIssue[], source: GiteaSource): TaskBlock[] { + const blocks: TaskBlock[] = [] // Определяем приоритет по меткам const getPriority = (issue: GiteaIssue): string => { @@ -549,46 +1022,60 @@ function generateGiteaPageContent(issues: GiteaIssue[], source: GiteaSource): st const typeTag = type ? `#${type} ` : '' const labelTags = issue.labels.map(l => `#${l.name.replace(/\s+/g, '-')}`).join(' ') - let line = `- TODO [#${priority}] ${typeTag}#${issue.number} ${issue.title}` + let content = `TODO [#${priority}] ${typeTag}#${issue.number} ${issue.title}` if (labelTags) { - line += ` ${labelTags}` + content += ` ${labelTags}` } - lines.push(line) - lines.push(` id:: gitea-${source.name}-${issue.number}`) - lines.push(` url:: ${issue.html_url}`) + const properties: Record = { + 'source-id': `gitea-${source.name}-${issue.number}`, + 'url': issue.html_url, + 'created': issue.created_at.split('T')[0] + } if (issue.assignee) { - lines.push(` assignee:: ${issue.assignee.login}`) + properties['assignee'] = issue.assignee.login } if (issue.milestone) { - lines.push(` milestone:: ${issue.milestone.title}`) + properties['milestone'] = issue.milestone.title } - lines.push(` created:: ${issue.created_at.split('T')[0]}`) - - // Тело issue как вложенный блок + const children: string[] = [] if (issue.body) { const bodyLines = issue.body.split('\n').filter(l => l.trim()).slice(0, 5) - for (const bodyLine of bodyLines) { - lines.push(` - ${bodyLine.substring(0, 200)}`) - } + children.push(...bodyLines.map(l => l.substring(0, 200))) if (issue.body.split('\n').length > 5) { - lines.push(` - ...`) + children.push('...') } } + + blocks.push({ + sourceId: `gitea-${source.name}-${issue.number}`, + content, + properties, + children: children.length > 0 ? children : undefined + }) } - return lines.join('\n') + return blocks } -// Запись контента в страницу -async function writeToPage(pageName: string, content: string) { +// Проверка валидности UUID +function isValidUUID(str: string): boolean { + // UUID формат: 8-4-4-4-12 hex символов + return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str) +} + +// Инкрементальная запись блоков в страницу +async function writeToPageIncremental(pageName: string, newBlocks: TaskBlock[]) { + console.log('[TE] writeToPageIncremental:', pageName, 'новых блоков:', newBlocks.length) + // Получаем или создаём страницу let page = await logseq.Editor.getPage(pageName) if (!page) { + console.log('[TE] Создаю страницу:', pageName) page = await logseq.Editor.createPage(pageName, {}, { redirect: false }) } @@ -596,54 +1083,145 @@ async function writeToPage(pageName: string, content: string) { throw new Error(`Не удалось создать страницу ${pageName}`) } - // Получаем блоки страницы - const blocks = await logseq.Editor.getPageBlocksTree(pageName) + // Получаем существующие блоки страницы + const existingBlocks = await logseq.Editor.getPageBlocksTree(pageName) || [] - // Удаляем все существующие блоки - for (const block of blocks) { - await logseq.Editor.removeBlock(block.uuid) + // Создаём map существующих блоков по source-id + const existingMap = new Map }>() + let skippedInvalidBlocks = 0 + + for (const block of existingBlocks) { + // Пропускаем блоки с невалидным UUID (старый кэш Logseq) + if (!isValidUUID(block.uuid)) { + skippedInvalidBlocks++ + continue + } + + try { + // Используем свойства из блока напрямую (getPageBlocksTree уже их возвращает) + // Logseq конвертирует source-id в sourceId (camelCase) + const props = block.properties || {} + const sourceId = props['source-id'] || props['sourceId'] || props['source_id'] + + if (sourceId) { + existingMap.set(sourceId, { + uuid: block.uuid, + content: block.content || '', + properties: props + }) + console.log('[TE] Найден блок с source-id:', sourceId, 'uuid:', block.uuid) + } + } catch (err) { + // Ошибка при обработке блока - пропускаем + console.warn('[TE] Ошибка обработки блока, пропускаем:', block.uuid, err) + } } - // Вставляем новый контент - const contentLines = content.split('\n') + if (skippedInvalidBlocks > 0) { + console.log('[TE] Пропущено блоков с невалидным UUID (кэш):', skippedInvalidBlocks) + } - // Первый блок - if (contentLines.length > 0) { - const firstBlock = await logseq.Editor.insertBlock(page.uuid, contentLines[0], { sibling: false }) + console.log('[TE] Существующих блоков с source-id:', existingMap.size) - if (firstBlock) { - // Остальные блоки добавляем как siblings - let prevBlockUuid = firstBlock.uuid + // Создаём set новых source-id + const newSourceIds = new Set(newBlocks.map(b => b.sourceId)) - for (let i = 1; i < contentLines.length; i++) { - const line = contentLines[i] + // Статистика + let added = 0, updated = 0, deleted = 0, unchanged = 0 - // Пропускаем пустые строки - if (!line.trim()) continue - - // Определяем уровень вложенности - const indent = line.search(/\S/) - - if (indent <= 0 || line.startsWith('-')) { - // Верхний уровень - const newBlock = await logseq.Editor.insertBlock(prevBlockUuid, line.replace(/^-\s*/, ''), { sibling: true }) - if (newBlock) { - prevBlockUuid = newBlock.uuid - } - } else { - // Вложенный блок (свойства или контент) - const cleanLine = line.trim() - if (cleanLine.includes('::')) { - // Это свойство - добавляем к предыдущему блоку - await logseq.Editor.upsertBlockProperty(prevBlockUuid, cleanLine.split('::')[0].trim(), cleanLine.split('::')[1].trim()) - } else if (cleanLine.startsWith('-')) { - // Вложенный контент - await logseq.Editor.insertBlock(prevBlockUuid, cleanLine.replace(/^-\s*/, ''), { sibling: false }) - } - } + // 1. Удаляем блоки, которых нет в новых данных + for (const [sourceId, existing] of existingMap) { + if (!newSourceIds.has(sourceId)) { + try { + await logseq.Editor.removeBlock(existing.uuid) + deleted++ + console.log('[TE] Удалён блок:', sourceId) + } catch (err) { + console.warn('[TE] Не удалось удалить блок:', existing.uuid, err) } } } + + // 2. Добавляем или обновляем блоки + let lastBlockUuid: string | null = null + + for (const newBlock of newBlocks) { + const existing = existingMap.get(newBlock.sourceId) + + if (existing) { + // Проверяем нужно ли обновлять + const contentChanged = normalizeContent(existing.content) !== normalizeContent(newBlock.content) + const propsChanged = !propsEqual(existing.properties, newBlock.properties) + + if (contentChanged || propsChanged) { + // Обновляем контент блока + if (contentChanged) { + await logseq.Editor.updateBlock(existing.uuid, newBlock.content) + } + + // Обновляем свойства + for (const [key, value] of Object.entries(newBlock.properties)) { + if (existing.properties[key] !== value) { + await logseq.Editor.upsertBlockProperty(existing.uuid, key, value) + } + } + + updated++ + console.log('[TE] Обновлён блок:', newBlock.sourceId) + } else { + unchanged++ + } + + lastBlockUuid = existing.uuid + } else { + // Добавляем новый блок + let insertedBlock + + if (lastBlockUuid) { + insertedBlock = await logseq.Editor.insertBlock(lastBlockUuid, newBlock.content, { sibling: true }) + } else { + insertedBlock = await logseq.Editor.insertBlock(page.uuid, newBlock.content, { sibling: false }) + } + + if (insertedBlock) { + // Устанавливаем свойства + for (const [key, value] of Object.entries(newBlock.properties)) { + await logseq.Editor.upsertBlockProperty(insertedBlock.uuid, key, value) + } + + // Добавляем дочерние блоки если есть + if (newBlock.children && newBlock.children.length > 0) { + for (const child of newBlock.children) { + await logseq.Editor.insertBlock(insertedBlock.uuid, child, { sibling: false }) + } + } + + lastBlockUuid = insertedBlock.uuid + added++ + console.log('[TE] Добавлен блок:', newBlock.sourceId) + } + } + } + + console.log(`[TE] writeToPageIncremental завершён: +${added} ~${updated} -${deleted} =${unchanged}`) + return { added, updated, deleted, unchanged } +} + +// Нормализация контента для сравнения (убираем свойства из текста) +function normalizeContent(content: string): string { + // Убираем свойства из начала контента + return content.replace(/^\w+-?\w*::[^\n]*\n?/gm, '').trim() +} + +// Сравнение свойств +function propsEqual(props1: Record, props2: Record): boolean { + // Проверяем только свойства из props2 (новые) + for (const key of Object.keys(props2)) { + if (String(props1[key] || '') !== String(props2[key])) { + return false + } + } + return true } // Запуск плагина diff --git a/vite.config.ts b/vite.config.ts index 0117e09..37e3a0f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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' } })