diff --git a/de_DE.json b/de_DE.json index 653384d..53c0771 100644 --- a/de_DE.json +++ b/de_DE.json @@ -14,6 +14,10 @@ "Gitea URL": "Gitea URL", "Owner": "Eigentümer", "Repository": "Repository", - "API Token": "API Token" + "API Token": "API Token", + "PR Filter": "PR-Filter", + "Assigned to me": "Mir zugewiesen", + "Review requested": "Review angefordert", + "Both": "Beides" } } diff --git a/en.json b/en.json index dd7b750..93d1479 100644 --- a/en.json +++ b/en.json @@ -14,6 +14,10 @@ "Gitea URL": "Gitea URL", "Owner": "Owner", "Repository": "Repository", - "API Token": "API Token" + "API Token": "API Token", + "PR Filter": "PR Filter", + "Assigned to me": "Assigned to me", + "Review requested": "Review requested", + "Both": "Both" } } diff --git a/manifest.json b/manifest.json index fbf2c01..448d370 100644 --- a/manifest.json +++ b/manifest.json @@ -49,6 +49,19 @@ ], "Tooltip": "Shows the latest Gitea Actions run status for a repository", "UUID": "dev.mars3142.ulanzideck.collection.gitea" + }, + { + "Name": "Gitea Pull Requests", + "Icon": "assets/icons/gitea.png", + "PropertyInspectorPath": "property-inspector/giteapr/inspector.html", + "States": [ + { + "Name": "Default", + "Image": "assets/icons/gitea.png" + } + ], + "Tooltip": "Shows open pull requests assigned to you across all repositories", + "UUID": "dev.mars3142.ulanzideck.collection.giteapr" } ], "OS": [ diff --git a/plugin/actions/GiteaPRAction.js b/plugin/actions/GiteaPRAction.js new file mode 100644 index 0000000..abc469e --- /dev/null +++ b/plugin/actions/GiteaPRAction.js @@ -0,0 +1,112 @@ +class GiteaPRAction extends ActionBase { + constructor($UD, context) { + super($UD, context); + this.config = { + url: '', + token: '', + filter: 'review_requested', + refreshRate: '4', + }; + } + + setActive() {} + + setParams(jsn) { + this.config = Object.assign(this.config, (jsn && jsn.param) || {}); + this.startTimer(() => this.fetchPRs(), this.config.refreshRate); + } + + onRun() { + if (this.config.url) { + this.$UD.openUrl(`${this.config.url}/pulls?type=your_repositories&sort=&state=open&q=`); + } + } + + fetchPRs() { + this.debounce(async () => { + if (!this.config.url || !this.config.token) { + this.renderButton(null, false); + return; + } + try { + const headers = { Authorization: `token ${this.config.token}` }; + const filter = this.config.filter || 'review_requested'; + const base = `${this.config.url}/api/v1/repos/issues/search?type=pullrequest&state=open&limit=50`; + + const fetchIds = async (url) => { + const r = await fetch(url, { headers, signal: AbortSignal.timeout(8000) }); + console.log('GiteaPR status:', r.status, url); + if (!r.ok) { + const body = await r.text(); + console.log('GiteaPR error body:', body); + return null; + } + const data = await r.json(); + return Array.isArray(data) ? data.map(i => i.id) : []; + }; + + let ids; + if (filter === 'both') { + const [a, b] = await Promise.all([ + fetchIds(`${base}&assigned=true`), + fetchIds(`${base}&review_requested=true`), + ]); + if (a === null || b === null) { this.renderButton(null, true); return; } + ids = [...new Set([...a, ...b])]; + } else { + const param = filter === 'assigned' ? '&assigned=true' : '&review_requested=true'; + ids = await fetchIds(`${base}${param}`); + if (ids === null) { this.renderButton(null, true); return; } + } + + this.renderButton(ids.length, false); + } catch (e) { + // timeout or network error = VPN likely down + this.renderButton(null, false); + } + }); + } + + renderButton(count, isError) { + const { canvas, ctx } = this.createCanvas(); + + // Label — top + ctx.fillStyle = '#7ec8e3'; + ctx.font = 'bold 22px "Source Han Sans SC"'; + ctx.fillText('Pull Requests', 98, 30); + + if (count === null) { + // No connection — show plug/disconnected icon + ctx.fillStyle = isError ? '#ff6b6b' : '#888888'; + ctx.font = '70px "Source Han Sans SC"'; + ctx.textBaseline = 'alphabetic'; + const bm = ctx.measureText('⚡'); + ctx.fillText('⚡', 98, 98 + (bm.actualBoundingBoxAscent - bm.actualBoundingBoxDescent) / 2); + ctx.textBaseline = 'middle'; + + ctx.fillStyle = isError ? '#ff6b6b' : '#888888'; + ctx.font = 'bold 22px "Source Han Sans SC"'; + ctx.fillText(isError ? 'API Error' : 'Offline', 98, 166); + } else { + // Count — large center number (visually centered using actual bounding box) + const color = count === 0 ? '#6bff6b' : count < 5 ? '#f0c040' : '#ff6b6b'; + ctx.fillStyle = color; + const fSize = count > 99 ? 60 : count > 9 ? 80 : 96; + ctx.font = `bold ${fSize}px "Source Han Sans SC"`; + const text = count > 999 ? '999+' : String(count); + ctx.textBaseline = 'alphabetic'; + const m = ctx.measureText(text); + ctx.fillText(text, 98, 98 + (m.actualBoundingBoxAscent - m.actualBoundingBoxDescent) / 2); + ctx.textBaseline = 'middle'; + + // Subtext + const filter = this.config.filter || 'review_requested'; + const label = filter === 'assigned' ? 'assigned' : filter === 'review_requested' ? 'review req.' : 'open'; + ctx.fillStyle = '#888888'; + ctx.font = '18px "Source Han Sans SC"'; + ctx.fillText(label, 98, 166); + } + + this.setIcon(canvas); + } +} diff --git a/plugin/app.html b/plugin/app.html index 9121a62..91f8782 100644 --- a/plugin/app.html +++ b/plugin/app.html @@ -15,6 +15,7 @@ + diff --git a/plugin/app.js b/plugin/app.js index f8ccd32..e6f8c1a 100644 --- a/plugin/app.js +++ b/plugin/app.js @@ -17,6 +17,8 @@ $UD.onAdd(jsn => { ACTION_CACHES[context] = new CopilotAction($UD, context); } else if (name === 'gitea') { ACTION_CACHES[context] = new GiteaAction($UD, context); + } else if (name === 'giteapr') { + ACTION_CACHES[context] = new GiteaPRAction($UD, context); } } diff --git a/property-inspector/giteapr/inspector.html b/property-inspector/giteapr/inspector.html new file mode 100644 index 0000000..d35d311 --- /dev/null +++ b/property-inspector/giteapr/inspector.html @@ -0,0 +1,49 @@ + + + + + + Gitea PRs + + + +
+
+
+
Gitea URL
+ +
+
+
API Token
+ +
+
+
PR Filter
+ +
+
+
Refresh Rate
+ +
+
+
+ + + + + + + + diff --git a/property-inspector/giteapr/inspector.js b/property-inspector/giteapr/inspector.js new file mode 100644 index 0000000..ec4a271 --- /dev/null +++ b/property-inspector/giteapr/inspector.js @@ -0,0 +1,31 @@ +let ACTION_SETTING = {}; +let form = ''; + +$UD.connect('dev.mars3142.ulanzideck.collection.giteapr'); + +$UD.onConnected(() => { + form = document.querySelector('#property-inspector'); + + form.addEventListener('input', Utils.debounce(() => { + const value = Utils.getFormValue(form); + ACTION_SETTING = { ...ACTION_SETTING, ...value }; + $UD.sendParamFromPlugin(ACTION_SETTING); + })); +}); + +$UD.onAdd(jsn => { + if (jsn && jsn.param) settingSaveParam(jsn.param); +}); + +$UD.onParamFromApp(jsn => { + settingSaveParam((jsn && jsn.param) || {}); +}); + +$UD.onParamFromPlugin(jsn => { + settingSaveParam((jsn && jsn.param) || {}); +}); + +function settingSaveParam(params) { + ACTION_SETTING = { ...ACTION_SETTING, ...params }; + if (form) Utils.setFormValue(ACTION_SETTING, form); +}