diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/ar.json b/packages/plugin-dev/gitea-issue-provider/i18n/ar.json new file mode 100644 index 0000000000..5c8d992a94 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/ar.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "المضيف (على سبيل المثال: \nhttps://try.gitea.io)", + "TOKEN": "رمز الوصول", + "REPO_FULL_NAME": "اسم المستخدم أو اسم المؤسسة/المشروع", + "SCOPE": "النطاق", + "SCOPE_ALL": "الكل", + "SCOPE_CREATED": "تم إنشاؤه بواسطتي", + "SCOPE_ASSIGNED": "تم تعيينه لي", + "FILTER_LABELS": "التصفية حسب التسميات (اختياري)", + "EXCLUDE_LABELS": "استبعاد التسميات (اختياري)", + "HOW_TO_GET_TOKEN": "كيف تحصل على الرمز المميز؟" + }, + "DISPLAY": { + "SUMMARY": "ملخص", + "STATE": "حالة", + "ASSIGNEE": "المحال", + "LABELS": "تسميات", + "DESCRIPTION": "وصف" + }, + "FORM_HELP": "

هنا يمكنك تهيئة 'سوبر برودكتيفيتي' لإدراج مشكلات ’جيتيا’ المفتوحة لمستودع معين في لوحة إنشاء المهام في عرض التخطيط اليومي. سيتم سردها كاقتراحات وستوفر رابطًا للمشكلة بالإضافة إلى مزيد من المعلومات عنها.

بالإضافة إلى ذلك يمكنك إضافة واستيراد جميع المشكلات المفتوحة تلقائيًا.

للحصول على حدود الاستخدام والوصول يمكنك توفير رمز وصول مميز." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/cs.json b/packages/plugin-dev/gitea-issue-provider/i18n/cs.json new file mode 100644 index 0000000000..30ca0392f4 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/cs.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Hostitel (např.: https://try.gitea.io)", + "TOKEN": "Přístupový token", + "REPO_FULL_NAME": "Uživatelské jméno nebo název organizace/projektu", + "SCOPE": "Rozsah", + "SCOPE_ALL": "Všechny", + "SCOPE_CREATED": "Vytvořeno mnou", + "SCOPE_ASSIGNED": "Přiřazeno mně", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "Jak získat token?" + }, + "DISPLAY": { + "SUMMARY": "Shrnutí", + "STATE": "Stav", + "ASSIGNEE": "Přiřazený", + "LABELS": "Štítky", + "DESCRIPTION": "Popis" + }, + "FORM_HELP": "

Zde můžete nakonfigurovat SuperProductivity tak, aby zobrazoval otevřené problémy Gitea pro konkrétní repozitář v panelu vytváření úkolů v denním plánovacím zobrazení. Budou uvedeny jako návrhy a poskytnou odkaz na problém a další informace o něm.

Kromě toho můžete automaticky přidat a importovat všechny otevřené problémy.

Pro překročení omezení a přístup můžete poskytnout přístupový token." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/de.json b/packages/plugin-dev/gitea-issue-provider/i18n/de.json new file mode 100644 index 0000000000..feab9e16cf --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/de.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (z. B. : https://try.gitea.io)", + "TOKEN": "Zugangstoken", + "REPO_FULL_NAME": "Benutzername oder Organisationsname/Projekt", + "SCOPE": "Umfang", + "SCOPE_ALL": "Alle", + "SCOPE_CREATED": "Von mir erstellt", + "SCOPE_ASSIGNED": "Mir zugewiesen", + "FILTER_LABELS": "Nach Labels filtern (optional)", + "EXCLUDE_LABELS": "Labels ausschließen (optional)", + "HOW_TO_GET_TOKEN": "Wie bekomme ich einen Token?" + }, + "DISPLAY": { + "SUMMARY": "Zusammenfassung", + "STATE": "Status", + "ASSIGNEE": "Zuständiger", + "LABELS": "Beschriftungen", + "DESCRIPTION": "Beschreibung" + }, + "FORM_HELP": "

Hier kannst du SuperProductivity so konfigurieren, dass offene Gitea-Issues für ein bestimmtes Repository im Aufgabenerstellungsfenster in der Tagesplanungsansicht aufgelistet werden. Sie werden als Vorschläge angezeigt und enthalten einen Link zum Issue sowie weitere Informationen dazu.

Darüber hinaus kannst du alle offenen Issues automatisch hinzufügen und importieren.

Um Nutzungsbeschränkungen zu umgehen und Zugriff zu erhalten, kannst du ein Zugriffstoken bereitstellen." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/en.json b/packages/plugin-dev/gitea-issue-provider/i18n/en.json new file mode 100644 index 0000000000..3f44bcbe86 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/en.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (e.g. https://try.gitea.io)", + "TOKEN": "Access Token", + "REPO_FULL_NAME": "Username or Organization name/repository", + "SCOPE": "Scope", + "SCOPE_ALL": "All", + "SCOPE_CREATED": "Created by me", + "SCOPE_ASSIGNED": "Assigned to me", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "How to get a token?" + }, + "DISPLAY": { + "SUMMARY": "Summary", + "STATE": "Status", + "ASSIGNEE": "Assignee", + "LABELS": "Labels", + "DESCRIPTION": "Description" + }, + "FORM_HELP": "

Here you can configure Super Productivity to list open Gitea issues for a specific repository in the task creation panel in the daily planning view. They will be listed as suggestions and will provide a link to the issue as well as more information about it.

In addition, you can automatically add and import all open issues.

To get by usage limits and gain access, you can provide an access token." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/es.json b/packages/plugin-dev/gitea-issue-provider/i18n/es.json new file mode 100644 index 0000000000..16bb5232d7 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/es.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (p. ej.: https://try.gitea.io)", + "TOKEN": "Token de acceso", + "REPO_FULL_NAME": "Nombre de usuario o nombre de la organización/proyecto", + "SCOPE": "Alcance", + "SCOPE_ALL": "Todos", + "SCOPE_CREATED": "Creado por mí", + "SCOPE_ASSIGNED": "Asignado a mí", + "FILTER_LABELS": "Filtrar por etiquetas (opcional)", + "EXCLUDE_LABELS": "Excluir etiquetas (opcional)", + "HOW_TO_GET_TOKEN": "¿Cómo obtener un token?" + }, + "DISPLAY": { + "SUMMARY": "Resumen", + "STATE": "Estado", + "ASSIGNEE": "Asignado", + "LABELS": "Etiquetas", + "DESCRIPTION": "Descripción" + }, + "FORM_HELP": "

Aquí puedes configurar SuperProductivity para listar las incidencias de Gitea abiertas para un repositorio específico en el panel de creación de tareas en la vista de planificación diaria. Se listarán como sugerencias y proporcionarán un enlace a la incidencia así como más información sobre ella.

Además, puedes añadir e importar automáticamente todas las incidencias abiertas.

Para superar los límites de uso y acceder puedes proporcionar un token de acceso." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/fa.json b/packages/plugin-dev/gitea-issue-provider/i18n/fa.json new file mode 100644 index 0000000000..4510c19ac0 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/fa.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "میزبان (به عنوان مثال: https://try.gitea.io)", + "TOKEN": "توکن دسترسی", + "REPO_FULL_NAME": "نام کاربری یا نام سازمان/پروژه", + "SCOPE": "دامنه", + "SCOPE_ALL": "همه", + "SCOPE_CREATED": "ایجاد شده توسط من", + "SCOPE_ASSIGNED": "به من اختصاص داده است", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "چگونه یک توکن دریافت کنیم؟" + }, + "DISPLAY": { + "SUMMARY": "خلاصه", + "STATE": "وضعیت", + "ASSIGNEE": "وکیل", + "LABELS": "برچسب", + "DESCRIPTION": "توضیحات" + }, + "FORM_HELP": "

در اینجا می‌توانید SuperProductivity را پیکربندی کنید تا مشکلات باز Gitea را برای یک مخزن خاص در پانل ایجاد کار در نمای برنامه‌ریزی روزانه فهرست کند. آنها به عنوان پیشنهاد فهرست می شوند و پیوندی به موضوع و همچنین اطلاعات بیشتری در مورد آن ارائه می دهند.

علاوه بر این، می‌توانید به‌طور خودکار همه مسائل باز را اضافه و وارد کنید.

برای دریافت محدودیت‌های استفاده و دسترسی، می‌توانید یک نشانه دسترسی ارائه کنید." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/fi.json b/packages/plugin-dev/gitea-issue-provider/i18n/fi.json new file mode 100644 index 0000000000..b953ee99c6 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/fi.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Isäntä (esim.: https://try.gitea.io)", + "TOKEN": "Käyttöoikeustunnus", + "REPO_FULL_NAME": "Käyttäjänimi tai organisaation nimi/projekti", + "SCOPE": "Laajuus", + "SCOPE_ALL": "Kaikki", + "SCOPE_CREATED": "Minun luomat", + "SCOPE_ASSIGNED": "Minulle osoitetut", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "Miten saan tunnuksen?" + }, + "DISPLAY": { + "SUMMARY": "Yhteenveto", + "STATE": "Tila", + "ASSIGNEE": "Vastuuhenkilö", + "LABELS": "Tunnisteet", + "DESCRIPTION": "Kuvaus" + }, + "FORM_HELP": "

