iobroker.kanban 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +24 -13
- package/io-package.json +28 -2
- package/lib/cron.js +4 -4
- package/lib/scheduler.js +4 -1
- package/lib/server.js +3 -3
- package/lib/store.js +92 -33
- package/main.js +7 -5
- package/package.json +2 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ Kanban board adapter for ioBroker with its **own web server**, live sync, webhoo
|
|
|
13
13
|
|
|
14
14
|
**Full documentation:** [English](docs/en/README.md) · [Deutsch](docs/de/README.md)
|
|
15
15
|
|
|
16
|
+
**Questions, ideas, bug reports:** [GitHub issues](https://github.com/bmueller77/ioBroker.kanban/issues), or the ioBroker forum threads [Kanban board (English)](https://forum.iobroker.net/topic/85031/new-adapter-kanban-board) and [KanBan-Board (deutsch)](https://forum.iobroker.net/topic/85030/test-kanban-board).
|
|
17
|
+
|
|
16
18
|
## Installation
|
|
17
19
|
|
|
18
20
|
Install the adapter from the ioBroker admin: *Adapters* → filter for `kanban` → install. Then create an instance `kanban.0` and open the web UI at `http://<host>:8095/`.
|
|
@@ -122,16 +124,25 @@ Details: [Security & access control](docs/en/README.md#security--access-control)
|
|
|
122
124
|
|
|
123
125
|
<!-- Der Platzhalter bleibt stehen. release-script trägt hier die
|
|
124
126
|
nächste Version ein und ersetzt die Überschrift. -->
|
|
127
|
+
### 0.3.4 (2026-09-27)
|
|
128
|
+
* (bmueller77) The copyright line in `LICENSE` and the author entry in `io-package.json` carry an e-mail address now. The repository checker asks for one in both places, and it was only in `package.json`
|
|
129
|
+
|
|
130
|
+
### 0.3.3 (2026-09-21)
|
|
131
|
+
* (bmueller77) **Everything the adapter says is in English now.** Thirty-five error messages were German, from `title fehlt` to `Karte '…' existiert nicht`. They reach users in the log, as REST responses and as the answer to a `sendTo` call, so a German message left half the ioBroker world guessing. Cron messages are included
|
|
132
|
+
* (bmueller77) **Five state names were German** and are English now: `Card count`, `Overdue cards`, `Overdue cards (list)` and `Assigned open cards`. Since these objects are created only when they are missing, a new name never reached an existing installation. The adapter now corrects the names of its own objects once at startup, so boards that have been running for months are renamed as well
|
|
133
|
+
* (bmueller77) The HTTP status code of the REST API no longer depends on the wording of an error. It used to test the message for the German `existiert nicht` to decide between 404 and 400, which would have broken silently with the translation. An error that stands for something missing now carries its status itself
|
|
134
|
+
* (bmueller77) `reminderDaysBefore` is held to the range 0 to 30 in the code as well, not only in the settings dialog. The configuration can be edited by hand, and a value far outside that range moved the reminder threshold somewhere nobody would find
|
|
135
|
+
|
|
125
136
|
### 0.3.2 (2026-09-10)
|
|
126
137
|
* (bmueller77) **The trash has the sort toggle now**, like every other column. The default stays deletion time, oldest first, so whatever is closest to being removed for good sits on top; sorting by due date, priority or age is available all the same. The broom moved to the left of the toggle so that the sorting sits in the same place in every column
|
|
127
|
-
* (bmueller77) Fix: the **focus ring** took the accent colour unchanged. That works in the light theme; in the dark one the same dark tone stood against dark surfaces
|
|
128
|
-
* (bmueller77) Fix: below 600 px the card editor, the board manager and the views dialog
|
|
138
|
+
* (bmueller77) Fix: the **focus ring** took the accent colour unchanged. That works in the light theme; in the dark one the same dark tone stood against dark surfaces and was barely visible. The ring colour is computed now: lightened or darkened from the accent colour, only as far as it takes to stand out against every surface it lands on, so the instance keeps its colour family
|
|
139
|
+
* (bmueller77) Fix: below 600 px the card editor, the board manager and the views dialog did not fill the screen and sat pinned to the top left. Their rules hang on their IDs and beat the element rule in the mobile block
|
|
129
140
|
* (bmueller77) Fix: in the same block the tab panel was squashed as a flex child while its content visibly ran out of it. The scroll area did not count the overflowing pixels, and on a short window **Save scrolled out of sight entirely** - anyone who did not think to scroll back lost their input
|
|
130
|
-
* (bmueller77) Fix: the text colour on avatars and chips hung on a fixed brightness threshold
|
|
141
|
+
* (bmueller77) Fix: the text colour on avatars and chips hung on a fixed brightness threshold, and around that threshold the choice came out wrong - on turquoise it picked white where black is the more legible of the two. It now compares both and takes the better one
|
|
131
142
|
* (bmueller77) Fix: the **browser's back button** closed the card editor without a word, while Escape and the close cross ask first. The open dialog now adds a history entry; the back button takes it away and raises the same prompt
|
|
132
143
|
* (bmueller77) Fix: if someone changed the same card in another window, the open editor did not notice and **overwrote it silently on save**. It now compares the state it opened with against the current one and asks when they differ. Only the fields the editor edits are compared, so a column change or a fresh completion timestamp does not raise the prompt
|
|
133
|
-
* (bmueller77)
|
|
134
|
-
* (bmueller77) Fix: the sort menu carried no roles, the tooltip on a completed card was the only single-line one, a badge without a warning colour
|
|
144
|
+
* (bmueller77) The eye and the broom in the column header, the drag handles in the settings and the "+X more" row were too small to hit reliably, on a touchscreen in particular. All four are bigger now. It weighs more on the broom than elsewhere, since an irreversible action sits behind it
|
|
145
|
+
* (bmueller77) Fix: the sort menu carried no roles, the tooltip on a completed card was the only single-line one, a badge without a warning colour was hard to make out against the white card and now carries a hairline, the disabled menu entry kept the pointing hand, the triangle was lost on a coloured badge, the focus landed on `body` after toggling "+X more", and in drag-handle mode the whole card carried the grab cursor while dragging only starts at the handle
|
|
135
146
|
* (bmueller77) Fix: the explanatory line in the due-date tooltip told the timeline backwards. It runs from yellow to red now, in eleven languages
|
|
136
147
|
* (bmueller77) Fix: the heading above the label switch always read "hide labels", even when it was set to "show only these labels". Fix: the prompt for removing a board member counted the cards correctly but named every future non-member instead of the people actually on those cards
|
|
137
148
|
* (bmueller77) Fix: the subject "card deleted" stood for a move to the trash. The checkbox says "to the trash" and so does the confirmation dialog, only the mail did not. Corrected in eleven languages
|
|
@@ -139,21 +150,21 @@ Details: [Security & access control](docs/en/README.md#security--access-control)
|
|
|
139
150
|
* (bmueller77) **Only IDs that something hangs on are frozen now.** Until now the adapter wrote the marker back for every user on the first start, and every such write restarts the instance - exactly the window in which saving in the admin was lost, reproduced twice, without a message and without a log entry. It hit the setup, the moment when someone sets the port, saves, enters users, saves. A fresh instance with users but no cards no longer writes its configuration back at all
|
|
140
151
|
* (bmueller77) Fix: creating a card fired "card created" and "card assigned", and **both attached the `.ics`** although the manual says the invite comes once. The notifier remembers the dispatch itself now
|
|
141
152
|
* (bmueller77) **Deleting a card sends a cancellation**: an `.ics` with `METHOD:CANCEL`, the same `UID` and the next sequence number, so the calendar clears the entry instead of leaving the appointment standing. Only where an invite was ever sent
|
|
142
|
-
* (bmueller77) The **add button** at the foot of a column is narrower.
|
|
153
|
+
* (bmueller77) The **add button** at the foot of a column is narrower. It used to span a good quarter of the column and caught the clicks on the rows of an expanded checklist below it. A "+" does not need that width
|
|
143
154
|
* (bmueller77) **No cards are created in a done column any more.** A card created there carried no `doneAt` and therefore dropped out of three documented features: the "age in column" sort, the display limit for done cards and the automatic cleanup. It stayed there forever without anyone seeing why. The store refuses it and the UI does not offer it: the card editor leaves done columns out when creating and copying, the transfer dialog likewise, and there is no "+" at the foot of such a column. Moving and editing an existing card keeps the column selectable
|
|
144
155
|
* (bmueller77) Fix: `doneAt` was cleared when a card moved to the trash, so restoring it lost the completion time. Fix: labels created through the interface were all green and now take their colour in turn from the same palette as the UI. Fix: the mirror states of deleted users stayed behind under `users.*`, so a dashboard reading `assignedCount` kept counting people too many. Fix: the "+" at the foot of a column was not blocked when no user existed - only the header button had been secured, the dead end was still reachable by the other route
|
|
145
156
|
* (bmueller77) **Error messages no longer arrive as a browser box.** All eight `alert()` calls go through the in-page hint now: a red border, `role=alert`, and they stay until someone closes them. A modal box halts the page until it is clicked away and is unreadable to any automated check - two testers could only report that the renderer refused scripts and had to leave the wording open
|
|
146
|
-
* (bmueller77) Fix: on a board without columns "+ Card" stayed active, the editor opened, the column field was empty, and only saving ran into the error. Fix: the menus in the column header hang off `body`, so a deleted column left its menu standing and operable. Fix: with fifteen users the chip bar
|
|
157
|
+
* (bmueller77) Fix: on a board without columns "+ Card" stayed active, the editor opened, the column field was empty, and only saving ran into the error. Fix: the menus in the column header hang off `body`, so a deleted column left its menu standing and operable. Fix: with fifteen users the chip bar grew wider than the window and pushed the whole page out of view; it wraps now. Fix: markdown tables in the description and the reading window had neither borders nor padding
|
|
147
158
|
* (bmueller77) The socket around the sticky add button is one pixel wide instead of seven. It only shows when the button floats over a card, and then as a heavy frame
|
|
148
159
|
* (bmueller77) Fix: a saved card that the active filter hides straight away **disappeared without a word**. A tester created six cards and thought saving was broken until the network response 201 told him otherwise. After saving, the board is checked for the card and the UI says so when it is not there
|
|
149
160
|
* (bmueller77) Fix: from the sixth column on, "+ Add column" sat on the edge of the scroll area, half cut off and without effect, and the input that followed was lost. Fix: the recurrence prefilled day and month with today's date even when the card carried a due date. Fix: the remembered expanded state silently overruled a freshly set display limit, so "Max 3" showed seven cards and looked broken. Fix: the board picker still showed the old name after renaming
|
|
150
|
-
* (bmueller77) Fix: the "duration" field took the whole width next to the checkbox,
|
|
161
|
+
* (bmueller77) Fix: the "duration" field took the whole width next to the checkbox, for a value like `01:00`. Fix: the summary of a collapsed section showed raw markdown for the description and only the host for the link, so two cards with different files on the same server looked identical. Fix: the label summary carried no colour dots although the manual describes them. Fix: the interface error texts wrote "zustaendige" without the umlaut
|
|
151
162
|
* (bmueller77) Fix: with the WIP limit exceeded and a filter active, the column header read "2/5" in red - the filtered figure before the slash, the colour from the unfiltered one. The column's own figure stands there now, and the tooltip still names how many the filter shows. The rule sat in the middle of the rendering and had been wrong twice for that reason; it lives in `totalLabel()` with five tests beside it
|
|
152
163
|
* (bmueller77) Fix: the plural form still read "card(s)", "tarjeta(s)" in the prompt for removing a board member. Written out in all eleven languages now. Fix: "+ New" in the card editor always assigned green - the palette had only reached the board manager
|
|
153
164
|
* (bmueller77) **A fresh instance no longer ships with two example users.** Since the adapter freezes user IDs, the presets were permanently called "user1" and "user2" and could only be deleted, not renamed, so the first thing to do on a new instance was to throw away two users whose names must not be touched. "+ Card" explains by itself that a user is missing first
|
|
154
165
|
* (bmueller77) Fix: a recurring card left lying for several intervals produced a follow-up card **dated in the past**. Due 28.08., interval 7, completed 07.09. gave 04.09. The calculation now starts from the later of due date and today. Finishing early keeps the calendar as the reference, otherwise the grid would creep forward with every early tick
|
|
155
166
|
* (bmueller77) Fix: without a single user the card editor opened and saving failed on a condition nobody could meet there. Fix: the reason on the blocked "+ Card" never appeared, because Chrome delivers no mouse events on disabled buttons. It uses `aria-disabled` now, so the click is caught and says what is missing
|
|
156
|
-
* (bmueller77) Fix: the column count and the sort button in the column header
|
|
167
|
+
* (bmueller77) Fix: the column count and the sort button in the column header were too small to hit reliably and are bigger now. Fix: "+X more" hung on the column while the add button hung in the card list, so the hint sat below the add button, set off from the cards and not lined up with them
|
|
157
168
|
* (bmueller77) Fix: `info.orphanedAssignees` was only written on start, so a repair or a restore from the trash left the state stale until the next start. Fix: `info.frozenUserIds` only ever grew, so a user created later with the same ID would have found the field locked from the outset. Fix: `info.apiSecret` was created on fresh instances after all, although the manual has said the opposite since 0.3.0
|
|
158
169
|
* (bmueller77) Fix: "setInterval called, but adapter is shutting down" did not come from a normal restart but from saving the settings: the host stops the instance mid-start while the start is still creating timers. Server, scheduler and the delayed write ask first now
|
|
159
170
|
* (bmueller77) Fix: "2 Karte(n)" in the prompt for removing a board member. Fix: the tooltip on the due date named only the state; the scale stands below it now. Fix: after "+ Add column" and "+ Add label" the focus stayed on the link. Fix: the links in the rendered markdown preview sat in the tab order although it is display only
|
|
@@ -177,12 +188,12 @@ Details: [Security & access control](docs/en/README.md#security--access-control)
|
|
|
177
188
|
* (bmueller77) Fix: an unusable date format was accepted without checking and then printed on every card verbatim. A format without a day, month or year token is discarded with a warning in the log
|
|
178
189
|
* (bmueller77) Fix: Enter inside the checklist saved the whole card and closed the dialog. It creates the next item now; Ctrl and Enter saves from there
|
|
179
190
|
* (bmueller77) Fix: selection and keyboard focus looked identical on the chips, both a two-pixel ring in the accent colour. The focus ring is dashed now
|
|
180
|
-
* (bmueller77) Fix: white on the orange due badge
|
|
191
|
+
* (bmueller77) Fix: white on the orange due badge was hard to read, so the orange is darker now. Red as a text colour on a dark surface got a lighter tone through `--danger-fg`
|
|
181
192
|
* (bmueller77) Fix: the sticky add-card button sat on top of card titles and labels while scrolling. It has an opaque pad in the column colour now
|
|
182
|
-
* (bmueller77) Fix: below 600 px the board row in the settings stretched the board picker and the name field
|
|
183
|
-
* (bmueller77) The due-date badge has a tooltip naming its state, since a colour on its own does not say whether it is worse than the one beside it. New labels take their colour from a palette in rotation. The pencil and the other card icons
|
|
193
|
+
* (bmueller77) Fix: below 600 px the board row in the settings stretched the board picker and the name field far beyond their content, because the column direction turned a `flex-basis` meant as width into a height
|
|
194
|
+
* (bmueller77) The due-date badge has a tooltip naming its state, since a colour on its own does not say whether it is worse than the one beside it. New labels take their colour from a palette in rotation. The pencil and the other card icons are easier to hit. "+ Card" is disabled while no board exists, and the filter button that has been dead since 0.3.0 is gone
|
|
184
195
|
* (bmueller77) **With exactly one user, assignment disappears** (#35). No chips in the header, no avatars on the cards, no assignees field in the editor, no user picker in the views dialog: with one person there is nothing to choose and nothing to filter. The API fills the field in instead of answering `400`, so a script does not fail on a question that answers itself. A card carrying an ID that no longer exists keeps it rather than being silently moved. Adding a second user brings all of it back
|
|
185
|
-
* (bmueller77) The copy button on a done card is `mdi:content-copy` now, two offset sheets (#33). It used to be a sheet with a circular arrow, which at
|
|
196
|
+
* (bmueller77) The copy button on a done card is `mdi:content-copy` now, two offset sheets (#33). It used to be a sheet with a circular arrow, which at that size looked like the recurrence icon sitting on the same card, so people read the copy button as a recurrence marker that had moved. The recurrence icon itself never moved. The documentation gained a table listing every icon that can appear on a card
|
|
186
197
|
* (bmueller77) Labels keep one order everywhere (#32). A card used to list them in the order they had been clicked in the editor, so two cards carrying the same labels looked different. Card, picker and section summary now all follow the order of the board, and that order can be dragged by a handle in the board settings, the same way the columns already worked
|
|
187
198
|
* (bmueller77) Fix: `overdueCount` and `overdueList` ignored the time of day (#31). A card due today at 09:00 turned red in the board at 09:01, while the states kept their old value until midnight, because the counters compared dates only. One function now decides it for the states, the `cardDue` event and the colour alike
|
|
188
199
|
* (bmueller77) **The column header can show more than the card count.** Clicking the number opens a menu with four checkmarks: total, tomorrow, today, overdue. Each ticked entry gets its own badge next to the others, in the same colours the due-date badges use on the cards and following the same arithmetic, lead time and time of day included. The choice is stored per column and per device, like the sort mode. Done columns and the trash have no menu, since every card there counts as completed
|
package/io-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "kanban",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"titleLang": {
|
|
6
6
|
"en": "Kanban Board",
|
|
7
7
|
"de": "Kanban Board",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"zh-cn": "带有独立 Web 服务器、Webhook、多用户支持和电子邮件通知的看板"
|
|
44
44
|
},
|
|
45
45
|
"authors": [
|
|
46
|
-
"Björn Müller"
|
|
46
|
+
"Björn Müller <bjoern@mueller.family>"
|
|
47
47
|
],
|
|
48
48
|
"platform": "Javascript/Node.js",
|
|
49
49
|
"mode": "daemon",
|
|
@@ -74,6 +74,32 @@
|
|
|
74
74
|
"license": "MIT"
|
|
75
75
|
},
|
|
76
76
|
"news": {
|
|
77
|
+
"0.3.4": {
|
|
78
|
+
"de": "- Copyright-Zeile in LICENSE und Autoreneintrag in io-package.json tragen jetzt eine E-Mail-Adresse",
|
|
79
|
+
"en": "- The copyright line in LICENSE and the author entry in io-package.json now carry an e-mail address",
|
|
80
|
+
"ru": "- Строка авторского права в LICENSE и запись автора в io-package.json теперь содержат адрес электронной почты",
|
|
81
|
+
"pt": "- A linha de direitos de autor em LICENSE e a entrada do autor em io-package.json incluem agora um endereço de e-mail",
|
|
82
|
+
"nl": "- De copyrightregel in LICENSE en de auteursvermelding in io-package.json bevatten nu een e-mailadres",
|
|
83
|
+
"fr": "- La ligne de copyright dans LICENSE et la mention d'auteur dans io-package.json comportent désormais une adresse e-mail",
|
|
84
|
+
"it": "- La riga di copyright in LICENSE e la voce autore in io-package.json contengono ora un indirizzo e-mail",
|
|
85
|
+
"es": "- La línea de copyright en LICENSE y la entrada de autor en io-package.json incluyen ahora una dirección de correo electrónico",
|
|
86
|
+
"pl": "- Wiersz praw autorskich w LICENSE oraz wpis autora w io-package.json zawierają teraz adres e-mail",
|
|
87
|
+
"uk": "- Рядок авторського права в LICENSE та запис автора в io-package.json тепер містять адресу електронної пошти",
|
|
88
|
+
"zh-cn": "- LICENSE 中的版权行和 io-package.json 中的作者条目现在都带有电子邮件地址"
|
|
89
|
+
},
|
|
90
|
+
"0.3.3": {
|
|
91
|
+
"de": "- Alle Meldungen des Adapters sind jetzt auf Englisch\n- Fünf Zustandsnamen waren deutsch und heißen jetzt \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" und \"Assigned open cards\"; bestehende Installationen werden beim Start umbenannt\n- Der HTTP-Statuscode der REST-Schnittstelle hängt nicht mehr am Wortlaut der Fehlermeldung\n- \"reminderDaysBefore\" wird auch im Code auf 0 bis 30 begrenzt",
|
|
92
|
+
"en": "- Every message the adapter produces is in English now\n- Five state names were German and are now \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" and \"Assigned open cards\"; existing installations are renamed at startup\n- The HTTP status code of the REST API no longer depends on the wording of the error message\n- \"reminderDaysBefore\" is held to the range 0 to 30 in the code as well",
|
|
93
|
+
"ru": "- Все сообщения адаптера теперь на английском языке\n- Пять имён состояний были на немецком, теперь это \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" и \"Assigned open cards\"; существующие установки переименовываются при запуске\n- Код состояния HTTP в REST-интерфейсе больше не зависит от формулировки сообщения об ошибке\n- \"reminderDaysBefore\" ограничивается диапазоном от 0 до 30 и в коде",
|
|
94
|
+
"pt": "- Todas as mensagens do adaptador estão agora em inglês\n- Cinco nomes de estados estavam em alemão e são agora \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" e \"Assigned open cards\"; as instalações existentes são renomeadas no arranque\n- O código de estado HTTP da API REST já não depende da redação da mensagem de erro\n- \"reminderDaysBefore\" é limitado ao intervalo de 0 a 30 também no código",
|
|
95
|
+
"nl": "- Alle meldingen van de adapter zijn nu in het Engels\n- Vijf toestandsnamen waren Duits en heten nu \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" en \"Assigned open cards\"; bestaande installaties worden bij het starten hernoemd\n- De HTTP-statuscode van de REST-API hangt niet meer af van de formulering van de foutmelding\n- \"reminderDaysBefore\" wordt ook in de code begrensd op 0 tot 30",
|
|
96
|
+
"fr": "- Tous les messages de l'adaptateur sont désormais en anglais\n- Cinq noms d'états étaient en allemand et s'appellent maintenant \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" et \"Assigned open cards\" ; les installations existantes sont renommées au démarrage\n- Le code de statut HTTP de l'API REST ne dépend plus de la formulation du message d'erreur\n- \"reminderDaysBefore\" est limité à la plage de 0 à 30 également dans le code",
|
|
97
|
+
"it": "- Tutti i messaggi dell'adattatore sono ora in inglese\n- Cinque nomi di stato erano in tedesco e ora sono \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" e \"Assigned open cards\"; le installazioni esistenti vengono rinominate all'avvio\n- Il codice di stato HTTP dell'API REST non dipende più dal testo del messaggio di errore\n- \"reminderDaysBefore\" è limitato all'intervallo da 0 a 30 anche nel codice",
|
|
98
|
+
"es": "- Todos los mensajes del adaptador están ahora en inglés\n- Cinco nombres de estados estaban en alemán y ahora son \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" y \"Assigned open cards\"; las instalaciones existentes se renombran al iniciar\n- El código de estado HTTP de la API REST ya no depende de la redacción del mensaje de error\n- \"reminderDaysBefore\" se limita al rango de 0 a 30 también en el código",
|
|
99
|
+
"pl": "- Wszystkie komunikaty adaptera są teraz w języku angielskim\n- Pięć nazw stanów było po niemiecku, teraz to \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" i \"Assigned open cards\"; istniejące instalacje są zmieniane przy starcie\n- Kod stanu HTTP interfejsu REST nie zależy już od treści komunikatu o błędzie\n- \"reminderDaysBefore\" jest ograniczany do zakresu od 0 do 30 również w kodzie",
|
|
100
|
+
"uk": "- Усі повідомлення адаптера тепер англійською мовою\n- П'ять імен станів були німецькими, тепер це \"Card count\", \"Overdue cards\", \"Overdue cards (list)\" та \"Assigned open cards\"; наявні встановлення перейменовуються під час запуску\n- Код стану HTTP інтерфейсу REST більше не залежить від формулювання повідомлення про помилку\n- \"reminderDaysBefore\" обмежується діапазоном від 0 до 30 і в коді",
|
|
101
|
+
"zh-cn": "- 适配器的所有消息现在均为英文\n- 五个状态名称原为德语,现为 \"Card count\"、\"Overdue cards\"、\"Overdue cards (list)\" 和 \"Assigned open cards\";现有安装在启动时会被重命名\n- REST 接口的 HTTP 状态码不再取决于错误消息的措辞\n- \"reminderDaysBefore\" 在代码中同样被限制在 0 到 30 之间"
|
|
102
|
+
},
|
|
77
103
|
"0.3.2": {
|
|
78
104
|
"de": "- Zahlen im Spaltenkopf umschaltbar: Gesamt, Morgen, Heute, Ueberfaellig\n- Aufklappbare Abschnitte im Karteneditor mit Zusammenfassung in der Kopfzeile\n- Benutzer-IDs werden gesperrt, sobald eine Karte oder ein Avatarbild daran haengt; verwaiste Zustaendige lassen sich in der Oberflaeche reparieren\n- Neue Wiederholungsart \"Alle X Tage (nach Erledigung des Vorgaengers)\"\n- Gelb heisst jetzt genau der naechste Kalendertag; die Vorlaufzeit steuert nur noch die Erinnerungsmail\n- In Erledigt-Spalten entstehen keine Karten mehr\n- Beim Loeschen einer Karte mit Einladung geht eine Kalender-Absage raus\n- Der Papierkorb hat denselben Sortier-Umschalter wie jede andere Spalte\n- Frische Instanzen bringen keine Beispielbenutzer mehr mit\n- Dazu rund siebzig Korrekturen aus acht Testpaketen",
|
|
79
105
|
"en": "- Switchable counts in the column header: total, tomorrow, today, overdue\n- Collapsible sections in the card editor, each summarised in its header\n- User IDs are frozen once a card or an avatar hangs on them; orphaned assignees can be repaired in the UI\n- New recurrence kind \"every X days (after the previous one is done)\"\n- Yellow now means exactly the next calendar day; the lead time only drives the reminder mail\n- No cards are created in done columns any more\n- Deleting a card with an invite sends a calendar cancellation\n- The trash has the same sort toggle as every other column\n- Fresh instances no longer ship with example users\n- Plus around seventy fixes from eight test packages",
|
package/lib/cron.js
CHANGED
|
@@ -76,7 +76,7 @@ function parseField(raw, field) {
|
|
|
76
76
|
if (stepPart !== undefined) {
|
|
77
77
|
step = Number(stepPart);
|
|
78
78
|
if (!Number.isInteger(step) || step < 1) {
|
|
79
|
-
throw new Error(`${field.name}:
|
|
79
|
+
throw new Error(`${field.name}: invalid step '${stepPart}'`);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -104,10 +104,10 @@ function parseField(raw, field) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
if (!Number.isInteger(from) || !Number.isInteger(to)) {
|
|
107
|
-
throw new Error(`${field.name}: '${piece}'
|
|
107
|
+
throw new Error(`${field.name}: '${piece}' is not a valid entry`);
|
|
108
108
|
}
|
|
109
109
|
if (from < field.min || to > field.max || from > to) {
|
|
110
|
-
throw new Error(`${field.name}: '${piece}'
|
|
110
|
+
throw new Error(`${field.name}: '${piece}' is outside ${field.min}-${field.max}`);
|
|
111
111
|
}
|
|
112
112
|
for (let v = from; v <= to; v += step) {
|
|
113
113
|
out.add(v);
|
|
@@ -137,7 +137,7 @@ function parseCron(expr) {
|
|
|
137
137
|
// Ausdruck ueber die token-freie Pruefroute den Event-Loop fuer Sekunden.
|
|
138
138
|
// Das laengste sinnvolle Muster (alle Monatsnamen) bleibt weit darunter.
|
|
139
139
|
if (raw.length > MAX_EXPR_LENGTH) {
|
|
140
|
-
throw new Error(`
|
|
140
|
+
throw new Error(`expression too long (at most ${MAX_EXPR_LENGTH} characters)`);
|
|
141
141
|
}
|
|
142
142
|
const parts = raw.split(/\s+/);
|
|
143
143
|
if (parts.length !== 5) {
|
package/lib/scheduler.js
CHANGED
|
@@ -146,7 +146,10 @@ class Scheduler {
|
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
|
|
149
|
+
// Die Grenze 0..30 steht in der jsonConfig, aber die Konfiguration laesst
|
|
150
|
+
// sich von Hand aendern. Ein absurder Wert verschoebe die Schwelle ins
|
|
151
|
+
// Nirgendwo, und niemand saehe den Grund.
|
|
152
|
+
const daysBefore = Math.min(Math.max(Math.floor(Number(cfg.reminderDaysBefore) || 0), 0), 30);
|
|
150
153
|
const threshold = new Date();
|
|
151
154
|
threshold.setDate(threshold.getDate() + daysBefore);
|
|
152
155
|
const thresholdStr = `${threshold.getFullYear()}-${String(threshold.getMonth() + 1).padStart(2, '0')}-${String(threshold.getDate()).padStart(2, '0')}`;
|
package/lib/server.js
CHANGED
|
@@ -342,7 +342,7 @@ class Server {
|
|
|
342
342
|
_apiRoutes(app) {
|
|
343
343
|
const wrap = fn => (req, res) => {
|
|
344
344
|
Promise.resolve(fn(req, res)).catch(e => {
|
|
345
|
-
const code =
|
|
345
|
+
const code = Number(e.status) || 400;
|
|
346
346
|
res.status(code).json({ error: e.message });
|
|
347
347
|
});
|
|
348
348
|
};
|
|
@@ -359,7 +359,7 @@ class Server {
|
|
|
359
359
|
timeFormat: cfg.timeFormat || '24h',
|
|
360
360
|
// Die Oberflaeche faerbt das Vorwarnfenster damit ein, damit
|
|
361
361
|
// Farbe und Erinnerungsmail dieselbe Aussage treffen
|
|
362
|
-
reminderDaysBefore: Number(cfg.reminderDaysBefore) || 0,
|
|
362
|
+
reminderDaysBefore: Math.min(Math.max(Math.floor(Number(cfg.reminderDaysBefore) || 0), 0), 30),
|
|
363
363
|
language: this.adapter._language || 'en',
|
|
364
364
|
trashRetentionDays: TRASH_RETENTION_DAYS,
|
|
365
365
|
});
|
|
@@ -770,7 +770,7 @@ class Server {
|
|
|
770
770
|
|
|
771
771
|
const wrap = fn => (req, res) => {
|
|
772
772
|
Promise.resolve(fn(req, res)).catch(e => {
|
|
773
|
-
const code =
|
|
773
|
+
const code = Number(e.status) || 400;
|
|
774
774
|
res.status(code).json({ error: e.message });
|
|
775
775
|
});
|
|
776
776
|
};
|
package/lib/store.js
CHANGED
|
@@ -355,7 +355,7 @@ function assertDate(v, field) {
|
|
|
355
355
|
}
|
|
356
356
|
const s = String(v);
|
|
357
357
|
if (!DATE_RE.test(s)) {
|
|
358
|
-
throw new Error(`${field}
|
|
358
|
+
throw new Error(`${field} must be in YYYY-MM-DD format, not '${s}'`);
|
|
359
359
|
}
|
|
360
360
|
return s;
|
|
361
361
|
}
|
|
@@ -365,7 +365,7 @@ function assertTime(v, field) {
|
|
|
365
365
|
}
|
|
366
366
|
const s = String(v);
|
|
367
367
|
if (!TIME_RE.test(s)) {
|
|
368
|
-
throw new Error(`${field}
|
|
368
|
+
throw new Error(`${field} must be in HH:MM format, not '${s}'`);
|
|
369
369
|
}
|
|
370
370
|
return s;
|
|
371
371
|
}
|
|
@@ -385,17 +385,32 @@ function assertRecurrence(rec) {
|
|
|
385
385
|
}
|
|
386
386
|
const check = validateCron(rec.cron);
|
|
387
387
|
if (!check.ok) {
|
|
388
|
-
throw new Error(`
|
|
388
|
+
throw new Error(`Invalid cron expression: ${check.error}`);
|
|
389
389
|
}
|
|
390
390
|
return check.time;
|
|
391
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Fehler mit HTTP-Statuscode. Der REST-Aufsatz hat den Code bisher aus dem Text
|
|
394
|
+
* der Meldung geraten (/existiert nicht/). Das haette die Uebersetzung ins
|
|
395
|
+
* Englische stillschweigend gebrochen und war ohnehin die falsche Ebene: Ob
|
|
396
|
+
* etwas nicht existiert, weiss die Datenhaltung, nicht der Aufsatz.
|
|
397
|
+
*
|
|
398
|
+
* @param message Meldung in englischer Sprache
|
|
399
|
+
* @returns Error mit gesetztem status 404
|
|
400
|
+
*/
|
|
401
|
+
function notFound(message) {
|
|
402
|
+
const e = new Error(message);
|
|
403
|
+
e.status = 404;
|
|
404
|
+
return e;
|
|
405
|
+
}
|
|
406
|
+
|
|
392
407
|
function assertPriority(v) {
|
|
393
408
|
if (v === undefined || v === null || v === '') {
|
|
394
409
|
return 0;
|
|
395
410
|
}
|
|
396
411
|
const n = Number(v);
|
|
397
412
|
if (!Number.isInteger(n) || n < 0 || n > 2) {
|
|
398
|
-
throw new Error(`priority
|
|
413
|
+
throw new Error(`priority accepts only 0, 1 or 2, not '${v}'`);
|
|
399
414
|
}
|
|
400
415
|
return n;
|
|
401
416
|
}
|
|
@@ -553,7 +568,7 @@ class Store {
|
|
|
553
568
|
_requireBoard(id) {
|
|
554
569
|
const b = this.boards.get(id);
|
|
555
570
|
if (!b) {
|
|
556
|
-
throw
|
|
571
|
+
throw notFound(`board '${id}' does not exist`);
|
|
557
572
|
}
|
|
558
573
|
return b;
|
|
559
574
|
}
|
|
@@ -588,7 +603,7 @@ class Store {
|
|
|
588
603
|
|
|
589
604
|
async createBoard({ id, title }) {
|
|
590
605
|
if (!title) {
|
|
591
|
-
throw new Error('title
|
|
606
|
+
throw new Error('title is missing');
|
|
592
607
|
}
|
|
593
608
|
let boardId = slugify(id || title);
|
|
594
609
|
let n = 2;
|
|
@@ -733,17 +748,17 @@ class Store {
|
|
|
733
748
|
addCard(boardId, data, source) {
|
|
734
749
|
const board = this._requireBoard(boardId);
|
|
735
750
|
if (!data || !data.title) {
|
|
736
|
-
throw new Error('title
|
|
751
|
+
throw new Error('title is missing');
|
|
737
752
|
}
|
|
738
753
|
const firstNormal = board.columns.find(c => !c.isTrash);
|
|
739
754
|
// Eine angegebene, aber unbekannte Spalte ist ein Fehler des Aufrufers — die
|
|
740
755
|
// Karte still in der ersten Spalte abzulegen verschleiert das nur.
|
|
741
756
|
if (data.columnId && !board.columns.some(c => c.id === data.columnId && !c.isTrash)) {
|
|
742
|
-
throw
|
|
757
|
+
throw notFound(`column '${data.columnId}' does not exist in board '${boardId}'`);
|
|
743
758
|
}
|
|
744
759
|
const columnId = data.columnId || (firstNormal && firstNormal.id);
|
|
745
760
|
if (!columnId) {
|
|
746
|
-
throw new Error('
|
|
761
|
+
throw new Error('board has no columns');
|
|
747
762
|
}
|
|
748
763
|
// In einer Erledigt-Spalte wird nichts angelegt. Karten wandern dorthin,
|
|
749
764
|
// sie entstehen dort nicht: Was dort neu auftauchte, trug kein doneAt,
|
|
@@ -752,8 +767,8 @@ class Store {
|
|
|
752
767
|
// immer liegen, ohne dass jemand den Grund sah (D2, E12).
|
|
753
768
|
if (this._isDoneColumn(board, columnId)) {
|
|
754
769
|
throw new Error(
|
|
755
|
-
'
|
|
756
|
-
'
|
|
770
|
+
'cards are not created in a done column. ' +
|
|
771
|
+
'Create the card in an open column and move it there afterwards.',
|
|
757
772
|
);
|
|
758
773
|
}
|
|
759
774
|
const due = assertDate(data.due, 'due');
|
|
@@ -812,7 +827,7 @@ class Store {
|
|
|
812
827
|
const board = this._requireBoard(boardId);
|
|
813
828
|
const card = board.cards.find(c => c.id === cardId);
|
|
814
829
|
if (!card) {
|
|
815
|
-
throw
|
|
830
|
+
throw notFound(`card '${cardId}' does not exist`);
|
|
816
831
|
}
|
|
817
832
|
const oldAssignees = new Set(card.assignees);
|
|
818
833
|
const oldDueAt = `${card.due || ''} ${card.dueTime || ''}`;
|
|
@@ -878,10 +893,10 @@ class Store {
|
|
|
878
893
|
const board = this._requireBoard(boardId);
|
|
879
894
|
const card = board.cards.find(c => c.id === cardId);
|
|
880
895
|
if (!card) {
|
|
881
|
-
throw
|
|
896
|
+
throw notFound(`card '${cardId}' does not exist`);
|
|
882
897
|
}
|
|
883
898
|
if (!board.columns.some(c => c.id === columnId)) {
|
|
884
|
-
throw
|
|
899
|
+
throw notFound(`column '${columnId}' does not exist`);
|
|
885
900
|
}
|
|
886
901
|
const fromColumn = card.columnId;
|
|
887
902
|
const wasDone = this._isDoneColumn(board, fromColumn);
|
|
@@ -1108,7 +1123,7 @@ class Store {
|
|
|
1108
1123
|
const board = this._requireBoard(boardId);
|
|
1109
1124
|
const card = board.cards.find(c => c.id === cardId);
|
|
1110
1125
|
if (!card) {
|
|
1111
|
-
throw
|
|
1126
|
+
throw notFound(`card '${cardId}' does not exist`);
|
|
1112
1127
|
}
|
|
1113
1128
|
const trash = this._ensureTrashColumn(board);
|
|
1114
1129
|
const fromColumn = card.columnId;
|
|
@@ -1151,13 +1166,13 @@ class Store {
|
|
|
1151
1166
|
}
|
|
1152
1167
|
const idx = board.cards.findIndex(c => c.id === cardId);
|
|
1153
1168
|
if (idx === -1) {
|
|
1154
|
-
throw
|
|
1169
|
+
throw notFound(`card '${cardId}' does not exist`);
|
|
1155
1170
|
}
|
|
1156
1171
|
// Nur aus dem Papierkorb: Sonst entfernt ein einzelner Aufruf eine aktive
|
|
1157
1172
|
// Karte unwiederbringlich und umgeht die Aufbewahrungsfrist. Der Aufraeumlauf
|
|
1158
1173
|
// und "Papierkorb leeren" gehen ohnehin ueber eigene Wege.
|
|
1159
1174
|
if (!opts.auto && !this._isTrashColumn(board, board.cards[idx].columnId)) {
|
|
1160
|
-
throw new Error(`
|
|
1175
|
+
throw new Error(`card '${cardId}' is not in the trash`);
|
|
1161
1176
|
}
|
|
1162
1177
|
const [card] = board.cards.splice(idx, 1);
|
|
1163
1178
|
const col = card.columnId;
|
|
@@ -1184,7 +1199,7 @@ class Store {
|
|
|
1184
1199
|
const board = this._requireBoard(boardId);
|
|
1185
1200
|
const card = board.cards.find(c => c.id === cardId);
|
|
1186
1201
|
if (!card) {
|
|
1187
|
-
throw
|
|
1202
|
+
throw notFound(`card '${cardId}' does not exist`);
|
|
1188
1203
|
}
|
|
1189
1204
|
if (!this._isTrashColumn(board, card.columnId)) {
|
|
1190
1205
|
return card;
|
|
@@ -1194,7 +1209,7 @@ class Store {
|
|
|
1194
1209
|
target = board.columns.find(c => !c.isTrash && !c.isDone) || board.columns.find(c => !c.isTrash);
|
|
1195
1210
|
}
|
|
1196
1211
|
if (!target) {
|
|
1197
|
-
throw new Error('
|
|
1212
|
+
throw new Error('no target column for the restore');
|
|
1198
1213
|
}
|
|
1199
1214
|
const fromColumn = card.columnId;
|
|
1200
1215
|
this._mutate(board, b => {
|
|
@@ -1361,18 +1376,18 @@ class Store {
|
|
|
1361
1376
|
// eines Boards passiert per Drag & Drop bzw. über die Spaltenauswahl.
|
|
1362
1377
|
const sameBoard = fromBoardId === toBoardId;
|
|
1363
1378
|
if (sameBoard && mode !== 'copy') {
|
|
1364
|
-
throw new Error('
|
|
1379
|
+
throw new Error('source and target board are identical');
|
|
1365
1380
|
}
|
|
1366
1381
|
const card = from.cards.find(c => c.id === cardId);
|
|
1367
1382
|
if (!card) {
|
|
1368
|
-
throw
|
|
1383
|
+
throw notFound(`card '${cardId}' does not exist`);
|
|
1369
1384
|
}
|
|
1370
1385
|
let target = toColumnId && to.columns.find(c => c.id === toColumnId && !c.isTrash);
|
|
1371
1386
|
if (!target) {
|
|
1372
1387
|
target = to.columns.find(c => c.allowAdd && !c.isTrash) || to.columns.find(c => !c.isTrash);
|
|
1373
1388
|
}
|
|
1374
1389
|
if (!target) {
|
|
1375
|
-
throw new Error('
|
|
1390
|
+
throw new Error('target board has no column');
|
|
1376
1391
|
}
|
|
1377
1392
|
// Labels über den Namen abgleichen
|
|
1378
1393
|
const labelName = l =>
|
|
@@ -1399,7 +1414,7 @@ class Store {
|
|
|
1399
1414
|
assignees = overrides.assignees.filter(a => members.has(a));
|
|
1400
1415
|
}
|
|
1401
1416
|
if (!assignees.length) {
|
|
1402
|
-
throw new Error('
|
|
1417
|
+
throw new Error('target board: at least one assignee who is a member is required');
|
|
1403
1418
|
}
|
|
1404
1419
|
const isDone = !!target.isDone;
|
|
1405
1420
|
const nowIso = new Date().toISOString();
|
|
@@ -1576,6 +1591,51 @@ class Store {
|
|
|
1576
1591
|
}
|
|
1577
1592
|
}
|
|
1578
1593
|
|
|
1594
|
+
/**
|
|
1595
|
+
* Zustandsnamen aus alten Installationen nachziehen.
|
|
1596
|
+
*
|
|
1597
|
+
* Die Objekte entstehen ueber setObjectNotExistsAsync. Ein geaenderter Name im
|
|
1598
|
+
* Code erreicht ein bestehendes Objekt deshalb nie: Auf einer Instanz, die es
|
|
1599
|
+
* vor 0.3.3 schon gab, stuenden weiter die deutschen Namen, und genau die
|
|
1600
|
+
* landen im Objekt-Dump. Der Durchlauf laeuft einmal je Start ueber die
|
|
1601
|
+
* eigenen Objekte und kostet einen Lesezugriff.
|
|
1602
|
+
*
|
|
1603
|
+
* @returns Anzahl der umbenannten Objekte
|
|
1604
|
+
*/
|
|
1605
|
+
async migrateStateNames() {
|
|
1606
|
+
const NEUE_NAMEN = {
|
|
1607
|
+
'Anzahl Karten': 'Card count',
|
|
1608
|
+
'Überfällige Karten': 'Overdue cards',
|
|
1609
|
+
'Überfällige Karten (Liste)': 'Overdue cards (list)',
|
|
1610
|
+
'Zugewiesene offene Karten': 'Assigned open cards',
|
|
1611
|
+
};
|
|
1612
|
+
let geaendert = 0;
|
|
1613
|
+
let objekte = null;
|
|
1614
|
+
try {
|
|
1615
|
+
objekte = await this.adapter.getAdapterObjectsAsync();
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
this.adapter.log.warn(`state name migration skipped: ${e.message}`);
|
|
1618
|
+
return 0;
|
|
1619
|
+
}
|
|
1620
|
+
for (const [id, obj] of Object.entries(objekte || {})) {
|
|
1621
|
+
const name = obj && obj.common && obj.common.name;
|
|
1622
|
+
if (typeof name !== 'string' || !NEUE_NAMEN[name]) {
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
const kurz = id.startsWith(`${this.adapter.namespace}.`) ? id.slice(this.adapter.namespace.length + 1) : id;
|
|
1626
|
+
try {
|
|
1627
|
+
await this.adapter.extendObjectAsync(kurz, { common: { name: NEUE_NAMEN[name] } });
|
|
1628
|
+
geaendert++;
|
|
1629
|
+
} catch (e) {
|
|
1630
|
+
this.adapter.log.warn(`could not rename '${id}': ${e.message}`);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
if (geaendert) {
|
|
1634
|
+
this.adapter.log.info(`${geaendert} state name(s) translated to English`);
|
|
1635
|
+
}
|
|
1636
|
+
return geaendert;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1579
1639
|
async _ensureBoardObjects(boardId, title) {
|
|
1580
1640
|
await this.adapter.setObjectNotExistsAsync(`boards.${boardId}`, {
|
|
1581
1641
|
type: 'channel',
|
|
@@ -1585,8 +1645,8 @@ class Store {
|
|
|
1585
1645
|
const stateDefs = {
|
|
1586
1646
|
data: { name: 'Board JSON', type: 'string', role: 'json', read: true, write: false },
|
|
1587
1647
|
rev: { name: 'Revision', type: 'number', role: 'value', read: true, write: false },
|
|
1588
|
-
cardCount: { name: '
|
|
1589
|
-
overdueCount: { name: '
|
|
1648
|
+
cardCount: { name: 'Card count', type: 'number', role: 'value', read: true, write: false },
|
|
1649
|
+
overdueCount: { name: 'Overdue cards', type: 'number', role: 'value', read: true, write: false },
|
|
1590
1650
|
};
|
|
1591
1651
|
for (const [suffix, common] of Object.entries(stateDefs)) {
|
|
1592
1652
|
await this.adapter.setObjectNotExistsAsync(`boards.${boardId}.${suffix}`, {
|
|
@@ -1660,7 +1720,7 @@ class Store {
|
|
|
1660
1720
|
if (alle.length === 1) {
|
|
1661
1721
|
return [alle[0]];
|
|
1662
1722
|
}
|
|
1663
|
-
throw new Error('assignees
|
|
1723
|
+
throw new Error('assignees is missing - at least one assignee is required');
|
|
1664
1724
|
}
|
|
1665
1725
|
const erlaubt = new Set(alle);
|
|
1666
1726
|
const bekannt = [...erlaubt].sort();
|
|
@@ -1669,9 +1729,7 @@ class Store {
|
|
|
1669
1729
|
}
|
|
1670
1730
|
const fremd = [...new Set(list.filter(a => !erlaubt.has(a)))];
|
|
1671
1731
|
if (fremd.length) {
|
|
1672
|
-
throw new Error(
|
|
1673
|
-
`unbekannte zuständige Person: ${fremd.join(', ')} - vorhanden sind: ${bekannt.join(', ') || '(keine)'}`,
|
|
1674
|
-
);
|
|
1732
|
+
throw new Error(`unknown assignee: ${fremd.join(', ')} - available are: ${bekannt.join(', ') || '(none)'}`);
|
|
1675
1733
|
}
|
|
1676
1734
|
return [...new Set(list)];
|
|
1677
1735
|
}
|
|
@@ -1808,7 +1866,7 @@ class Store {
|
|
|
1808
1866
|
}
|
|
1809
1867
|
const known = (this.adapter.config.users || []).map(u => u && u.name).filter(Boolean);
|
|
1810
1868
|
if (!known.includes(neu)) {
|
|
1811
|
-
throw
|
|
1869
|
+
throw notFound(`user '${neu}' does not exist`);
|
|
1812
1870
|
}
|
|
1813
1871
|
let cards = 0;
|
|
1814
1872
|
const boards = new Set();
|
|
@@ -2019,14 +2077,14 @@ class Store {
|
|
|
2019
2077
|
});
|
|
2020
2078
|
const defs = {
|
|
2021
2079
|
assignedCount: {
|
|
2022
|
-
name: '
|
|
2080
|
+
name: 'Assigned open cards',
|
|
2023
2081
|
type: 'number',
|
|
2024
2082
|
role: 'value',
|
|
2025
2083
|
read: true,
|
|
2026
2084
|
write: false,
|
|
2027
2085
|
},
|
|
2028
|
-
overdueCount: { name: '
|
|
2029
|
-
overdueList: { name: '
|
|
2086
|
+
overdueCount: { name: 'Overdue cards', type: 'number', role: 'value', read: true, write: false },
|
|
2087
|
+
overdueList: { name: 'Overdue cards (list)', type: 'string', role: 'json', read: true, write: false },
|
|
2030
2088
|
};
|
|
2031
2089
|
for (const [suffix, common] of Object.entries(defs)) {
|
|
2032
2090
|
await this.adapter.setObjectNotExistsAsync(`users.${name}.${suffix}`, {
|
|
@@ -2049,5 +2107,6 @@ module.exports = {
|
|
|
2049
2107
|
nextDue,
|
|
2050
2108
|
recMatches,
|
|
2051
2109
|
nextCronDates,
|
|
2110
|
+
notFound,
|
|
2052
2111
|
TRASH_RETENTION_DAYS,
|
|
2053
2112
|
};
|
package/main.js
CHANGED
|
@@ -11,7 +11,7 @@ const { cardWithDueAt, boardWithDueAt } = require('./lib/dueat');
|
|
|
11
11
|
const { Server } = require('./lib/server');
|
|
12
12
|
|
|
13
13
|
const { freezePlan, prunePlan } = require('./lib/freeze');
|
|
14
|
-
const { hasDateToken } = require('./lib/store');
|
|
14
|
+
const { hasDateToken, notFound } = require('./lib/store');
|
|
15
15
|
|
|
16
16
|
// Wie oft der Adapter versucht, das Merkmal `fixed` in die Benutzerliste
|
|
17
17
|
// zurueckzuschreiben, bevor er aufgibt. Mehr als einer, weil ein Speichern der
|
|
@@ -55,6 +55,8 @@ class Kanban extends utils.Adapter {
|
|
|
55
55
|
// angelegt und braucht dafür den lokalisierten Namen.
|
|
56
56
|
await this._resolveLanguage();
|
|
57
57
|
await this.store.load();
|
|
58
|
+
// Namen aus alten Installationen nachziehen (siehe migrateStateNames).
|
|
59
|
+
await this.store.migrateStateNames();
|
|
58
60
|
// Erst nach dem Laden: Ob eine Kennung festgeschrieben werden muss,
|
|
59
61
|
// haengt daran, ob Karten auf sie zeigen, und die kennt der Store
|
|
60
62
|
// vorher nicht. Schreibt der Adapter dabei seine eigene Konfiguration,
|
|
@@ -418,7 +420,7 @@ class Kanban extends utils.Adapter {
|
|
|
418
420
|
// Nicht still null liefern: ein Skript, das nur den Statuscode prüft,
|
|
419
421
|
// hielte das sonst für einen Erfolg.
|
|
420
422
|
if (!b) {
|
|
421
|
-
throw
|
|
423
|
+
throw notFound(`board '${boardId}' does not exist`);
|
|
422
424
|
}
|
|
423
425
|
return board(b);
|
|
424
426
|
}
|
|
@@ -439,11 +441,11 @@ class Kanban extends utils.Adapter {
|
|
|
439
441
|
case 'doneCard': {
|
|
440
442
|
const b = this.store.getBoard(boardId);
|
|
441
443
|
if (!b) {
|
|
442
|
-
throw
|
|
444
|
+
throw notFound(`board '${boardId}' does not exist`);
|
|
443
445
|
}
|
|
444
446
|
const doneCol = b.columns.find(c => c.isDone);
|
|
445
447
|
if (!doneCol) {
|
|
446
|
-
throw new Error(`
|
|
448
|
+
throw new Error(`board '${boardId}' has no done column`);
|
|
447
449
|
}
|
|
448
450
|
return card(this.store.moveCard(boardId, cardId, doneCol.id, undefined, source));
|
|
449
451
|
}
|
|
@@ -472,7 +474,7 @@ class Kanban extends utils.Adapter {
|
|
|
472
474
|
),
|
|
473
475
|
);
|
|
474
476
|
default:
|
|
475
|
-
throw new Error(`
|
|
477
|
+
throw new Error(`unknown command '${cmd}'`);
|
|
476
478
|
}
|
|
477
479
|
}
|
|
478
480
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iobroker.kanban",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Kanban board for ioBroker with webhooks, multi-user support and email notifications",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Björn Müller",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@alcalzone/release-script-plugin-manual-review": "^5.2.0",
|
|
61
61
|
"@iobroker/adapter-dev": "^1.5.0",
|
|
62
62
|
"@iobroker/eslint-config": "^2.2.0",
|
|
63
|
-
"@iobroker/testing": "^
|
|
63
|
+
"@iobroker/testing": "^6.2.1",
|
|
64
64
|
"@tsconfig/node22": "^22.0.6",
|
|
65
65
|
"@types/node": "^22.0.0"
|
|
66
66
|
}
|