Tässä voit määrittää SuperProductivityn listamaan avoimet Gitea-ongelmat tietystä arkistosta tehtävänluontipaneelissa päivittäisessä suunnittelinäkymässä. Ne listataan ehdotuksina ja tarjoavat linkin ongelmaan sekä lisätietoja siitä.

Lisäksi voit automaattisesti lisätä ja tuoda kaikki avoimet ongelmat.

Käyttörajoitusten ja pääsyn saamiseksi voit antaa käyttöoikeustunnuksen." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/fr.json b/packages/plugin-dev/gitea-issue-provider/i18n/fr.json new file mode 100644 index 0000000000..958b36c827 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/fr.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Hôte (e.g.: https://try.gitea.io)", + "TOKEN": "Jeton d'accès", + "REPO_FULL_NAME": "Nom d'utilisateur ou nom de l'organisation/projet", + "SCOPE": "Portée", + "SCOPE_ALL": "Tout", + "SCOPE_CREATED": "Créé par moi", + "SCOPE_ASSIGNED": "Attribués à moi", + "FILTER_LABELS": "Filtrer par étiquettes (facultatif)", + "EXCLUDE_LABELS": "Exclure des étiquettes (facultatif)", + "HOW_TO_GET_TOKEN": "Comment obtenir un jeton ?" + }, + "DISPLAY": { + "SUMMARY": "Résumé", + "STATE": "Statut", + "ASSIGNEE": "Bénéficiaire", + "LABELS": "Étiquettes", + "DESCRIPTION": "Description" + }, + "FORM_HELP": "

Ici, vous pouvez configurer SuperProductivity pour lister les problèmes ouverts de Gitea pour un dépôt spécifique dans le panneau de création de tâches dans la vue de planification quotidienne. Ils seront listés comme suggestions et fourniront un lien vers le problème ainsi que plus d'informations à son sujet.

En plus, vous pouvez automatiquement ajouter et importer tous les problèmes ouverts.

Pour contourner les limites d'utilisation et pour accéder, vous pouvez fournir un jeton d'accès.

" +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/hr.json b/packages/plugin-dev/gitea-issue-provider/i18n/hr.json new file mode 100644 index 0000000000..803e9bfba2 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/hr.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (npr. https://try.gitea.io)", + "TOKEN": "Token za pristup", + "REPO_FULL_NAME": "Korisničko ime ili ime organizacije/repozitorija", + "SCOPE": "Opseg", + "SCOPE_ALL": "Sve", + "SCOPE_CREATED": "Stvoreno od mene", + "SCOPE_ASSIGNED": "Dodijeljeno meni", + "FILTER_LABELS": "Filtriraj prema oznakama (opcionalno)", + "EXCLUDE_LABELS": "Isključi oznake (opcionalno)", + "HOW_TO_GET_TOKEN": "Kako dobiti token?" + }, + "DISPLAY": { + "SUMMARY": "Sažetak", + "STATE": "Status", + "ASSIGNEE": "Dodijeljeno", + "LABELS": "Oznake", + "DESCRIPTION": "Opis" + }, + "FORM_HELP": "

Ovdje možeš podesiti Super Productivity da prikazuje otvorene Gitea probleme za određeni repozitorij u ploči za stvaranje zadataka u dnevnom planiranju. Prikazat će se kao prijedlozi i imat će poveznicu na problem kao i dodatne informacije.

Također možeš automatski dodati i uvesti sve otvorene probleme.

Za izbjegavanja ograničenja i dobivanja pristupa, unesi token za pristup.

" +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/id.json b/packages/plugin-dev/gitea-issue-provider/i18n/id.json new file mode 100644 index 0000000000..33b6da61ca --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/id.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (contoh: https://try.gitea.io)", + "TOKEN": "Akses Token", + "REPO_FULL_NAME": "Nama pengguna atau nama/proyek Organisasi", + "SCOPE": "Cakupan", + "SCOPE_ALL": "Semua", + "SCOPE_CREATED": "Dibuat oleh saya", + "SCOPE_ASSIGNED": "Ditugaskan kepada saya", + "FILTER_LABELS": "Filter berdasarkan label (opsional)", + "EXCLUDE_LABELS": "Kecualikan label (opsional)", + "HOW_TO_GET_TOKEN": "Bagaimana cara mendapatkan token?" + }, + "DISPLAY": { + "SUMMARY": "Ringkasan", + "STATE": "Status", + "ASSIGNEE": "Penanggung jawab", + "LABELS": "Label", + "DESCRIPTION": "Deskripsi" + }, + "FORM_HELP": "

Di sini Anda dapat mengonfigurasi SuperProductivity untuk membuat daftar open Gitea issues untuk repositori tertentu di panel pembuatan tugas dalam tampilan perencanaan harian. Mereka akan dicantumkan sebagai saran dan akan memberikan tautan ke masalah tersebut serta informasi lebih lanjut tentangnya.

Selain itu, Anda dapat secara otomatis menambahkan dan menyinkronkan semua open issues ke backlog tugas Anda.

Untuk mencapai batas penggunaan dan untuk mengakses, Anda dapat memberikan token akses.

" +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/it.json b/packages/plugin-dev/gitea-issue-provider/i18n/it.json new file mode 100644 index 0000000000..58e49f6bd1 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/it.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (es.: https://try.gitea.io)", + "TOKEN": "Token di accesso", + "REPO_FULL_NAME": "Username o nome Organizzazione/Progetto", + "SCOPE": "Scopo", + "SCOPE_ALL": "Tutti", + "SCOPE_CREATED": "Create da me", + "SCOPE_ASSIGNED": "Assegnate a me", + "FILTER_LABELS": "Filtra per etichette (facoltativo)", + "EXCLUDE_LABELS": "Escludi etichette (facoltativo)", + "HOW_TO_GET_TOKEN": "Come ottenere un token?" + }, + "DISPLAY": { + "SUMMARY": "Riepilogo", + "STATE": "Stato", + "ASSIGNEE": "Assegnatario", + "LABELS": "Etichette", + "DESCRIPTION": "Descrizione" + }, + "FORM_HELP": "

Qui è possibile configurare SuperProductivity in modo che elenchi le issue aperte di Gitea per uno specifico repository nel pannello di creazione delle attività nella vista di pianificazione giornaliera. Saranno elencati come suggerimenti e forniranno un link all'issue e ulteriori informazioni su di esso.

Inoltre è possibile aggiungere e importare automaticamente tutte le issue aperte.

Per superare i limiti di utilizzo e accedere è possibile fornire un token di accesso." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/ja.json b/packages/plugin-dev/gitea-issue-provider/i18n/ja.json new file mode 100644 index 0000000000..e796cf6075 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/ja.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "ホスト (例:https://try.gitea.io)", + "TOKEN": "アクセストークン", + "REPO_FULL_NAME": "ユーザー名または組織名/プロジェクト名", + "SCOPE": "範囲", + "SCOPE_ALL": "全て", + "SCOPE_CREATED": "私が作った", + "SCOPE_ASSIGNED": "私に割り当てられた", + "FILTER_LABELS": "ラベルで絞り込み(任意)", + "EXCLUDE_LABELS": "ラベルを除外(任意)", + "HOW_TO_GET_TOKEN": "トークンの入手方法は?" + }, + "DISPLAY": { + "SUMMARY": "概要", + "STATE": "地位", + "ASSIGNEE": "担当者", + "LABELS": "ラベル", + "DESCRIPTION": "形容" + }, + "FORM_HELP": "

ここで SuperProductivity を設定し、日次計画ビューのタスク作成パネルで特定のリポジトリに対して未解決の Gitea 課題を一覧表示させることができます。それらは提案としてリストされ、課題へのリンクとそれに関する詳細情報が提供されます。

さらに、すべてのオープン課題を自動的に追加してインポートすることもできます。

利用制限を通過してアクセスするには、アクセストークンを提供することができます。" +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/ko.json b/packages/plugin-dev/gitea-issue-provider/i18n/ko.json new file mode 100644 index 0000000000..ce371721ec --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/ko.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "호스트(예: https://try.gitea.io)", + "TOKEN": "액세스 토큰", + "REPO_FULL_NAME": "사용자 이름 또는 조직 이름/프로젝트", + "SCOPE": "범위", + "SCOPE_ALL": "모두", + "SCOPE_CREATED": "작성자: 나", + "SCOPE_ASSIGNED": "나에게 할당됨", + "FILTER_LABELS": "라벨로 필터링(선택 사항)", + "EXCLUDE_LABELS": "라벨 제외(선택 사항)", + "HOW_TO_GET_TOKEN": "토큰은 어떻게 받나요?" + }, + "DISPLAY": { + "SUMMARY": "요약", + "STATE": "상태", + "ASSIGNEE": "담당자", + "LABELS": "레이블", + "DESCRIPTION": "묘사" + }, + "FORM_HELP": "

여기에서 일일 계획 보기의 작업 만들기 패널에서 특정 리포지토리에 대해 열려 있는 Gitea 이슈를 나열하도록 SuperProductivity를 구성할 수 있습니다. 이슈는 제안 사항으로 나열되며 이슈에 대한 링크와 자세한 정보를 제공합니다.

또한 열려 있는 모든 이슈를 자동으로 추가하고 가져올 수 있습니다.

사용량 제한을 통과하고 액세스하려면 액세스 토큰을 제공할 수 있습니다." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/nb.json b/packages/plugin-dev/gitea-issue-provider/i18n/nb.json new file mode 100644 index 0000000000..b00ff99267 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/nb.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Vert (f.eks.: https://try.gitea.io)", + "TOKEN": "Access Token", + "REPO_FULL_NAME": "Brukernavn eller organisasjonsnavn/prosjekt", + "SCOPE": "Omfang", + "SCOPE_ALL": "Alle", + "SCOPE_CREATED": "Laget av meg", + "SCOPE_ASSIGNED": "Tildelt til meg", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "Hvordan får man et token?" + }, + "DISPLAY": { + "SUMMARY": "Sammendrag", + "STATE": "Status", + "ASSIGNEE": "Tildelt", + "LABELS": "Etiketter", + "DESCRIPTION": "Beskrivelse" + }, + "FORM_HELP": "

Her kan du konfigurere SuperProductivity til å liste åpne Gitea-problemer for et spesifikt repository i opprettingspanelet for oppgaver i den daglige planleggingsvisningen. De vil bli listet som forslag og vil gi en lenke til problemet samt mer informasjon om det.<\\/p>

I tillegg kan du automatisk legge til og importere alle åpne problemer.<\\/p>

For å komme forbi bruksgrensene og få tilgang kan du gi et tilgangstoken." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/nl.json b/packages/plugin-dev/gitea-issue-provider/i18n/nl.json new file mode 100644 index 0000000000..64be91178b --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/nl.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (bijv. https://try.gitea.io)", + "TOKEN": "Access Token", + "REPO_FULL_NAME": "Gebruikersnaam of Organisatie naam/project", + "SCOPE": "Toepassingsgebied", + "SCOPE_ALL": "Alle", + "SCOPE_CREATED": "Aangemaakt door mij", + "SCOPE_ASSIGNED": "Aan mij toegewezen", + "FILTER_LABELS": "Filteren op labels (optioneel)", + "EXCLUDE_LABELS": "Labels uitsluiten (optioneel)", + "HOW_TO_GET_TOKEN": "Hoe krijg ik een token?" + }, + "DISPLAY": { + "SUMMARY": "Samenvatting", + "STATE": "Status", + "ASSIGNEE": "Toegewezen persoon", + "LABELS": "Labels", + "DESCRIPTION": "Beschrijving" + }, + "FORM_HELP": "

Hier kun je SuperProductivity configureren om open Gitea kwesties voor een specifieke repository te tonen in het paneel voor het aanmaken van taken in de dagelijkse planningsweergave. Ze worden weergegeven als suggesties en geven een link naar het issue en meer informatie over het issue.

Daarnaast kun je automatisch alle openstaande issues toevoegen en importeren.

Om de gebruikslimieten te omzeilen en toegang te krijgen kun je een toegangstoken geven." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/pl.json b/packages/plugin-dev/gitea-issue-provider/i18n/pl.json new file mode 100644 index 0000000000..7698afb87a --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/pl.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Host (np. https://try.gitea.io)", + "TOKEN": "Token dostępu", + "REPO_FULL_NAME": "Nazwa użytkownika lub nazwa organizacji/projektu", + "SCOPE": "Zakres", + "SCOPE_ALL": "Wszystkie", + "SCOPE_CREATED": "Stworzony przeze mnie", + "SCOPE_ASSIGNED": "Przydzielony do mnie", + "FILTER_LABELS": "Filtruj według etykiet (opcjonalnie)", + "EXCLUDE_LABELS": "Wyklucz etykiety (opcjonalnie)", + "HOW_TO_GET_TOKEN": "Jak zdobyć token?" + }, + "DISPLAY": { + "SUMMARY": "Podsumowanie", + "STATE": "Status", + "ASSIGNEE": "Osoba przypisana", + "LABELS": "Etykiety", + "DESCRIPTION": "Opis" + }, + "FORM_HELP": "

Tutaj można skonfigurować SuperProductivity tak, aby wyświetlał listę otwartych zgłoszeń Gitea dla określonego repozytorium w panelu tworzenia zadań w widoku planowania dziennego. Będą one wyświetlane jako sugestie i będą zawierać link do zgłoszenia oraz więcej informacji na jego temat.

Ponadto możesz automatycznie dodawać i importować wszystkie otwarte zgłoszenia.

Aby ominąć limity użytkowania i uzyskać dostęp, możesz podać token dostępu." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/pt-br.json b/packages/plugin-dev/gitea-issue-provider/i18n/pt-br.json new file mode 100644 index 0000000000..c7ebb1c4ff --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/pt-br.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Servidor (ex: https://try.gitea.io)", + "TOKEN": "Token de Acesso", + "REPO_FULL_NAME": "Nome do Usuário ou Organização/repositório", + "SCOPE": "Escopo", + "SCOPE_ALL": "Todos", + "SCOPE_CREATED": "Criado por mim", + "SCOPE_ASSIGNED": "Atribuído a mim", + "FILTER_LABELS": "Filtrar por rótulos (opcional)", + "EXCLUDE_LABELS": "Excluir rótulos (opcional)", + "HOW_TO_GET_TOKEN": "Como obter um token?" + }, + "DISPLAY": { + "SUMMARY": "Resumo", + "STATE": "Status", + "ASSIGNEE": "Responsável", + "LABELS": "Etiquetas", + "DESCRIPTION": "Descrição" + }, + "FORM_HELP": "

Aqui você pode configurar o Super Productivity para listar issues abertas do Gitea de um repositório específico no painel de criação de tarefas na visualização de planejador. Elas serão listadas como sugestões e fornecerão um link para a issue, bem como mais informações sobre ela.

Além disso, você pode adicionar e importar automaticamente todas as issues abertas.

Para contornar limites de uso e para ter acesso, você pode fornecer um token de acesso." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/pt.json b/packages/plugin-dev/gitea-issue-provider/i18n/pt.json new file mode 100644 index 0000000000..7d35f33d26 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/pt.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Endereço do servidor (ex: https://try.gitea.io)", + "TOKEN": "Token de acesso", + "REPO_FULL_NAME": "Nome do usuário ou organização/projeto", + "SCOPE": "Escopo", + "SCOPE_ALL": "Todos", + "SCOPE_CREATED": "Criados por mim", + "SCOPE_ASSIGNED": "Atribuídos a mim", + "FILTER_LABELS": "Filtrar por etiquetas (opcional)", + "EXCLUDE_LABELS": "Excluir etiquetas (opcional)", + "HOW_TO_GET_TOKEN": "Como obter um token?" + }, + "DISPLAY": { + "SUMMARY": "Resumo", + "STATE": "Estado", + "ASSIGNEE": "Atribuído", + "LABELS": "Etiquetas", + "DESCRIPTION": "Descrição" + }, + "FORM_HELP": "

Aqui você pode configurar o SuperProductivity para listar tarefas do Gitea de um repositório específico no painel de tarefas na tela de planejamento diário. Elas também serão listadas como sugestões e será disponibilizado um link para a tarefa bem como mais informações sobre a mesma.

Além disso você pode adicionar e sincronizar todas as tarefas abertas para o seu backlog.

" +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/ro-md.json b/packages/plugin-dev/gitea-issue-provider/i18n/ro-md.json new file mode 100644 index 0000000000..728874fd21 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/ro-md.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Gazdă (d.e. https://try.gitea.io)", + "TOKEN": "Jeton de acces", + "REPO_FULL_NAME": "Nume utilizator sau nume organizație/proiect", + "SCOPE": "Domeniu de aplicare", + "SCOPE_ALL": "Toate", + "SCOPE_CREATED": "Create de mine", + "SCOPE_ASSIGNED": "Atribuite mie", + "FILTER_LABELS": "Filtrează după etichete (opțional)", + "EXCLUDE_LABELS": "Exclude etichetele (opțional)", + "HOW_TO_GET_TOKEN": "Cum se obține un jeton?" + }, + "DISPLAY": { + "SUMMARY": "Sumar", + "STATE": "Stare", + "ASSIGNEE": "Asignat lui", + "LABELS": "Etichete", + "DESCRIPTION": "Descriere" + }, + "FORM_HELP": "

Aici poți configura Super Productivity pentru a lista problemele deschise pe Gitea pentru un repository specific în panoul de creare a sarcinilor în modul de planificare zilnic. Acestea vor fi listate ca sugestii și vor conține o legătură către problemă și mai multe informații despre acesta.

În plus, poți adăuga și importa automat toate problemele deschise.

Pentru a fenta limitele de utilizare și acces, poți folosi un jeton de acces." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/ro.json b/packages/plugin-dev/gitea-issue-provider/i18n/ro.json new file mode 100644 index 0000000000..728874fd21 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/ro.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Gazdă (d.e. https://try.gitea.io)", + "TOKEN": "Jeton de acces", + "REPO_FULL_NAME": "Nume utilizator sau nume organizație/proiect", + "SCOPE": "Domeniu de aplicare", + "SCOPE_ALL": "Toate", + "SCOPE_CREATED": "Create de mine", + "SCOPE_ASSIGNED": "Atribuite mie", + "FILTER_LABELS": "Filtrează după etichete (opțional)", + "EXCLUDE_LABELS": "Exclude etichetele (opțional)", + "HOW_TO_GET_TOKEN": "Cum se obține un jeton?" + }, + "DISPLAY": { + "SUMMARY": "Sumar", + "STATE": "Stare", + "ASSIGNEE": "Asignat lui", + "LABELS": "Etichete", + "DESCRIPTION": "Descriere" + }, + "FORM_HELP": "

Aici poți configura Super Productivity pentru a lista problemele deschise pe Gitea pentru un repository specific în panoul de creare a sarcinilor în modul de planificare zilnic. Acestea vor fi listate ca sugestii și vor conține o legătură către problemă și mai multe informații despre acesta.

În plus, poți adăuga și importa automat toate problemele deschise.

Pentru a fenta limitele de utilizare și acces, poți folosi un jeton de acces." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/ru.json b/packages/plugin-dev/gitea-issue-provider/i18n/ru.json new file mode 100644 index 0000000000..f2c59e6ef2 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/ru.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Хост (н-р: https://try.gitea.io)", + "TOKEN": "Токен доступа", + "REPO_FULL_NAME": "Имя пользователя или название организации/проекта", + "SCOPE": "Скоуп", + "SCOPE_ALL": "Все", + "SCOPE_CREATED": "Создано мной", + "SCOPE_ASSIGNED": "Назначено мне", + "FILTER_LABELS": "Фильтровать по меткам (необязательно)", + "EXCLUDE_LABELS": "Исключить метки (необязательно)", + "HOW_TO_GET_TOKEN": "Как получить токен?" + }, + "DISPLAY": { + "SUMMARY": "Резюме", + "STATE": "Статус", + "ASSIGNEE": "Исполнитель", + "LABELS": "Метки", + "DESCRIPTION": "Описание" + }, + "FORM_HELP": "

Здесь вы можете настроить SuperProductivity чтобы отображать проблемы Gitea для конкретного репозитория на панели создания задач в представлении ежедневного планирования. Они будут перечислены в качестве предложений и предоставят ссылку на проблему, а также дополнительную информацию о ней.

Кроме того, вы можете автоматически добавлять и синхронизировать все открытые проблемы.

Чтобы получить доступ к закрытым репозиториям, вы можете предоставить токен доступа." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/sk.json b/packages/plugin-dev/gitea-issue-provider/i18n/sk.json new file mode 100644 index 0000000000..84f52e06b6 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/sk.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Hostiteľ (napr.: https://try.gitea.io)", + "TOKEN": "Prístupový token", + "REPO_FULL_NAME": "Používateľské meno alebo názov organizácie/projektu", + "SCOPE": "Rozsah pôsobnosti", + "SCOPE_ALL": "Všetky", + "SCOPE_CREATED": "Vytvorené mnou", + "SCOPE_ASSIGNED": "Pridelené mne", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "Ako získať token?" + }, + "DISPLAY": { + "SUMMARY": "Zhrnutie", + "STATE": "Stav", + "ASSIGNEE": "Priradený", + "LABELS": "Štítky", + "DESCRIPTION": "Popis" + }, + "FORM_HELP": "

Tu môžete nakonfigurovať program SuperProductivity tak, aby na paneli vytvárania úloh v zobrazení denného plánovania zobrazoval zoznam otvorených problémov Gitea pre konkrétny repozitár. Budú uvedené ako návrhy a budú obsahovať odkaz na problém, ako aj ďalšie informácie o ňom.

Okrem toho môžete automaticky pridať a importovať všetky otvorené problémy.

Ak chcete obísť obmedzenia používania a získať prístup, môžete zadať prístupový token." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/sv.json b/packages/plugin-dev/gitea-issue-provider/i18n/sv.json new file mode 100644 index 0000000000..407d60df59 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/sv.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Värd (t.ex. https://try.gitea.io)", + "TOKEN": "Åtkomsttoken", + "REPO_FULL_NAME": "Användarnamn eller organisationsnamn/projekt", + "SCOPE": "Omfattning", + "SCOPE_ALL": "Alla", + "SCOPE_CREATED": "Skapade av mig", + "SCOPE_ASSIGNED": "Ålagda mig", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "Hur får man en token?" + }, + "DISPLAY": { + "SUMMARY": "Sammanfattning", + "STATE": "Status", + "ASSIGNEE": "Mottagare", + "LABELS": "Etiketter", + "DESCRIPTION": "Beskrivning" + }, + "FORM_HELP": "

Här kan du konfigurera SuperProductivity så att öppna Gitea-ärenden för ett specifikt arkiv listas i panelen för uppgiftskapande i den dagliga planeringsvyn. De listas som förslag och innehåller en länk till ärendet samt mer information om det.

Dessutom kan du automatiskt lägga till och importera alla öppna ärenden.

För att komma förbi användningsbegränsningar och få åtkomst kan du ange en åtkomsttoken." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/tr.json b/packages/plugin-dev/gitea-issue-provider/i18n/tr.json new file mode 100644 index 0000000000..859e90c087 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/tr.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Sağlayıcı (örneğin: https://try.gitea.io)", + "TOKEN": "Erişim Jetonu", + "REPO_FULL_NAME": "Kullanıcı adı veya Organizasyon adı/projesi", + "SCOPE": "Kapsam", + "SCOPE_ALL": "Hepsi", + "SCOPE_CREATED": "Benim tarafımdan oluşturuldu", + "SCOPE_ASSIGNED": "Bana atanan", + "FILTER_LABELS": "Etiketlere göre filtrele (isteğe bağlı)", + "EXCLUDE_LABELS": "Etiketleri hariç tut (isteğe bağlı)", + "HOW_TO_GET_TOKEN": "Bir token nasıl alınır?" + }, + "DISPLAY": { + "SUMMARY": "Özet", + "STATE": "Durum", + "ASSIGNEE": "Atanan", + "LABELS": "Etiketler", + "DESCRIPTION": "Açıklama" + }, + "FORM_HELP": "

Burada SuperProductivity'yi, günlük planlama görünümündeki görev oluşturma panelinde belirli bir depo için açık Gitea sorunlarını listeleyecek şekilde yapılandırabilirsiniz. Öneriler olarak listelenecekler ve konuyla ilgili bir bağlantının yanı sıra konuyla ilgili daha fazla bilgi sağlayacaklar.

Ek olarak, tüm açık sorunları otomatik olarak görev biriktirme listenize ekleyebilir ve senkronize edebilirsiniz.

Kullanım sınırlarını aşmak ve erişim sağlamak için bir erişim jetonu sağlayabilirsiniz." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/uk.json b/packages/plugin-dev/gitea-issue-provider/i18n/uk.json new file mode 100644 index 0000000000..d8c3509e1f --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/uk.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Хост (наприклад: https://try.gitea.io)", + "TOKEN": "Токен доступу", + "REPO_FULL_NAME": "Ім'я користувача або назва організації/проекту", + "SCOPE": "Область пошуку (Scope)", + "SCOPE_ALL": "Усі", + "SCOPE_CREATED": "Створені мною", + "SCOPE_ASSIGNED": "Призначені мені", + "FILTER_LABELS": "Filter by labels (optional)", + "EXCLUDE_LABELS": "Exclude labels (optional)", + "HOW_TO_GET_TOKEN": "Як отримати токен?" + }, + "DISPLAY": { + "SUMMARY": "Підсумок", + "STATE": "Статус", + "ASSIGNEE": "Виконавець", + "LABELS": "Мітки", + "DESCRIPTION": "Опис" + }, + "FORM_HELP": "

Тут ви можете налаштувати SuperProductivity для відображення відкритих завдань Gitea для конкретного репозиторію в панелі створення завдань у вікні щоденного планування. Вони будуть показані як пропозиції та надаватимуть посилання на завдання, а також додаткову інформацію про нього.

Крім того, ви можете автоматично додавати та імпортувати всі відкриті завдання.

Щоб обійти обмеження використання та отримати доступ, ви можете надати токен доступу." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/vi.json b/packages/plugin-dev/gitea-issue-provider/i18n/vi.json new file mode 100644 index 0000000000..153157f0b3 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/vi.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "Máy chủ (ví dụ: https://try.gitea.io)", + "TOKEN": "Token truy cập", + "REPO_FULL_NAME": "Tên người dùng hoặc Tổ chức/kho lưu trữ", + "SCOPE": "Phạm vi", + "SCOPE_ALL": "Tất cả", + "SCOPE_CREATED": "Do tôi tạo", + "SCOPE_ASSIGNED": "Được giao cho tôi", + "FILTER_LABELS": "Lọc theo nhãn (tùy chọn)", + "EXCLUDE_LABELS": "Loại trừ nhãn (tùy chọn)", + "HOW_TO_GET_TOKEN": "Cách lấy token?" + }, + "DISPLAY": { + "SUMMARY": "Tóm tắt", + "STATE": "Trạng thái", + "ASSIGNEE": "Người được giao", + "LABELS": "Nhãn", + "DESCRIPTION": "Mô tả" + }, + "FORM_HELP": "

Tại đây bạn có thể cấu hình Super Productivity để liệt kê các sự cố Gitea mở cho một kho lưu trữ cụ thể trong bảng tạo công việc ở chế độ xem lên kế hoạch hàng ngày. Chúng sẽ được liệt kê làm gợi ý và cung cấp liên kết đến sự cố cũng như thêm thông tin về nó.

Ngoài ra, bạn có thể tự động thêm và nhập tất cả sự cố mở.

Để vượt qua giới hạn sử dụng và truy cập, bạn có thể cung cấp token truy cập." +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/zh-tw.json b/packages/plugin-dev/gitea-issue-provider/i18n/zh-tw.json new file mode 100644 index 0000000000..420be47d4a --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/zh-tw.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "主機(例如:https://try.gitea.io)", + "TOKEN": "存取權杖", + "REPO_FULL_NAME": "使用者名稱或組織名稱/專案", + "SCOPE": "範圍", + "SCOPE_ALL": "全部", + "SCOPE_CREATED": "由我建立", + "SCOPE_ASSIGNED": "指派給我", + "FILTER_LABELS": "依標籤篩選(選填)", + "EXCLUDE_LABELS": "排除標籤(選填)", + "HOW_TO_GET_TOKEN": "如何取得權杖?" + }, + "DISPLAY": { + "SUMMARY": "摘要", + "STATE": "狀態", + "ASSIGNEE": "指派人", + "LABELS": "標籤", + "DESCRIPTION": "描述" + }, + "FORM_HELP": "

在此您可以設定 Super Productivity 在每日規劃檢視的工作建立面板中列出特定程式碼庫的開放 Gitea 議題。它們將作為建議列出,並提供議題的連結以及更多相關資訊。

此外,您可以自動新增和匯入所有開放議題。

為了繞過使用限制並存取,您可以提供存取權杖。" +} diff --git a/packages/plugin-dev/gitea-issue-provider/i18n/zh.json b/packages/plugin-dev/gitea-issue-provider/i18n/zh.json new file mode 100644 index 0000000000..9739ae2f55 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/i18n/zh.json @@ -0,0 +1,22 @@ +{ + "CFG": { + "HOST": "主机(例如:https://try.gitea.io)", + "TOKEN": "访问令牌", + "REPO_FULL_NAME": "用户名或组织名称/项目", + "SCOPE": "范围", + "SCOPE_ALL": "全部", + "SCOPE_CREATED": "由我创建", + "SCOPE_ASSIGNED": "分配给我", + "FILTER_LABELS": "按标签筛选(可选)", + "EXCLUDE_LABELS": "排除标签(可选)", + "HOW_TO_GET_TOKEN": "如何获取令牌?" + }, + "DISPLAY": { + "SUMMARY": "摘要", + "STATE": "状态", + "ASSIGNEE": "负责人", + "LABELS": "标签", + "DESCRIPTION": "描述" + }, + "FORM_HELP": "

在这里,您可以将 SuperProductivity 配置为在每日规划视图的任务创建看板中列出特定存储库的未结 Gitea 问题。它们将列为建议,并提供指向问题的链接以及有关它的更多信息。

此外,您还可以自动添加和导入所有未结问题。

为了通过使用限制并访问,您可以提供一个访问令牌。" +} diff --git a/packages/plugin-dev/gitea-issue-provider/icon.svg b/packages/plugin-dev/gitea-issue-provider/icon.svg new file mode 100644 index 0000000000..bae7e5aa72 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/plugin-dev/gitea-issue-provider/package-lock.json b/packages/plugin-dev/gitea-issue-provider/package-lock.json new file mode 100644 index 0000000000..ca96ea3c47 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/package-lock.json @@ -0,0 +1,532 @@ +{ + "name": "gitea-issue-provider", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gitea-issue-provider", + "version": "1.0.0", + "dependencies": { + "@super-productivity/plugin-api": "file:../../plugin-api" + }, + "devDependencies": { + "esbuild": "^0.25.11", + "typescript": "^5.8.3" + } + }, + "../../plugin-api": { + "name": "@super-productivity/plugin-api", + "version": "1.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@super-productivity/plugin-api": { + "resolved": "../../plugin-api", + "link": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/packages/plugin-dev/gitea-issue-provider/package.json b/packages/plugin-dev/gitea-issue-provider/package.json new file mode 100644 index 0000000000..5c94ce6564 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/package.json @@ -0,0 +1,17 @@ +{ + "name": "gitea-issue-provider", + "version": "1.0.0", + "description": "Gitea issue provider plugin", + "scripts": { + "build": "node scripts/build.js", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit" + }, + "devDependencies": { + "esbuild": "^0.25.11", + "typescript": "^5.8.3" + }, + "dependencies": { + "@super-productivity/plugin-api": "file:../../plugin-api" + } +} diff --git a/packages/plugin-dev/gitea-issue-provider/scripts/build.js b/packages/plugin-dev/gitea-issue-provider/scripts/build.js new file mode 100644 index 0000000000..90ae76df05 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/scripts/build.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { build } = require('esbuild'); + +const ROOT_DIR = path.join(__dirname, '..'); +const SRC_DIR = path.join(ROOT_DIR, 'src'); +const DIST_DIR = path.join(ROOT_DIR, 'dist'); +const I18N_SRC = path.join(ROOT_DIR, 'i18n'); +const I18N_DIST = path.join(DIST_DIR, 'i18n'); + +async function buildPlugin() { + console.log('Building gitea-issue-provider...'); + + if (fs.existsSync(DIST_DIR)) { + fs.rmSync(DIST_DIR, { recursive: true }); + } + fs.mkdirSync(DIST_DIR); + + // Build TypeScript to IIFE bundle + await build({ + entryPoints: [path.join(SRC_DIR, 'plugin.ts')], + bundle: true, + outfile: path.join(DIST_DIR, 'plugin.js'), + platform: 'browser', + target: 'es2020', + format: 'iife', + define: { + 'process.env.NODE_ENV': '"production"', + }, + logLevel: 'info', + minify: true, + sourcemap: false, + }); + + // Copy manifest.json + fs.copyFileSync( + path.join(SRC_DIR, 'manifest.json'), + path.join(DIST_DIR, 'manifest.json'), + ); + + // Copy icon.svg if present + const iconSrc = path.join(ROOT_DIR, 'icon.svg'); + if (fs.existsSync(iconSrc)) { + fs.copyFileSync(iconSrc, path.join(DIST_DIR, 'icon.svg')); + console.log('Copied icon.svg'); + } + + // Copy i18n files + if (fs.existsSync(I18N_SRC)) { + fs.mkdirSync(I18N_DIST, { recursive: true }); + for (const file of fs.readdirSync(I18N_SRC)) { + if (file.endsWith('.json')) { + fs.copyFileSync(path.join(I18N_SRC, file), path.join(I18N_DIST, file)); + } + } + console.log('Copied i18n files'); + } + + console.log('Build complete!'); +} + +buildPlugin().catch((err) => { + console.error('Build failed:', err); + process.exit(1); +}); diff --git a/packages/plugin-dev/gitea-issue-provider/src/manifest.json b/packages/plugin-dev/gitea-issue-provider/src/manifest.json new file mode 100644 index 0000000000..4e1466a7d7 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/src/manifest.json @@ -0,0 +1,57 @@ +{ + "id": "gitea-issue-provider", + "name": "Gitea Issues", + "version": "1.0.0", + "manifestVersion": 1, + "minSupVersion": "13.0.0", + "description": "Gitea issue provider plugin", + "author": "Super Productivity", + "type": "issueProvider", + "icon": "icon.svg", + "iFrame": false, + "permissions": [], + "hooks": [], + "i18n": { + "languages": [ + "ar", + "cs", + "de", + "en", + "es", + "fa", + "fi", + "fr", + "hr", + "id", + "it", + "ja", + "ko", + "nb", + "nl", + "pl", + "pt", + "pt-br", + "ro", + "ro-md", + "ru", + "sk", + "sv", + "tr", + "uk", + "vi", + "zh", + "zh-tw" + ] + }, + "issueProvider": { + "pollIntervalMs": 300000, + "icon": "gitea", + "humanReadableName": "Gitea", + "issueProviderKey": "GITEA", + "allowPrivateNetwork": true, + "issueStrings": { + "singular": "Issue", + "plural": "Issues" + } + } +} diff --git a/packages/plugin-dev/gitea-issue-provider/src/plugin.ts b/packages/plugin-dev/gitea-issue-provider/src/plugin.ts new file mode 100644 index 0000000000..1dfc8c06e8 --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/src/plugin.ts @@ -0,0 +1,371 @@ +import type { + IssueProviderPluginDefinition, + PluginFieldMapping, + PluginHttp, + PluginHttpOptions, + PluginIssue, + PluginSearchResult, +} from '@super-productivity/plugin-api'; + +declare const PluginAPI: { + registerIssueProvider(definition: IssueProviderPluginDefinition): void; + translate(key: string, params?: Record): string; +}; + +const API_SUFFIX = 'api'; +const API_VERSION = 'v1'; + +// Scope option values — must stay identical to the built-in provider's stored +// config so existing (migrated) providers keep working. +const SCOPE_CREATED_BY_ME = 'created-by-me'; +const SCOPE_ASSIGNED_TO_ME = 'assigned-to-me'; + +interface GiteaConfig { + host?: string; + token?: string; + repoFullname?: string; + scope?: string; + filterLabels?: string; + excludeLabels?: string; +} + +interface GiteaUser { + avatar_url: string; + id: number; + username: string; + login: string; + full_name: string; +} + +interface GiteaLabel { + id: number; + name: string; + color: string; +} + +interface GiteaRepositoryReduced { + id: number; + full_name: string; +} + +interface GiteaIssue { + id: number; + number: number; + url: string; + html_url: string; + title: string; + body: string; + state: string; + labels: GiteaLabel[]; + user: GiteaUser | null; + assignee: GiteaUser | null; + assignees: GiteaUser[] | null; + comments: number; + created_at: string; + updated_at: string; + closed_at: string | null; + repository: GiteaRepositoryReduced | null; +} + +interface GiteaComment { + id: number; + body: string; + created_at: string; + user: GiteaUser | null; +} + +const t = (key: string): string => { + try { + return PluginAPI.translate(key); + } catch { + return key; + } +}; + +const baseUrl = (cfg: GiteaConfig): string => { + const host = (cfg.host || '').replace(/\/+$/, ''); + if (!host) { + throw new Error('Gitea host is not configured.'); + } + return `${host}/${API_SUFFIX}/${API_VERSION}`; +}; + +// Auth is sent as `Authorization: token ` (a Gitea-supported scheme), +// which keeps the token out of request URLs / logs. +const giteaHeaders = (cfg: GiteaConfig): Record => { + const headers: Record = { accept: 'application/json' }; + if (cfg.token) { + headers['Authorization'] = `token ${cfg.token}`; + } + return headers; +}; + +const parseLabelList = (raw: string | undefined): string[] => + (raw ?? '') + .split(',') + .map((l) => l.trim()) + .filter((l) => l.length > 0); + +// Gitea/Forgejo's two issue endpoints historically disagree on whether a +// `labels=a,b` query means AND or OR (see go-gitea/gitea#33509). We always +// filter labels client-side so behavior is consistent regardless of server. +const hasAllLabels = (issue: GiteaIssue, required: readonly string[]): boolean => { + if (required.length === 0) { + return true; + } + const names = new Set((issue.labels ?? []).map((l) => l.name)); + return required.every((name) => names.has(name)); +}; + +const isIssueIncludedByLabels = ( + issue: GiteaIssue, + excluded: readonly string[], +): boolean => { + if (excluded.length === 0) { + return true; + } + const names = new Set((issue.labels ?? []).map((l) => l.name)); + return !excluded.some((name) => names.has(name)); +}; + +const issueAssignees = (issue: GiteaIssue): string[] => + (issue.assignees ?? []) + .map((a) => a.login || a.username) + .filter((name): name is string => !!name); + +const mapSearchResult = (issue: GiteaIssue): PluginSearchResult => ({ + // Gitea tracks issues by their per-repo `number`, not the global `id`. + id: String(issue.number), + title: `#${issue.number} ${issue.title}`, + url: issue.html_url, + status: issue.state, + assignee: issue.assignee?.login || issue.assignee?.username, + labels: (issue.labels ?? []).map((l) => l.name), +}); + +PluginAPI.registerIssueProvider({ + configFields: [ + { + key: 'host', + type: 'input', + label: t('CFG.HOST'), + required: true, + }, + { + key: 'token', + type: 'password', + label: t('CFG.TOKEN'), + required: true, + }, + { + key: 'tokenHelp', + type: 'link', + label: t('CFG.HOW_TO_GET_TOKEN'), + url: 'https://docs.gitea.com/development/api-usage#generating-and-listing-api-tokens', + }, + { + key: 'repoFullname', + type: 'input', + label: t('CFG.REPO_FULL_NAME'), + required: true, + }, + { + key: 'scope', + type: 'select', + label: t('CFG.SCOPE'), + required: true, + options: [ + { value: 'all', label: t('CFG.SCOPE_ALL') }, + { value: SCOPE_CREATED_BY_ME, label: t('CFG.SCOPE_CREATED') }, + { value: SCOPE_ASSIGNED_TO_ME, label: t('CFG.SCOPE_ASSIGNED') }, + ], + }, + { + key: 'filterLabels', + type: 'input', + label: t('CFG.FILTER_LABELS'), + advanced: true, + }, + { + key: 'excludeLabels', + type: 'input', + label: t('CFG.EXCLUDE_LABELS'), + advanced: true, + }, + ], + + getHeaders(config: Record): Record { + return giteaHeaders(config as unknown as GiteaConfig); + }, + + async searchIssues( + searchTerm: string, + config: Record, + http: PluginHttp, + ): Promise { + const cfg = config as unknown as GiteaConfig; + const base = baseUrl(cfg); + const includedLabels = parseLabelList(cfg.filterLabels); + const excludedLabels = parseLabelList(cfg.excludeLabels); + + // `priority_repo_id` is the only reliable way to scope the global issue + // search to the configured repository, so look it up first. + const repo = await http.get( + `${base}/repos/${cfg.repoFullname}`, + ); + + const params: Record = { + limit: '100', + state: 'open', + q: searchTerm, + }; + if (repo?.id) { + params['priority_repo_id'] = String(repo.id); + } + if (cfg.scope === SCOPE_CREATED_BY_ME) { + params['created'] = 'true'; + } else if (cfg.scope === SCOPE_ASSIGNED_TO_ME) { + params['assigned'] = 'true'; + } + if (includedLabels.length > 0) { + params['labels'] = includedLabels.join(','); + } + + const issues = + (await http.get(`${base}/repos/issues/search`, { params })) || []; + return issues + .filter((issue) => issue.repository?.full_name === cfg.repoFullname) + .filter((issue) => hasAllLabels(issue, includedLabels)) + .filter((issue) => isIssueIncludedByLabels(issue, excludedLabels)) + .map(mapSearchResult); + }, + + async getById( + issueId: string, + config: Record, + http: PluginHttp, + ): Promise { + const cfg = config as unknown as GiteaConfig; + const base = baseUrl(cfg); + const issueUrl = `${base}/repos/${cfg.repoFullname}/issues/${issueId}`; + const issue = await http.get(issueUrl); + + const result: PluginIssue = { + id: String(issue.number), + title: issue.title, + body: issue.body || '', + url: issue.html_url, + state: issue.state, + lastUpdated: new Date(issue.updated_at).getTime(), + assignee: issue.assignee?.login || issue.assignee?.username, + labels: (issue.labels ?? []).map((l) => l.name), + comments: [], + + // Extended fields for richer display + number: issue.number, + summary: `#${issue.number} ${issue.title}`, + assignees: issueAssignees(issue), + creator: issue.user?.login || issue.user?.username, + creatorAvatarUrl: issue.user?.avatar_url, + createdAt: new Date(issue.created_at).getTime(), + closedAt: issue.closed_at ? new Date(issue.closed_at).getTime() : undefined, + }; + + if (issue.comments > 0) { + const commentsData = await http.get(`${issueUrl}/comments`); + result.comments = (commentsData || []).map((c) => ({ + author: c.user?.login || c.user?.username || 'unknown', + body: c.body || '', + created: new Date(c.created_at).getTime(), + avatarUrl: c.user?.avatar_url, + })); + } + + return result; + }, + + getIssueLink(issueId: string, config: Record): string { + const cfg = config as unknown as GiteaConfig; + const host = (cfg.host || '').replace(/\/+$/, ''); + return `${host}/${cfg.repoFullname}/issues/${issueId}`; + }, + + async testConnection( + config: Record, + http: PluginHttp, + ): Promise { + const cfg = config as unknown as GiteaConfig; + try { + await http.get(`${baseUrl(cfg)}/repos/${cfg.repoFullname}`); + return true; + } catch { + return false; + } + }, + + async getNewIssuesForBacklog( + config: Record, + http: PluginHttp, + ): Promise { + const cfg = config as unknown as GiteaConfig; + const base = baseUrl(cfg); + const includedLabels = parseLabelList(cfg.filterLabels); + const excludedLabels = parseLabelList(cfg.excludeLabels); + + const params: Record = { limit: '100', state: 'open' }; + if (cfg.scope === SCOPE_CREATED_BY_ME || cfg.scope === SCOPE_ASSIGNED_TO_ME) { + const user = await http.get(`${base}/user`); + if (cfg.scope === SCOPE_CREATED_BY_ME) { + params['created_by'] = user.username; + } else { + params['assigned_by'] = user.username; + } + } + if (includedLabels.length > 0) { + params['labels'] = includedLabels.join(','); + } + + const opts: PluginHttpOptions = { params }; + const issues = + (await http.get(`${base}/repos/${cfg.repoFullname}/issues`, opts)) || + []; + return issues + .filter((issue) => hasAllLabels(issue, includedLabels)) + .filter((issue) => isIssueIncludedByLabels(issue, excludedLabels)) + .map(mapSearchResult); + }, + + issueDisplay: [ + { field: 'summary', label: t('DISPLAY.SUMMARY'), type: 'link', linkField: 'url' }, + { field: 'state', label: t('DISPLAY.STATE'), type: 'text' }, + { field: 'assignees', label: t('DISPLAY.ASSIGNEE'), type: 'list', hideEmpty: true }, + { field: 'labels', label: t('DISPLAY.LABELS'), type: 'list', hideEmpty: true }, + { field: 'body', label: t('DISPLAY.DESCRIPTION'), type: 'markdown' }, + ], + + commentsConfig: { + authorField: 'author', + bodyField: 'body', + createdField: 'created', + avatarField: 'avatarUrl', + }, + + // Read-only provider: pull-only mappings drive remote-update detection only. + fieldMappings: [ + { + taskField: 'isDone', + issueField: 'state', + defaultDirection: 'pullOnly', + toIssueValue: (taskValue: unknown): string => (taskValue ? 'closed' : 'open'), + toTaskValue: (issueValue: unknown): boolean => issueValue === 'closed', + }, + ] satisfies PluginFieldMapping[], + + extractSyncValues(issue: PluginIssue): Record { + return { + state: issue.state, + title: issue.title, + body: issue.body, + }; + }, +}); diff --git a/packages/plugin-dev/gitea-issue-provider/tsconfig.json b/packages/plugin-dev/gitea-issue-provider/tsconfig.json new file mode 100644 index 0000000000..3c59f94bab --- /dev/null +++ b/packages/plugin-dev/gitea-issue-provider/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/plugin-dev/linear-issue-provider/i18n/en.json b/packages/plugin-dev/linear-issue-provider/i18n/en.json new file mode 100644 index 0000000000..793a06ac66 --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/i18n/en.json @@ -0,0 +1,17 @@ +{ + "CFG": { + "API_KEY": "API Key", + "TEAM_ID": "Team ID (optional, filter to a specific team)", + "PROJECT_ID": "Project ID (optional, filter to a specific project)", + "HOW_TO_GET_TOKEN": "Get your API key" + }, + "DISPLAY": { + "SUMMARY": "Summary", + "STATE": "Status", + "PRIORITY": "Priority", + "ASSIGNEE": "Assignee", + "LABELS": "Labels", + "DESCRIPTION": "Description" + }, + "FORM_HELP": "

Configure Super Productivity to list and import your assigned Linear issues in the task creation panel. They will be listed as suggestions with a link to the issue and more information about it.

" +} diff --git a/packages/plugin-dev/linear-issue-provider/icon.svg b/packages/plugin-dev/linear-issue-provider/icon.svg new file mode 100644 index 0000000000..613820a135 --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/icon.svg @@ -0,0 +1 @@ + diff --git a/packages/plugin-dev/linear-issue-provider/package-lock.json b/packages/plugin-dev/linear-issue-provider/package-lock.json new file mode 100644 index 0000000000..c6fbc2f8a8 --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/package-lock.json @@ -0,0 +1,532 @@ +{ + "name": "linear-issue-provider", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "linear-issue-provider", + "version": "1.0.0", + "dependencies": { + "@super-productivity/plugin-api": "file:../../plugin-api" + }, + "devDependencies": { + "esbuild": "^0.25.11", + "typescript": "^5.8.3" + } + }, + "../../plugin-api": { + "name": "@super-productivity/plugin-api", + "version": "1.0.1", + "license": "MIT", + "devDependencies": { + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@super-productivity/plugin-api": { + "resolved": "../../plugin-api", + "link": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/packages/plugin-dev/linear-issue-provider/package.json b/packages/plugin-dev/linear-issue-provider/package.json new file mode 100644 index 0000000000..1cd493b42e --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/package.json @@ -0,0 +1,17 @@ +{ + "name": "linear-issue-provider", + "version": "1.0.0", + "description": "Linear issue provider plugin", + "scripts": { + "build": "node scripts/build.js", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit" + }, + "devDependencies": { + "esbuild": "^0.25.11", + "typescript": "^5.8.3" + }, + "dependencies": { + "@super-productivity/plugin-api": "file:../../plugin-api" + } +} diff --git a/packages/plugin-dev/linear-issue-provider/scripts/build.js b/packages/plugin-dev/linear-issue-provider/scripts/build.js new file mode 100644 index 0000000000..a841a2754c --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/scripts/build.js @@ -0,0 +1,67 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { build } = require('esbuild'); + +const ROOT_DIR = path.join(__dirname, '..'); +const SRC_DIR = path.join(ROOT_DIR, 'src'); +const DIST_DIR = path.join(ROOT_DIR, 'dist'); +const I18N_SRC = path.join(ROOT_DIR, 'i18n'); +const I18N_DIST = path.join(DIST_DIR, 'i18n'); + +async function buildPlugin() { + console.log('Building linear-issue-provider...'); + + if (fs.existsSync(DIST_DIR)) { + fs.rmSync(DIST_DIR, { recursive: true }); + } + fs.mkdirSync(DIST_DIR); + + // Build TypeScript to IIFE bundle + await build({ + entryPoints: [path.join(SRC_DIR, 'plugin.ts')], + bundle: true, + outfile: path.join(DIST_DIR, 'plugin.js'), + platform: 'browser', + target: 'es2020', + format: 'iife', + define: { + 'process.env.NODE_ENV': '"production"', + }, + logLevel: 'info', + minify: true, + sourcemap: false, + }); + + // Copy manifest.json + fs.copyFileSync( + path.join(SRC_DIR, 'manifest.json'), + path.join(DIST_DIR, 'manifest.json'), + ); + + // Copy icon.svg if present + const iconSrc = path.join(ROOT_DIR, 'icon.svg'); + if (fs.existsSync(iconSrc)) { + fs.copyFileSync(iconSrc, path.join(DIST_DIR, 'icon.svg')); + console.log('Copied icon.svg'); + } + + // Copy i18n files + if (fs.existsSync(I18N_SRC)) { + fs.mkdirSync(I18N_DIST, { recursive: true }); + for (const file of fs.readdirSync(I18N_SRC)) { + if (file.endsWith('.json')) { + fs.copyFileSync(path.join(I18N_SRC, file), path.join(I18N_DIST, file)); + } + } + console.log('Copied i18n files'); + } + + console.log('Build complete!'); +} + +buildPlugin().catch((err) => { + console.error('Build failed:', err); + process.exit(1); +}); diff --git a/packages/plugin-dev/linear-issue-provider/src/manifest.json b/packages/plugin-dev/linear-issue-provider/src/manifest.json new file mode 100644 index 0000000000..39f53b3c58 --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/src/manifest.json @@ -0,0 +1,27 @@ +{ + "id": "linear-issue-provider", + "name": "Linear Issues", + "version": "1.0.0", + "manifestVersion": 1, + "minSupVersion": "13.0.0", + "description": "Linear issue provider plugin", + "author": "Super Productivity", + "type": "issueProvider", + "icon": "icon.svg", + "iFrame": false, + "permissions": [], + "hooks": [], + "i18n": { + "languages": ["en"] + }, + "issueProvider": { + "pollIntervalMs": 300000, + "icon": "linear", + "humanReadableName": "Linear", + "issueProviderKey": "LINEAR", + "issueStrings": { + "singular": "Issue", + "plural": "Issues" + } + } +} diff --git a/packages/plugin-dev/linear-issue-provider/src/plugin.ts b/packages/plugin-dev/linear-issue-provider/src/plugin.ts new file mode 100644 index 0000000000..02fb85d5cf --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/src/plugin.ts @@ -0,0 +1,319 @@ +import type { + IssueProviderPluginDefinition, + PluginFieldMapping, + PluginHttp, + PluginIssue, + PluginSearchResult, +} from '@super-productivity/plugin-api'; + +declare const PluginAPI: { + registerIssueProvider(definition: IssueProviderPluginDefinition): void; + translate(key: string, params?: Record): string; +}; + +const LINEAR_API_URL = 'https://api.linear.app/graphql'; + +// Linear workflow state types that mean "done" (matches the built-in provider). +const DONE_STATE_TYPES = ['completed', 'canceled']; + +interface LinearConfig { + apiKey?: string; + teamId?: string; + projectId?: string; +} + +interface LinearGraphQLResponse { + data?: T; + errors?: Array<{ message: string }>; +} + +interface LinearRawIssueReduced { + id: string; + identifier: string; + number: number; + title: string; + updatedAt: string; + url: string; + state: { name: string; type: string }; +} + +interface LinearRawIssue extends LinearRawIssueReduced { + description?: string; + priority: number; + createdAt: string; + completedAt?: string; + canceledAt?: string; + dueDate?: string; + assignee?: { id: string; name: string; avatarUrl?: string }; + creator?: { id: string; name: string }; + labels?: { nodes: Array<{ id: string; name: string; color: string }> }; + comments?: { + nodes: Array<{ + id: string; + body: string; + createdAt: string; + user?: { id: string; name: string; avatarUrl?: string }; + }>; + }; +} + +const t = (key: string): string => { + try { + return PluginAPI.translate(key); + } catch { + return key; + } +}; + +const SEARCH_ISSUES_QUERY = ` + query SearchIssues($first: Int!, $team: TeamFilter, $project: NullableProjectFilter) { + viewer { + assignedIssues( + first: $first, + filter: { + state: { type: { in: ["backlog", "unstarted", "started"] } }, + team: $team, + project: $project + } + ) { + nodes { + id identifier number title updatedAt url + state { id name type } + } + } + } + } +`; + +const GET_ISSUE_QUERY = ` + query GetIssue($id: String!) { + issue(id: $id) { + id identifier number title description priority + createdAt updatedAt completedAt canceledAt dueDate url + state { id name type } + team { id name key } + assignee { id name avatarUrl } + creator { id name } + labels(first: 50) { nodes { id name color } } + comments(first: 50) { + nodes { id body createdAt user { id name avatarUrl } } + } + } + } +`; + +const GET_VIEWER_QUERY = `query GetViewer { viewer { id name } }`; + +const graphql = async ( + http: PluginHttp, + query: string, + variables: Record, +): Promise => { + const res = await http.post>(LINEAR_API_URL, { + query, + variables, + }); + if (res?.errors?.length) { + throw new Error(res.errors[0].message || 'Linear GraphQL error'); + } + if (!res?.data) { + throw new Error('No data returned from Linear'); + } + return res.data; +}; + +const mapReduced = (issue: LinearRawIssueReduced): PluginSearchResult => ({ + id: issue.id, + title: `${issue.identifier} ${issue.title}`, + url: issue.url, + status: issue.state?.name, + // Provider-specific fields used for display + isDone mapping. + identifier: issue.identifier, + stateType: issue.state?.type, +}); + +const searchAssignedIssues = async ( + searchTerm: string, + cfg: LinearConfig, + http: PluginHttp, +): Promise => { + const variables: Record = { first: 50 }; + // Deliberate behavior change from the built-in provider: the old + // LinearApiService accepted teamId/projectId but its callers never passed + // them, so the "filter to specific team/project" config fields were inert. + // Here we honor them as the labels promise. Empty fields = no filter (the + // common case), so this only narrows results for users who set a value. + if (cfg.teamId) { + variables.team = { id: { eq: cfg.teamId } }; + } + if (cfg.projectId) { + variables.project = { id: { eq: cfg.projectId } }; + } + + const data = await graphql<{ + viewer: { assignedIssues: { nodes: LinearRawIssueReduced[] } }; + }>(http, SEARCH_ISSUES_QUERY, variables); + + let issues = data.viewer?.assignedIssues?.nodes || []; + const term = searchTerm.trim().toLowerCase(); + if (term) { + issues = issues.filter( + (issue) => + issue.title.toLowerCase().includes(term) || + issue.identifier.toLowerCase().includes(term), + ); + } + return issues.map(mapReduced); +}; + +PluginAPI.registerIssueProvider({ + configFields: [ + { + key: 'apiKey', + type: 'password', + label: t('CFG.API_KEY'), + required: true, + }, + { + key: 'apiKeyHelp', + type: 'link', + label: t('CFG.HOW_TO_GET_TOKEN'), + url: 'https://linear.app/settings/account/security', + }, + { + key: 'teamId', + type: 'input', + label: t('CFG.TEAM_ID'), + advanced: true, + }, + { + key: 'projectId', + type: 'input', + label: t('CFG.PROJECT_ID'), + advanced: true, + }, + ], + + getHeaders(config: Record): Record { + const cfg = config as unknown as LinearConfig; + return { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'Content-Type': 'application/json', + Authorization: cfg.apiKey || '', + }; + }, + + searchIssues( + searchTerm: string, + config: Record, + http: PluginHttp, + ): Promise { + return searchAssignedIssues(searchTerm, config as unknown as LinearConfig, http); + }, + + async getById( + issueId: string, + _config: Record, + http: PluginHttp, + ): Promise { + const data = await graphql<{ issue: LinearRawIssue | null }>(http, GET_ISSUE_QUERY, { + id: issueId, + }); + const issue = data.issue; + if (!issue) { + throw new Error('No issue data returned from Linear'); + } + + return { + id: issue.id, + title: issue.title, + body: issue.description || '', + url: issue.url, + state: issue.state?.name, + lastUpdated: new Date(issue.updatedAt).getTime(), + assignee: issue.assignee?.name, + labels: (issue.labels?.nodes || []).map((l) => l.name), + comments: (issue.comments?.nodes || []) + .filter((c) => !!c.user) + .map((c) => ({ + author: c.user!.name, + body: c.body || '', + created: new Date(c.createdAt).getTime(), + avatarUrl: c.user!.avatarUrl, + })), + + // Extended fields for display + isDone mapping. + identifier: issue.identifier, + number: issue.number, + summary: `${issue.identifier} ${issue.title}`, + stateType: issue.state?.type, + priority: issue.priority, + creator: issue.creator?.name, + createdAt: new Date(issue.createdAt).getTime(), + completedAt: issue.completedAt ? new Date(issue.completedAt).getTime() : undefined, + }; + }, + + // Linear issue URLs require the workspace slug, which can't be derived from the + // id + config alone. Returning '' makes the adapter fall back to getById().url, + // matching the built-in provider's behavior. + getIssueLink(): string { + return ''; + }, + + async testConnection( + _config: Record, + http: PluginHttp, + ): Promise { + try { + await graphql(http, GET_VIEWER_QUERY, {}); + return true; + } catch { + return false; + } + }, + + getNewIssuesForBacklog( + config: Record, + http: PluginHttp, + ): Promise { + return searchAssignedIssues('', config as unknown as LinearConfig, http); + }, + + issueDisplay: [ + { field: 'summary', label: t('DISPLAY.SUMMARY'), type: 'link', linkField: 'url' }, + { field: 'state', label: t('DISPLAY.STATE'), type: 'text', hideEmpty: true }, + { field: 'priority', label: t('DISPLAY.PRIORITY'), type: 'text', hideEmpty: true }, + { field: 'assignee', label: t('DISPLAY.ASSIGNEE'), type: 'text', hideEmpty: true }, + { field: 'labels', label: t('DISPLAY.LABELS'), type: 'list', hideEmpty: true }, + { field: 'body', label: t('DISPLAY.DESCRIPTION'), type: 'markdown' }, + ], + + commentsConfig: { + authorField: 'author', + bodyField: 'body', + createdField: 'created', + avatarField: 'avatarUrl', + }, + + // Read-only provider: pull-only mapping drives remote-update detection only. + fieldMappings: [ + { + taskField: 'isDone', + issueField: 'stateType', + defaultDirection: 'pullOnly', + toIssueValue: (taskValue: unknown): string => + taskValue ? 'completed' : 'unstarted', + toTaskValue: (issueValue: unknown): boolean => + DONE_STATE_TYPES.includes(issueValue as string), + }, + ] satisfies PluginFieldMapping[], + + extractSyncValues(issue: PluginIssue): Record { + return { + stateType: issue.stateType, + title: issue.title, + body: issue.body, + }; + }, +}); diff --git a/packages/plugin-dev/linear-issue-provider/tsconfig.json b/packages/plugin-dev/linear-issue-provider/tsconfig.json new file mode 100644 index 0000000000..3c59f94bab --- /dev/null +++ b/packages/plugin-dev/linear-issue-provider/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/src/app/features/issue-panel/issue-provider-setup-overview/issue-provider-setup-overview.component.html b/src/app/features/issue-panel/issue-provider-setup-overview/issue-provider-setup-overview.component.html index ce395605a1..baef3ddce2 100644 --- a/src/app/features/issue-panel/issue-provider-setup-overview/issue-provider-setup-overview.component.html +++ b/src/app/features/issue-panel/issue-provider-setup-overview/issue-provider-setup-overview.component.html @@ -105,22 +105,8 @@ Open Project - - +