Compare commits
23 Commits
enh/nextcl
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99c3beafb7 | ||
|
|
9dc78b790c | ||
|
|
854798f7e2 | ||
|
|
59211e3e9b | ||
|
|
929ef69d31 | ||
|
|
4e091d0428 | ||
|
|
266818cd2b | ||
|
|
fd82f4c792 | ||
|
|
e7d2c74986 | ||
|
|
23e06b58f5 | ||
|
|
bea8370c03 | ||
|
|
08284caedd | ||
|
|
da3cc5b589 | ||
|
|
d0d2da490c | ||
|
|
f6b4807ba0 | ||
|
|
50391204c9 | ||
|
|
cf4b59ddcd | ||
|
|
e22a72d836 | ||
|
|
a590e15e75 | ||
|
|
396a5c395f | ||
|
|
22fb70f957 | ||
|
|
6cab67cb11 | ||
|
|
bb6790882a |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,6 +1,36 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.0.0 - unreleased
|
||||
|
||||
## Added
|
||||
|
||||
- Completly rewritten frontend
|
||||
- Better maintainability
|
||||
- Various small fixes
|
||||
- Unified user interface with Nextcloud
|
||||
- Separate comment and activity timelines
|
||||
- Add ability to reply to comments #1537
|
||||
- Filter cards on board #1507 @jakobroehrl
|
||||
- Add cards to projects #1294 @jakobroehrl
|
||||
- Move cards to other boards #1242 @jakobroehrl
|
||||
- Clone boards with existing stacks and labels #1221 @jakobroehrl
|
||||
- Upload multiple files at once and in parallel
|
||||
|
||||
A huge thangs goes to our awesome community that put enourmous effort into the frontend migration:
|
||||
|
||||
Special thanks for contributing huge parts of the Vue.js migration:
|
||||
@jakobroehrl @weeman1337 @nicolad
|
||||
|
||||
Testers/reporters:
|
||||
@cloud2018 @putt1ck @bpcurse
|
||||
|
||||
Calendar/Tasks integration help:
|
||||
@raimund-schluessler @georgehrke
|
||||
|
||||
Android app team for helping to improve our REST API:
|
||||
@desperateCoder @stefan-niedermann
|
||||
|
||||
## 0.8.0 - 2020-01-16
|
||||
|
||||
## Added
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.0.0-alpha1</version>
|
||||
<version>1.0.0-beta1</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -36,7 +36,7 @@
|
||||
<database min-version="9.4">pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database min-version="5.5">mysql</database>
|
||||
<nextcloud min-version="17" max-version="19" />
|
||||
<nextcloud min-version="18" max-version="19" />
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Vybrat termín",
|
||||
"Remove due date" : "Odstranit termín",
|
||||
"Description" : "Popis",
|
||||
"(Unsaved)" : "(Neuloženo)",
|
||||
"(Saving…)" : "(Ukládání…)",
|
||||
"Formatting help" : "Nápověda k formátování",
|
||||
"Attachments" : "Přílohy",
|
||||
"Comments" : "Komentáře",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Vybrat termín",
|
||||
"Remove due date" : "Odstranit termín",
|
||||
"Description" : "Popis",
|
||||
"(Unsaved)" : "(Neuloženo)",
|
||||
"(Saving…)" : "(Ukládání…)",
|
||||
"Formatting help" : "Nápověda k formátování",
|
||||
"Attachments" : "Přílohy",
|
||||
"Comments" : "Komentáře",
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Ein Ablaufdatum setzen",
|
||||
"Remove due date" : "Fälligkeitsdatum löschen",
|
||||
"Description" : "Beschreibung",
|
||||
"(Unsaved)" : "(nicht gespeichert)",
|
||||
"(Saving…)" : "(speichere...)",
|
||||
"Formatting help" : "Formatierungshilfe",
|
||||
"Attachments" : "Anhänge",
|
||||
"Comments" : "Kommentare",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Ein Ablaufdatum setzen",
|
||||
"Remove due date" : "Fälligkeitsdatum löschen",
|
||||
"Description" : "Beschreibung",
|
||||
"(Unsaved)" : "(nicht gespeichert)",
|
||||
"(Saving…)" : "(speichere...)",
|
||||
"Formatting help" : "Formatierungshilfe",
|
||||
"Attachments" : "Anhänge",
|
||||
"Comments" : "Kommentare",
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Ein Ablaufdatum setzen",
|
||||
"Remove due date" : "Fälligkeitsdatum entfernen",
|
||||
"Description" : "Beschreibung",
|
||||
"(Unsaved)" : "(nicht gespeichert)",
|
||||
"(Saving…)" : "(speichere...)",
|
||||
"Formatting help" : "Formatierungshilfe",
|
||||
"Attachments" : "Anhänge",
|
||||
"Comments" : "Kommentare",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Ein Ablaufdatum setzen",
|
||||
"Remove due date" : "Fälligkeitsdatum entfernen",
|
||||
"Description" : "Beschreibung",
|
||||
"(Unsaved)" : "(nicht gespeichert)",
|
||||
"(Saving…)" : "(speichere...)",
|
||||
"Formatting help" : "Formatierungshilfe",
|
||||
"Attachments" : "Anhänge",
|
||||
"Comments" : "Kommentare",
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Καθορίστε ημερομηνίας λήξης",
|
||||
"Remove due date" : "Αφαίρεση ημερομηνίας λήξης",
|
||||
"Description" : "Περιγραφή",
|
||||
"(Unsaved)" : "(Δεν αποθηκεύτηκε)",
|
||||
"(Saving…)" : "(Αποθήκευση...)",
|
||||
"Formatting help" : "Βοήθεια μορφοποίησης",
|
||||
"Attachments" : "Συνημμένα",
|
||||
"Comments" : "Σχόλια",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Καθορίστε ημερομηνίας λήξης",
|
||||
"Remove due date" : "Αφαίρεση ημερομηνίας λήξης",
|
||||
"Description" : "Περιγραφή",
|
||||
"(Unsaved)" : "(Δεν αποθηκεύτηκε)",
|
||||
"(Saving…)" : "(Αποθήκευση...)",
|
||||
"Formatting help" : "Βοήθεια μορφοποίησης",
|
||||
"Attachments" : "Συνημμένα",
|
||||
"Comments" : "Σχόλια",
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Définir une date d'échéance",
|
||||
"Remove due date" : "Supprimer la date d'échéance",
|
||||
"Description" : "Description",
|
||||
"(Unsaved)" : "(Non enregistré)",
|
||||
"(Saving…)" : "(Enregistrement ...)",
|
||||
"Formatting help" : "Aide sur la mise en forme",
|
||||
"Attachments" : "Pièces jointes",
|
||||
"Comments" : "Commentaires",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Définir une date d'échéance",
|
||||
"Remove due date" : "Supprimer la date d'échéance",
|
||||
"Description" : "Description",
|
||||
"(Unsaved)" : "(Non enregistré)",
|
||||
"(Saving…)" : "(Enregistrement ...)",
|
||||
"Formatting help" : "Aide sur la mise en forme",
|
||||
"Attachments" : "Pièces jointes",
|
||||
"Comments" : "Commentaires",
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Estabelecer a data de caducidade",
|
||||
"Remove due date" : "Retirar a data de caducidade",
|
||||
"Description" : "Descrición",
|
||||
"(Unsaved)" : "(Sen gardar)",
|
||||
"(Saving…)" : "(Gardando…)",
|
||||
"Formatting help" : "Axuda de formatado",
|
||||
"Attachments" : "Anexos",
|
||||
"Comments" : "Comentarios",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Estabelecer a data de caducidade",
|
||||
"Remove due date" : "Retirar a data de caducidade",
|
||||
"Description" : "Descrición",
|
||||
"(Unsaved)" : "(Sen gardar)",
|
||||
"(Saving…)" : "(Gardando…)",
|
||||
"Formatting help" : "Axuda de formatado",
|
||||
"Attachments" : "Anexos",
|
||||
"Comments" : "Comentarios",
|
||||
|
||||
@@ -155,11 +155,14 @@ OC.L10N.register(
|
||||
"Details" : "Dettagli",
|
||||
"Assign a tag to this card…" : "Assegna un'etichetta a questa scheda…",
|
||||
"Assign to users" : "Assegna a utenti",
|
||||
"Assign to users/groups/circles" : "Assegna a utenti/gruppi/cerchie",
|
||||
"Assign a user to this card…" : "Assegna un utente a questa scheda…",
|
||||
"Due date" : "Data di scadenza",
|
||||
"Set a due date" : "Imposta una data di scadenza",
|
||||
"Remove due date" : "Rimuovi data di scadenza",
|
||||
"Description" : "Descrizione",
|
||||
"(Unsaved)" : "(Non salvata)",
|
||||
"(Saving…)" : "(Salvataggio...)",
|
||||
"Formatting help" : "Guida di formattazione",
|
||||
"Attachments" : "Allegati",
|
||||
"Comments" : "Commenti",
|
||||
@@ -176,6 +179,7 @@ OC.L10N.register(
|
||||
"Reply" : "Rispondi",
|
||||
"Update" : "Aggiorna",
|
||||
"(group)" : "(gruppo)",
|
||||
"(circle)" : "(cerchia)",
|
||||
"seconds ago" : "secondi fa",
|
||||
"Assign to me" : "Assegna a me",
|
||||
"Delete card" : "Elimina scheda",
|
||||
|
||||
@@ -153,11 +153,14 @@
|
||||
"Details" : "Dettagli",
|
||||
"Assign a tag to this card…" : "Assegna un'etichetta a questa scheda…",
|
||||
"Assign to users" : "Assegna a utenti",
|
||||
"Assign to users/groups/circles" : "Assegna a utenti/gruppi/cerchie",
|
||||
"Assign a user to this card…" : "Assegna un utente a questa scheda…",
|
||||
"Due date" : "Data di scadenza",
|
||||
"Set a due date" : "Imposta una data di scadenza",
|
||||
"Remove due date" : "Rimuovi data di scadenza",
|
||||
"Description" : "Descrizione",
|
||||
"(Unsaved)" : "(Non salvata)",
|
||||
"(Saving…)" : "(Salvataggio...)",
|
||||
"Formatting help" : "Guida di formattazione",
|
||||
"Attachments" : "Allegati",
|
||||
"Comments" : "Commenti",
|
||||
@@ -174,6 +177,7 @@
|
||||
"Reply" : "Rispondi",
|
||||
"Update" : "Aggiorna",
|
||||
"(group)" : "(gruppo)",
|
||||
"(circle)" : "(cerchia)",
|
||||
"seconds ago" : "secondi fa",
|
||||
"Assign to me" : "Assegna a me",
|
||||
"Delete card" : "Elimina scheda",
|
||||
|
||||
@@ -155,11 +155,14 @@ OC.L10N.register(
|
||||
"Details" : "Szczegóły",
|
||||
"Assign a tag to this card…" : "Przypisz etykietę do tej karty…",
|
||||
"Assign to users" : "Przypisz do użytkowników",
|
||||
"Assign to users/groups/circles" : "Przypisz do użytkowników/grup/kręgów",
|
||||
"Assign a user to this card…" : "Przypisz użytkownika do tej karty…",
|
||||
"Due date" : "Data realizacji",
|
||||
"Set a due date" : "Ustaw termin",
|
||||
"Remove due date" : "Usuń datę realizacji",
|
||||
"Description" : "Opis",
|
||||
"(Unsaved)" : "(Niezapisane)",
|
||||
"(Saving…)" : "(Zapisywanie...)",
|
||||
"Formatting help" : "Pomoc przy formatowaniu",
|
||||
"Attachments" : "Załączniki",
|
||||
"Comments" : "Komentarze",
|
||||
@@ -176,6 +179,7 @@ OC.L10N.register(
|
||||
"Reply" : "Odpowiedz",
|
||||
"Update" : "Aktualizuj",
|
||||
"(group)" : "(grupa)",
|
||||
"(circle)" : "(krąg)",
|
||||
"seconds ago" : "przed chwilą",
|
||||
"Assign to me" : "Przydziel do mnie",
|
||||
"Delete card" : "Usuń kartę",
|
||||
|
||||
@@ -153,11 +153,14 @@
|
||||
"Details" : "Szczegóły",
|
||||
"Assign a tag to this card…" : "Przypisz etykietę do tej karty…",
|
||||
"Assign to users" : "Przypisz do użytkowników",
|
||||
"Assign to users/groups/circles" : "Przypisz do użytkowników/grup/kręgów",
|
||||
"Assign a user to this card…" : "Przypisz użytkownika do tej karty…",
|
||||
"Due date" : "Data realizacji",
|
||||
"Set a due date" : "Ustaw termin",
|
||||
"Remove due date" : "Usuń datę realizacji",
|
||||
"Description" : "Opis",
|
||||
"(Unsaved)" : "(Niezapisane)",
|
||||
"(Saving…)" : "(Zapisywanie...)",
|
||||
"Formatting help" : "Pomoc przy formatowaniu",
|
||||
"Attachments" : "Załączniki",
|
||||
"Comments" : "Komentarze",
|
||||
@@ -174,6 +177,7 @@
|
||||
"Reply" : "Odpowiedz",
|
||||
"Update" : "Aktualizuj",
|
||||
"(group)" : "(grupa)",
|
||||
"(circle)" : "(krąg)",
|
||||
"seconds ago" : "przed chwilą",
|
||||
"Assign to me" : "Przydziel do mnie",
|
||||
"Delete card" : "Usuń kartę",
|
||||
|
||||
@@ -161,6 +161,8 @@ OC.L10N.register(
|
||||
"Set a due date" : "Definir uma data de finalização",
|
||||
"Remove due date" : "Remover data de vencimento",
|
||||
"Description" : "Descrição",
|
||||
"(Unsaved)" : "(Não salvo)",
|
||||
"(Saving…)" : "(Salvando...)",
|
||||
"Formatting help" : "Formatando ajuda",
|
||||
"Attachments" : "Anexos",
|
||||
"Comments" : "Comentários",
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
"Set a due date" : "Definir uma data de finalização",
|
||||
"Remove due date" : "Remover data de vencimento",
|
||||
"Description" : "Descrição",
|
||||
"(Unsaved)" : "(Não salvo)",
|
||||
"(Saving…)" : "(Salvando...)",
|
||||
"Formatting help" : "Formatando ajuda",
|
||||
"Attachments" : "Anexos",
|
||||
"Comments" : "Comentários",
|
||||
|
||||
@@ -155,11 +155,14 @@ OC.L10N.register(
|
||||
"Details" : "Подробнее",
|
||||
"Assign a tag to this card…" : "Назначить метку этой карточке…",
|
||||
"Assign to users" : "Назначить пользователям",
|
||||
"Assign to users/groups/circles" : "Назначить пользователям / группам / кругам",
|
||||
"Assign a user to this card…" : "Назначить пользователя этой карточке…",
|
||||
"Due date" : "Срок исполнения",
|
||||
"Set a due date" : "Задать дату исполнения",
|
||||
"Remove due date" : "Удалить срок исполнения",
|
||||
"Description" : "Описание",
|
||||
"(Unsaved)" : "(Не сохранен)",
|
||||
"(Saving…)" : "(Сохранение...)",
|
||||
"Formatting help" : "Справка по форматированию",
|
||||
"Attachments" : "Вложения",
|
||||
"Comments" : "Комментарии",
|
||||
@@ -176,6 +179,7 @@ OC.L10N.register(
|
||||
"Reply" : "Ответить",
|
||||
"Update" : "Обновить",
|
||||
"(group)" : "(группа)",
|
||||
"(circle)" : "(круг)",
|
||||
"seconds ago" : "несколько секунд назад",
|
||||
"Assign to me" : "Назначить себе",
|
||||
"Delete card" : "Удалить",
|
||||
|
||||
@@ -153,11 +153,14 @@
|
||||
"Details" : "Подробнее",
|
||||
"Assign a tag to this card…" : "Назначить метку этой карточке…",
|
||||
"Assign to users" : "Назначить пользователям",
|
||||
"Assign to users/groups/circles" : "Назначить пользователям / группам / кругам",
|
||||
"Assign a user to this card…" : "Назначить пользователя этой карточке…",
|
||||
"Due date" : "Срок исполнения",
|
||||
"Set a due date" : "Задать дату исполнения",
|
||||
"Remove due date" : "Удалить срок исполнения",
|
||||
"Description" : "Описание",
|
||||
"(Unsaved)" : "(Не сохранен)",
|
||||
"(Saving…)" : "(Сохранение...)",
|
||||
"Formatting help" : "Справка по форматированию",
|
||||
"Attachments" : "Вложения",
|
||||
"Comments" : "Комментарии",
|
||||
@@ -174,6 +177,7 @@
|
||||
"Reply" : "Ответить",
|
||||
"Update" : "Обновить",
|
||||
"(group)" : "(группа)",
|
||||
"(circle)" : "(круг)",
|
||||
"seconds ago" : "несколько секунд назад",
|
||||
"Assign to me" : "Назначить себе",
|
||||
"Delete card" : "Удалить",
|
||||
|
||||
10
l10n/sl.js
10
l10n/sl.js
@@ -68,6 +68,8 @@ OC.L10N.register(
|
||||
"Changes in the <strong>Deck app</strong>" : "Spremembe v programu <strong>Deck</strong>",
|
||||
"A <strong>comment</strong> was created on a card" : "Nalogi je dodana nova <strong>opomba</strong>",
|
||||
"Personal" : "Osebno",
|
||||
"{user} has assigned the card \"%s\" on \"%s\" to you." : "{user} vam dodeli nalogo »%s« na »%s«.",
|
||||
"The card \"%s\" on \"%s\" has reached its due date." : "Naloga »%s« na »%s« je dosegla datum preteka.",
|
||||
"%s has mentioned you in a comment on \"%s\"." : "%s vas omeni v opombi na »%s«.",
|
||||
"{user} has mentioned you in a comment on \"%s\"." : "{user} vas omeni v opombi na »%s«.",
|
||||
"{user} has shared the board %s with you." : "{user} vam omogoči souporabo zbirke %s.",
|
||||
@@ -103,9 +105,12 @@ OC.L10N.register(
|
||||
"Link to card" : "Poveži nalogo",
|
||||
"Cancel" : "Prekliči",
|
||||
"File already exists" : "Datoteka s tem imenom že obstaja.",
|
||||
"A file with the name {filename} already exists." : "Datoteka z imenom {filename} že obstaja.",
|
||||
"Do you want to overwrite it?" : "Ali želite predmet prepisati?",
|
||||
"Overwrite file" : "Prepiši datoteko",
|
||||
"Keep existing file" : "Ohrani obstoječe datoteke",
|
||||
"This board is read only" : "Zbirka je označena le za branje",
|
||||
"Drop your files to upload" : "Spustite datoteke za pošiljanje v oblak",
|
||||
"Add new list" : "Dodaj nov seznam",
|
||||
"List name" : "Ime seznama",
|
||||
"Apply filter" : "Uveljavi filter",
|
||||
@@ -146,11 +151,14 @@ OC.L10N.register(
|
||||
"Details" : "Podrobnosti",
|
||||
"Assign a tag to this card…" : "Dodeli oznako nalogi ...",
|
||||
"Assign to users" : "Dodeli uporabnikom",
|
||||
"Assign to users/groups/circles" : "Dodeli uporabnikom/skupinam/krogom",
|
||||
"Assign a user to this card…" : "Dodeli uporabnika k nalogi ...",
|
||||
"Due date" : "Datum preteka",
|
||||
"Set a due date" : "Nastavi datum preteka",
|
||||
"Remove due date" : "Odstrani datum preteka",
|
||||
"Description" : "Opis",
|
||||
"(Unsaved)" : "(neshranjeno)",
|
||||
"(Saving…)" : "(shranjevanje ...)",
|
||||
"Formatting help" : "Pomoč pri oblikovanju",
|
||||
"Attachments" : "Priloge",
|
||||
"Comments" : "Opombe",
|
||||
@@ -163,9 +171,11 @@ OC.L10N.register(
|
||||
"Save" : "Shrani",
|
||||
"The comment cannot be empty." : "Polje opombe ne sme biti prazno.",
|
||||
"The comment cannot be longer than 1000 characters." : "Opomba ne sme biti daljša od 1000 znakov.",
|
||||
"In reply to" : "V odgovor",
|
||||
"Reply" : "Odgovori",
|
||||
"Update" : "Posodobi",
|
||||
"(group)" : "(skupina)",
|
||||
"(circle)" : "(krog)",
|
||||
"seconds ago" : "pred nekaj sekundami",
|
||||
"Assign to me" : "Nalogo dodeli meni",
|
||||
"Delete card" : "Izbriši nalogo",
|
||||
|
||||
10
l10n/sl.json
10
l10n/sl.json
@@ -66,6 +66,8 @@
|
||||
"Changes in the <strong>Deck app</strong>" : "Spremembe v programu <strong>Deck</strong>",
|
||||
"A <strong>comment</strong> was created on a card" : "Nalogi je dodana nova <strong>opomba</strong>",
|
||||
"Personal" : "Osebno",
|
||||
"{user} has assigned the card \"%s\" on \"%s\" to you." : "{user} vam dodeli nalogo »%s« na »%s«.",
|
||||
"The card \"%s\" on \"%s\" has reached its due date." : "Naloga »%s« na »%s« je dosegla datum preteka.",
|
||||
"%s has mentioned you in a comment on \"%s\"." : "%s vas omeni v opombi na »%s«.",
|
||||
"{user} has mentioned you in a comment on \"%s\"." : "{user} vas omeni v opombi na »%s«.",
|
||||
"{user} has shared the board %s with you." : "{user} vam omogoči souporabo zbirke %s.",
|
||||
@@ -101,9 +103,12 @@
|
||||
"Link to card" : "Poveži nalogo",
|
||||
"Cancel" : "Prekliči",
|
||||
"File already exists" : "Datoteka s tem imenom že obstaja.",
|
||||
"A file with the name {filename} already exists." : "Datoteka z imenom {filename} že obstaja.",
|
||||
"Do you want to overwrite it?" : "Ali želite predmet prepisati?",
|
||||
"Overwrite file" : "Prepiši datoteko",
|
||||
"Keep existing file" : "Ohrani obstoječe datoteke",
|
||||
"This board is read only" : "Zbirka je označena le za branje",
|
||||
"Drop your files to upload" : "Spustite datoteke za pošiljanje v oblak",
|
||||
"Add new list" : "Dodaj nov seznam",
|
||||
"List name" : "Ime seznama",
|
||||
"Apply filter" : "Uveljavi filter",
|
||||
@@ -144,11 +149,14 @@
|
||||
"Details" : "Podrobnosti",
|
||||
"Assign a tag to this card…" : "Dodeli oznako nalogi ...",
|
||||
"Assign to users" : "Dodeli uporabnikom",
|
||||
"Assign to users/groups/circles" : "Dodeli uporabnikom/skupinam/krogom",
|
||||
"Assign a user to this card…" : "Dodeli uporabnika k nalogi ...",
|
||||
"Due date" : "Datum preteka",
|
||||
"Set a due date" : "Nastavi datum preteka",
|
||||
"Remove due date" : "Odstrani datum preteka",
|
||||
"Description" : "Opis",
|
||||
"(Unsaved)" : "(neshranjeno)",
|
||||
"(Saving…)" : "(shranjevanje ...)",
|
||||
"Formatting help" : "Pomoč pri oblikovanju",
|
||||
"Attachments" : "Priloge",
|
||||
"Comments" : "Opombe",
|
||||
@@ -161,9 +169,11 @@
|
||||
"Save" : "Shrani",
|
||||
"The comment cannot be empty." : "Polje opombe ne sme biti prazno.",
|
||||
"The comment cannot be longer than 1000 characters." : "Opomba ne sme biti daljša od 1000 znakov.",
|
||||
"In reply to" : "V odgovor",
|
||||
"Reply" : "Odgovori",
|
||||
"Update" : "Posodobi",
|
||||
"(group)" : "(skupina)",
|
||||
"(circle)" : "(krog)",
|
||||
"seconds ago" : "pred nekaj sekundami",
|
||||
"Assign to me" : "Nalogo dodeli meni",
|
||||
"Delete card" : "Izbriši nalogo",
|
||||
|
||||
@@ -154,6 +154,7 @@ OC.L10N.register(
|
||||
"Details" : "Detaljer",
|
||||
"Assign a tag to this card…" : "Tilldela en tagg till det här kortet ...",
|
||||
"Assign to users" : "Tilldela till användare",
|
||||
"Assign to users/groups/circles" : "Tilldela till användare/grupper/cirklar",
|
||||
"Assign a user to this card…" : "Tilldela en användare till det här kortet ...",
|
||||
"Due date" : "Slutdatum",
|
||||
"Set a due date" : "Sätt ett slutdatum",
|
||||
@@ -175,6 +176,7 @@ OC.L10N.register(
|
||||
"Reply" : "Svara",
|
||||
"Update" : "Uppdatera",
|
||||
"(group)" : " (grupp)",
|
||||
"(circle)" : "(cirkel)",
|
||||
"seconds ago" : "sekunder sedan",
|
||||
"Assign to me" : "Tilldela till mig",
|
||||
"Delete card" : "Ta bort kort",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"Details" : "Detaljer",
|
||||
"Assign a tag to this card…" : "Tilldela en tagg till det här kortet ...",
|
||||
"Assign to users" : "Tilldela till användare",
|
||||
"Assign to users/groups/circles" : "Tilldela till användare/grupper/cirklar",
|
||||
"Assign a user to this card…" : "Tilldela en användare till det här kortet ...",
|
||||
"Due date" : "Slutdatum",
|
||||
"Set a due date" : "Sätt ett slutdatum",
|
||||
@@ -173,6 +174,7 @@
|
||||
"Reply" : "Svara",
|
||||
"Update" : "Uppdatera",
|
||||
"(group)" : " (grupp)",
|
||||
"(circle)" : "(cirkel)",
|
||||
"seconds ago" : "sekunder sedan",
|
||||
"Assign to me" : "Tilldela till mig",
|
||||
"Delete card" : "Ta bort kort",
|
||||
|
||||
@@ -155,11 +155,14 @@ OC.L10N.register(
|
||||
"Details" : "Ayrıntılar",
|
||||
"Assign a tag to this card…" : "Bu karta bir etiket ata…",
|
||||
"Assign to users" : "Kullanıcılara ata",
|
||||
"Assign to users/groups/circles" : "Kullanıcılara/gruplara/çevrelere ata",
|
||||
"Assign a user to this card…" : "Bu karta bir kullanıcı ata…",
|
||||
"Due date" : "Bitiş tarihi",
|
||||
"Set a due date" : "Bitiş tarihi ayarla",
|
||||
"Remove due date" : "Bitiş tarihini kaldır",
|
||||
"Description" : "Açıklama",
|
||||
"(Unsaved)" : "(Kaydedilmemiş)",
|
||||
"(Saving…)" : "(Kaydediliyor…)",
|
||||
"Formatting help" : "Biçimlendirme yardımı",
|
||||
"Attachments" : "Ek dosyalar",
|
||||
"Comments" : "Açıklamalar",
|
||||
@@ -176,6 +179,7 @@ OC.L10N.register(
|
||||
"Reply" : "Yanıtla",
|
||||
"Update" : "Güncelle",
|
||||
"(group)" : "(grup)",
|
||||
"(circle)" : "(çevre)",
|
||||
"seconds ago" : "saniye önce",
|
||||
"Assign to me" : "Bana ata",
|
||||
"Delete card" : "Kartı sil",
|
||||
|
||||
@@ -153,11 +153,14 @@
|
||||
"Details" : "Ayrıntılar",
|
||||
"Assign a tag to this card…" : "Bu karta bir etiket ata…",
|
||||
"Assign to users" : "Kullanıcılara ata",
|
||||
"Assign to users/groups/circles" : "Kullanıcılara/gruplara/çevrelere ata",
|
||||
"Assign a user to this card…" : "Bu karta bir kullanıcı ata…",
|
||||
"Due date" : "Bitiş tarihi",
|
||||
"Set a due date" : "Bitiş tarihi ayarla",
|
||||
"Remove due date" : "Bitiş tarihini kaldır",
|
||||
"Description" : "Açıklama",
|
||||
"(Unsaved)" : "(Kaydedilmemiş)",
|
||||
"(Saving…)" : "(Kaydediliyor…)",
|
||||
"Formatting help" : "Biçimlendirme yardımı",
|
||||
"Attachments" : "Ek dosyalar",
|
||||
"Comments" : "Açıklamalar",
|
||||
@@ -174,6 +177,7 @@
|
||||
"Reply" : "Yanıtla",
|
||||
"Update" : "Güncelle",
|
||||
"(group)" : "(grup)",
|
||||
"(circle)" : "(çevre)",
|
||||
"seconds ago" : "saniye önce",
|
||||
"Assign to me" : "Bana ata",
|
||||
"Delete card" : "Kartı sil",
|
||||
|
||||
@@ -455,6 +455,17 @@ class BoardService {
|
||||
return $board;
|
||||
}
|
||||
|
||||
private function applyPermissions($boardId, $edit, $share, $manage) {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_MANAGE);
|
||||
} catch (NoPermissionException $e) {
|
||||
$acls = $this->aclMapper->findAll($boardId);
|
||||
$edit = $this->permissionService->userCan($acls, Acl::PERMISSION_EDIT, $this->userId) && $edit;
|
||||
$share = $this->permissionService->userCan($acls, Acl::PERMISSION_SHARE, $this->userId) && $share;
|
||||
$manage = $this->permissionService->userCan($acls, Acl::PERMISSION_MANAGE, $this->userId) && $manage;
|
||||
}
|
||||
return [$edit, $share, $manage];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $boardId
|
||||
@@ -494,6 +505,8 @@ class BoardService {
|
||||
}
|
||||
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_SHARE);
|
||||
[$edit, $share, $manage] = $this->applyPermissions($boardId, $edit, $share, $manage);
|
||||
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId($boardId);
|
||||
$acl->setType($type);
|
||||
@@ -556,8 +569,10 @@ class BoardService {
|
||||
}
|
||||
|
||||
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
|
||||
|
||||
/** @var Acl $acl */
|
||||
$acl = $this->aclMapper->find($id);
|
||||
[$edit, $share, $manage] = $this->applyPermissions($acl->getBoardId(), $edit, $share, $manage);
|
||||
$acl->setPermissionEdit($edit);
|
||||
$acl->setPermissionShare($share);
|
||||
$acl->setPermissionManage($manage);
|
||||
|
||||
25
package-lock.json
generated
25
package-lock.json
generated
@@ -7049,6 +7049,11 @@
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
|
||||
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg=="
|
||||
},
|
||||
"events": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz",
|
||||
@@ -12778,8 +12783,7 @@
|
||||
"p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
|
||||
"dev": true
|
||||
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||
},
|
||||
"p-is-promise": {
|
||||
"version": "2.1.0",
|
||||
@@ -12805,6 +12809,23 @@
|
||||
"p-limit": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"p-queue": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.3.0.tgz",
|
||||
"integrity": "sha512-fg5dJlFpd5+3CgG3/0ogpVZUeJbjiyXFg0nu53hrOYsybqSiDyxyOpad0Rm6tAiGjgztAwkyvhlYHC53OiAJOA==",
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"p-timeout": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"p-timeout": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
|
||||
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
|
||||
"requires": {
|
||||
"p-finally": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
"@nextcloud/vue": "^1.4.1",
|
||||
"blueimp-md5": "^2.13.0",
|
||||
"dompurify": "^2.0.8",
|
||||
"moment": "^2.24.0",
|
||||
"nextcloud-vue-collections": "^0.7.2",
|
||||
"p-queue": "^6.3.0",
|
||||
"url-search-params-polyfill": "^8.0.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-at": "^2.5.0-beta.2",
|
||||
|
||||
@@ -102,18 +102,15 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dragEnter() {
|
||||
|
||||
},
|
||||
dragLeave() {
|
||||
|
||||
},
|
||||
handleDropFiles(event) {
|
||||
this.isDraggingOver = false
|
||||
if (this.isReadOnly) {
|
||||
return
|
||||
}
|
||||
this.onLocalAttachmentSelected(event.dataTransfer.files[0])
|
||||
const files = event.dataTransfer.files
|
||||
for (const file of files) {
|
||||
this.onLocalAttachmentSelected(file)
|
||||
}
|
||||
event.dataTransfer.value = ''
|
||||
},
|
||||
},
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
:drag-handle-selector="dragHandleSelector"
|
||||
@should-accept-drop="canEdit"
|
||||
@drop="($event) => onDropCard(stack.id, $event)">
|
||||
<Draggable v-for="card in cardsByStack(stack.id)" :key="card.id">
|
||||
<Draggable v-for="card in cardsByStack" :key="card.id">
|
||||
<CardItem v-if="card" :id="card.id" />
|
||||
</Draggable>
|
||||
</Container>
|
||||
@@ -122,7 +122,7 @@ export default {
|
||||
'canEdit',
|
||||
]),
|
||||
cardsByStack() {
|
||||
return (id) => this.$store.getters.cardsByStack(id)
|
||||
return this.$store.getters.cardsByStack(this.stack.id)
|
||||
},
|
||||
dragHandleSelector() {
|
||||
return this.canEdit ? null : '.no-drag'
|
||||
@@ -150,7 +150,7 @@ export default {
|
||||
},
|
||||
payloadForCard(stackId) {
|
||||
return index => {
|
||||
return this.cardsByStack(stackId)[index]
|
||||
return this.cardsByStack[index]
|
||||
}
|
||||
},
|
||||
deleteStack(stack) {
|
||||
|
||||
@@ -121,13 +121,18 @@
|
||||
|
||||
<h5>
|
||||
{{ t('deck', 'Description') }}
|
||||
<span v-if="copiedCard.descriptionLastEdit && !descriptionSaving">{{ t('deck', '(Unsaved)') }}</span>
|
||||
<span v-if="descriptionSaving">{{ t('deck', '(Saving…)') }}</span>
|
||||
<a v-tooltip="t('deck', 'Formatting help')"
|
||||
href="https://deck.readthedocs.io/en/latest/Markdown/"
|
||||
target="_blank"
|
||||
class="icon icon-info" />
|
||||
</h5>
|
||||
<!-- FIXME: make sure the editor is disabled when canEdit is false -->
|
||||
<VueEasymde ref="markdownEditor" v-model="copiedCard.description" :configs="mdeConfig" />
|
||||
<VueEasymde ref="markdownEditor"
|
||||
:value="copiedCard.description"
|
||||
:configs="mdeConfig"
|
||||
@input="updateDescription" />
|
||||
</AppSidebarTab>
|
||||
|
||||
<AppSidebarTab id="attachments"
|
||||
@@ -158,7 +163,6 @@
|
||||
<script>
|
||||
import { Avatar, Actions, ActionButton, Multiselect, AppSidebar, AppSidebarTab, DatetimePicker } from '@nextcloud/vue'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import VueEasymde from 'vue-easymde/dist/VueEasyMDE.common'
|
||||
import Color from '../../mixins/color'
|
||||
import { CollectionList } from 'nextcloud-vue-collections'
|
||||
import CardSidebarTabAttachments from './CardSidebarTabAttachments'
|
||||
@@ -174,7 +178,7 @@ export default {
|
||||
AppSidebarTab,
|
||||
Multiselect,
|
||||
DatetimePicker,
|
||||
VueEasymde,
|
||||
VueEasymde: () => import('vue-easymde/dist/VueEasyMDE.common'),
|
||||
Actions,
|
||||
ActionButton,
|
||||
Avatar,
|
||||
@@ -209,6 +213,8 @@ export default {
|
||||
},
|
||||
lastModifiedRelative: null,
|
||||
lastCreatedRemative: null,
|
||||
descriptionSaveTimeout: null,
|
||||
descriptionSaving: false,
|
||||
hasActivity: capabilities && capabilities.activity,
|
||||
hasComments: window.OCP && window.OCP.Comments,
|
||||
}
|
||||
@@ -270,7 +276,7 @@ export default {
|
||||
this.copiedCard = JSON.parse(JSON.stringify(this.currentCard))
|
||||
this.allLabels = this.currentCard.labels
|
||||
|
||||
if (this.currentCard.assignedUsers.length > 0) {
|
||||
if (this.currentCard.assignedUsers && this.currentCard.assignedUsers.length > 0) {
|
||||
this.assignedUsers = this.currentCard.assignedUsers.map((item) => item.participant)
|
||||
} else {
|
||||
this.assignedUsers = []
|
||||
@@ -299,8 +305,20 @@ export default {
|
||||
this.copiedCard.duedate = null
|
||||
this.$store.dispatch('updateCardDue', this.copiedCard)
|
||||
},
|
||||
saveDesc() {
|
||||
this.$store.dispatch('updateCardDesc', this.copiedCard)
|
||||
updateDescription(text) {
|
||||
this.copiedCard.description = text
|
||||
this.copiedCard.descriptionLastEdit = Date.now()
|
||||
clearTimeout(this.descriptionSaveTimeout)
|
||||
this.descriptionSaveTimeout = setTimeout(async() => {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.copiedCard, 'descriptionLastEdit') || this.descriptionSaving) {
|
||||
return
|
||||
}
|
||||
this.descriptionSaving = true
|
||||
await this.$store.dispatch('updateCardDesc', this.copiedCard)
|
||||
delete this.copiedCard.descriptionLastEdit
|
||||
this.descriptionSaving = false
|
||||
}, 2500)
|
||||
|
||||
},
|
||||
|
||||
closeSidebar() {
|
||||
|
||||
@@ -28,13 +28,24 @@
|
||||
<input ref="localAttachments"
|
||||
type="file"
|
||||
style="display: none;"
|
||||
multiple
|
||||
@change="handleUploadFile">
|
||||
<div class="attachment-list">
|
||||
<ul>
|
||||
<li v-for="attachment in uploadQueue" :key="attachment.name" class="attachment">
|
||||
<a class="fileicon" :style="mimetypeForAttachment('none')" />
|
||||
<div class="details">
|
||||
<a>
|
||||
<div class="filename">
|
||||
<span class="basename">{{ attachment.name }}</span>
|
||||
</div>
|
||||
<progress :value="attachment.progress" max="100" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li v-for="attachment in attachments"
|
||||
:key="attachment.id"
|
||||
class="attachment"
|
||||
style="display: flex;">
|
||||
class="attachment">
|
||||
<a class="fileicon" :style="mimetypeForAttachment(attachment.extendedData.mimetype)" :href="attachmentUrl(attachment)" />
|
||||
<div class="details">
|
||||
<a :href="attachmentUrl(attachment)" target="_blank">
|
||||
@@ -109,7 +120,7 @@ export default {
|
||||
}
|
||||
},
|
||||
attachments() {
|
||||
return this.$store.getters.attachmentsByCard(this.card.id)
|
||||
return [...this.$store.getters.attachmentsByCard(this.card.id)].sort((a, b) => b.id - a.id)
|
||||
},
|
||||
formattedFileSize() {
|
||||
return (filesize) => formatFileSize(filesize)
|
||||
@@ -134,22 +145,11 @@ export default {
|
||||
this.$store.dispatch('fetchAttachments', this.card.id)
|
||||
},
|
||||
methods: {
|
||||
dragEnter() {
|
||||
|
||||
},
|
||||
dragLeave() {
|
||||
|
||||
},
|
||||
handleDropFiles(event) {
|
||||
this.isDraggingOver = false
|
||||
if (this.isReadOnly) {
|
||||
return
|
||||
}
|
||||
this.onLocalAttachmentSelected(event.dataTransfer.files[0])
|
||||
event.dataTransfer.value = ''
|
||||
},
|
||||
handleUploadFile(event) {
|
||||
this.onLocalAttachmentSelected(event.target.files[0])
|
||||
const files = event.target.files ?? []
|
||||
for (const file of files) {
|
||||
this.onLocalAttachmentSelected(file)
|
||||
}
|
||||
event.target.value = ''
|
||||
},
|
||||
clickAddNewAttachmment() {
|
||||
@@ -214,6 +214,7 @@ export default {
|
||||
li.attachment {
|
||||
display: flex;
|
||||
padding: 3px;
|
||||
min-height: 44px;
|
||||
|
||||
&.deleted {
|
||||
opacity: .5;
|
||||
|
||||
@@ -22,16 +22,10 @@
|
||||
|
||||
<template>
|
||||
<div class="badges">
|
||||
<div v-if="card.description" class="icon icon-edit" />
|
||||
|
||||
<div v-if="card.commentsUnread > 0" class="icon icon-comment" />
|
||||
|
||||
<div v-if="card.duedate" :class="dueIcon">
|
||||
<span>{{ relativeDate }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="card.description && checkListCount > 0" class="card-tasks icon icon-checkmark">
|
||||
<span>{{ checkListCheckedCount }}/{{ checkListCount }}</span>
|
||||
{{ checkListCheckedCount }}/{{ checkListCount }}
|
||||
</div>
|
||||
|
||||
<div v-if="card.attachmentCount > 0" class="icon-attach icon icon-attach-dark">
|
||||
@@ -39,22 +33,79 @@
|
||||
</div>
|
||||
|
||||
<AvatarList :users="card.assignedUsers" />
|
||||
|
||||
<div @click.stop.prevent>
|
||||
<Actions v-if="canEdit">
|
||||
<ActionButton v-if="showArchived === false" icon="icon-user" @click="assignCardToMe()">
|
||||
{{ t('deck', 'Assign to me') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-archive" @click="archiveUnarchiveCard()">
|
||||
{{ t('deck', (showArchived ? 'Unarchive card' : 'Archive card')) }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="showArchived === false" icon="icon-delete" @click="deleteCard()">
|
||||
{{ t('deck', 'Delete card') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-external" @click.stop="modalShow=true">
|
||||
{{ t('deck', 'Move card') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-settings-dark" @click="openCard">
|
||||
{{ t('deck', 'Card details') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
</div>
|
||||
<Modal v-if="modalShow" title="Move card to another board" @close="modalShow=false">
|
||||
<div class="modal__content">
|
||||
<Multiselect v-model="selectedBoard"
|
||||
:placeholder="t('deck', 'Select a board')"
|
||||
:options="boards"
|
||||
label="title"
|
||||
@select="loadStacksFromBoard" />
|
||||
<Multiselect v-model="selectedStack"
|
||||
:placeholder="t('deck', 'Select a stack')"
|
||||
:options="stacksFromBoard"
|
||||
label="title" />
|
||||
|
||||
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
|
||||
{{ t('deck', 'Move card') }}
|
||||
</button>
|
||||
<button @click="modalShow=false">
|
||||
{{ t('deck', 'Cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AvatarList from './AvatarList'
|
||||
import moment from '@nextcloud/moment'
|
||||
import { Modal, Actions, ActionButton, Multiselect } from '@nextcloud/vue'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
export default {
|
||||
name: 'CardBadges',
|
||||
components: { AvatarList },
|
||||
components: { AvatarList, Actions, ActionButton, Modal, Multiselect },
|
||||
props: {
|
||||
id: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalShow: false,
|
||||
selectedBoard: '',
|
||||
selectedStack: '',
|
||||
stacksFromBoard: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'canEdit',
|
||||
]),
|
||||
...mapState({
|
||||
showArchived: state => state.showArchived,
|
||||
currentBoard: state => state.currentBoard,
|
||||
}),
|
||||
checkListCount() {
|
||||
return (this.card.description.match(/\[\s*\x*\]/g) || []).length
|
||||
},
|
||||
@@ -67,28 +118,53 @@ export default {
|
||||
card() {
|
||||
return this.$store.getters.cardById(this.id)
|
||||
},
|
||||
dueDateTooltip() {
|
||||
return moment(this.card.duedate).format('LLLL')
|
||||
isBoardAndStackChoosen() {
|
||||
if (this.selectedBoard === '' || this.selectedStack === '') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
relativeDate() {
|
||||
const diff = moment(this.$root.time).diff(this.card.duedate, 'seconds')
|
||||
if (diff >= 0 && diff < 45) {
|
||||
return t('core', 'seconds ago')
|
||||
}
|
||||
return moment(this.card.duedate).fromNow()
|
||||
boards() {
|
||||
return this.$store.getters.boards.filter(board => {
|
||||
return board.id !== this.currentBoard.id
|
||||
})
|
||||
},
|
||||
dueIcon() {
|
||||
const days = Math.floor(moment(this.card.duedate).diff(this.$root.time, 'seconds') / 60 / 60 / 24)
|
||||
if (days < 0) {
|
||||
return 'icon-calendar due icon overdue'
|
||||
},
|
||||
methods: {
|
||||
openCard() {
|
||||
this.$router.push({ name: 'card', params: { cardId: this.id } })
|
||||
},
|
||||
deleteCard() {
|
||||
this.$store.dispatch('deleteCard', this.card)
|
||||
},
|
||||
archiveUnarchiveCard() {
|
||||
this.$store.dispatch('archiveUnarchiveCard', { ...this.card, archived: !this.card.archived })
|
||||
},
|
||||
assignCardToMe() {
|
||||
this.copiedCard = Object.assign({}, this.card)
|
||||
this.$store.dispatch('assignCardToUser', {
|
||||
card: this.copiedCard,
|
||||
assignee: {
|
||||
userId: OC.getCurrentUser().uid,
|
||||
type: 0,
|
||||
},
|
||||
})
|
||||
},
|
||||
moveCard() {
|
||||
this.copiedCard = Object.assign({}, this.card)
|
||||
this.copiedCard.stackId = this.selectedStack.id
|
||||
this.$store.dispatch('moveCard', this.copiedCard)
|
||||
this.modalShow = false
|
||||
},
|
||||
async loadStacksFromBoard(board) {
|
||||
try {
|
||||
console.debug(board)
|
||||
const url = OC.generateUrl('/apps/deck/stacks/' + board.id)
|
||||
const response = await axios.get(url)
|
||||
this.stacksFromBoard = response.data
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
if (days === 0) {
|
||||
return 'icon-calendar-dark due icon now'
|
||||
}
|
||||
if (days === 1) {
|
||||
return 'icon-calendar-dark due icon next'
|
||||
}
|
||||
return 'icon-calendar-dark due icon'
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -102,7 +178,7 @@ export default {
|
||||
|
||||
.icon {
|
||||
opacity: 0.5;
|
||||
padding: 12px 14px;
|
||||
padding: 12px 18px;
|
||||
padding-right: 4px;
|
||||
margin-right: 5px;
|
||||
background-position: left;
|
||||
@@ -153,4 +229,27 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .125s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
width: 25vw;
|
||||
min-width: 250px;
|
||||
height: 120px;
|
||||
text-align: center;
|
||||
margin: 20px 20px 60px 20px;
|
||||
|
||||
.multiselect {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal__content button {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -47,44 +47,11 @@
|
||||
<input type="button" class="icon-confirm" @click="finishedEdit(card)">
|
||||
</form>
|
||||
|
||||
<Actions v-if="canEdit && !editing" @click.stop.prevent>
|
||||
<ActionButton v-if="showArchived === false" icon="icon-user" @click="assignCardToMe()">
|
||||
{{ t('deck', 'Assign to me') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-archive" @click="archiveUnarchiveCard()">
|
||||
{{ t('deck', (showArchived ? 'Unarchive card' : 'Archive card')) }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="showArchived === false" icon="icon-delete" @click="deleteCard()">
|
||||
{{ t('deck', 'Delete card') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-external" @click.stop="modalShow=true">
|
||||
{{ t('deck', 'Move card') }}
|
||||
</ActionButton>
|
||||
<ActionButton icon="icon-settings-dark" @click="openCard">
|
||||
{{ t('deck', 'Card details') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
|
||||
<Modal v-if="modalShow" title="Move card to another board" @close="modalShow=false">
|
||||
<div class="modal__content">
|
||||
<Multiselect v-model="selectedBoard"
|
||||
:placeholder="t('deck', 'Select a board')"
|
||||
:options="boards"
|
||||
label="title"
|
||||
@select="loadStacksFromBoard" />
|
||||
<Multiselect v-model="selectedStack"
|
||||
:placeholder="t('deck', 'Select a stack')"
|
||||
:options="stacksFromBoard"
|
||||
label="title" />
|
||||
|
||||
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="moveCard">
|
||||
{{ t('deck', 'Move card') }}
|
||||
</button>
|
||||
<button @click="modalShow=false">
|
||||
{{ t('deck', 'Cancel') }}
|
||||
</button>
|
||||
<div v-if="!editing" class="right">
|
||||
<div v-if="card.duedate" :class="dueIcon">
|
||||
<span>{{ relativeDate }}</span>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</div>
|
||||
<ul v-if="card.labels && card.labels.length > 0" class="labels" @click="openCard">
|
||||
<li v-for="label in card.labels" :key="label.id" :style="labelStyle(label)">
|
||||
@@ -99,11 +66,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Modal, Actions, ActionButton, Multiselect } from '@nextcloud/vue'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
||||
import moment from 'moment'
|
||||
import CardBadges from './CardBadges'
|
||||
import Color from '../../mixins/color'
|
||||
import labelStyle from '../../mixins/labelStyle'
|
||||
@@ -111,7 +76,7 @@ import AttachmentDragAndDrop from '../AttachmentDragAndDrop'
|
||||
|
||||
export default {
|
||||
name: 'CardItem',
|
||||
components: { Modal, CardBadges, Actions, ActionButton, Multiselect, AttachmentDragAndDrop },
|
||||
components: { CardBadges, AttachmentDragAndDrop },
|
||||
directives: {
|
||||
ClickOutside,
|
||||
},
|
||||
@@ -124,13 +89,8 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuOpened: false,
|
||||
editing: false,
|
||||
copiedCard: '',
|
||||
modalShow: false,
|
||||
selectedBoard: '',
|
||||
selectedStack: '',
|
||||
stacksFromBoard: [],
|
||||
copiedCard: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -145,34 +105,37 @@ export default {
|
||||
card() {
|
||||
return this.$store.getters.cardById(this.id)
|
||||
},
|
||||
boards() {
|
||||
return this.$store.getters.boards.filter(board => {
|
||||
return board.id !== this.currentBoard.id
|
||||
})
|
||||
},
|
||||
menu() {
|
||||
return []
|
||||
},
|
||||
currentCard() {
|
||||
return this.$route.params.cardId === this.id
|
||||
},
|
||||
isBoardAndStackChoosen() {
|
||||
if (this.selectedBoard === '' || this.selectedStack === '') {
|
||||
return false
|
||||
relativeDate() {
|
||||
const diff = moment(this.$root.time).diff(this.card.duedate, 'seconds')
|
||||
if (diff >= 0 && diff < 45) {
|
||||
return t('core', 'seconds ago')
|
||||
}
|
||||
return true
|
||||
return moment(this.card.duedate).fromNow()
|
||||
},
|
||||
dueIcon() {
|
||||
const days = Math.floor(moment(this.card.duedate).diff(this.$root.time, 'seconds') / 60 / 60 / 24)
|
||||
if (days < 0) {
|
||||
return 'icon-calendar due icon overdue'
|
||||
}
|
||||
if (days === 0) {
|
||||
return 'icon-calendar-dark due icon now'
|
||||
}
|
||||
if (days === 1) {
|
||||
return 'icon-calendar-dark due icon next'
|
||||
}
|
||||
return 'icon-calendar-dark due icon'
|
||||
},
|
||||
dueDateTooltip() {
|
||||
return moment(this.card.duedate).format('LLLL')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openCard() {
|
||||
this.$router.push({ name: 'card', params: { cardId: this.id } })
|
||||
},
|
||||
togglePopoverMenu() {
|
||||
this.menuOpened = !this.menuOpened
|
||||
},
|
||||
hidePopoverMenu() {
|
||||
this.menuOpened = false
|
||||
},
|
||||
startEditing(card) {
|
||||
this.copiedCard = Object.assign({}, card)
|
||||
this.editing = true
|
||||
@@ -186,39 +149,6 @@ export default {
|
||||
cancelEdit() {
|
||||
this.editing = false
|
||||
},
|
||||
deleteCard() {
|
||||
this.$store.dispatch('deleteCard', this.card)
|
||||
},
|
||||
archiveUnarchiveCard() {
|
||||
this.copiedCard = Object.assign({}, this.card)
|
||||
this.copiedCard.archived = !this.copiedCard.archived
|
||||
this.$store.dispatch('archiveUnarchiveCard', this.copiedCard)
|
||||
},
|
||||
assignCardToMe() {
|
||||
this.copiedCard = Object.assign({}, this.card)
|
||||
this.$store.dispatch('assignCardToUser', {
|
||||
card: this.copiedCard,
|
||||
assignee: {
|
||||
userId: OC.getCurrentUser().uid,
|
||||
type: 0,
|
||||
},
|
||||
})
|
||||
},
|
||||
async loadStacksFromBoard(board) {
|
||||
try {
|
||||
const url = OC.generateUrl('/apps/deck/stacks/' + board.id)
|
||||
const response = await axios.get(url)
|
||||
this.stacksFromBoard = response.data
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
},
|
||||
moveCard() {
|
||||
this.copiedCard = Object.assign({}, this.card)
|
||||
this.copiedCard.stackId = this.selectedStack.id
|
||||
this.$store.dispatch('moveCard', this.copiedCard)
|
||||
this.modalShow = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -227,13 +157,6 @@ export default {
|
||||
$card-spacing: 10px;
|
||||
$card-padding: 10px;
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .125s;
|
||||
}
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
body.dark .card {
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
@@ -290,8 +213,8 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
padding: 1px 3px;
|
||||
border-radius: 3px;
|
||||
padding: 3px 7px;
|
||||
border-radius: 15px;
|
||||
font-size: 85%;
|
||||
margin-right: 3px;
|
||||
margin-bottom: 3px;
|
||||
@@ -314,13 +237,56 @@ export default {
|
||||
.card-controls {
|
||||
display: flex;
|
||||
margin-left: $card-padding;
|
||||
margin-right: $card-padding;
|
||||
& > div {
|
||||
display: flex;
|
||||
max-height: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.icon.due {
|
||||
background-position: 4px center;
|
||||
border-radius: 3px;
|
||||
margin-top: 9px;
|
||||
margin-bottom: 9px;
|
||||
padding: 3px 4px;
|
||||
font-size: 90%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: .5;
|
||||
flex-shrink: 1;
|
||||
z-index: 2;
|
||||
|
||||
.icon {
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
&.overdue {
|
||||
background-color: var(--color-error);
|
||||
color: var(--color-primary-text);
|
||||
opacity: .7;
|
||||
}
|
||||
&.now {
|
||||
background-color: var(--color-warning);
|
||||
opacity: .7;
|
||||
}
|
||||
&.next {
|
||||
background-color: var(--color-background-dark);
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 20px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.compact {
|
||||
min-height: 50px;
|
||||
@@ -340,20 +306,4 @@ export default {
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
width: 25vw;
|
||||
min-width: 250px;
|
||||
height: 120px;
|
||||
text-align: center;
|
||||
margin: 20px 20px 60px 20px;
|
||||
|
||||
.multiselect {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal__content button {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -30,6 +30,9 @@ import { Tooltip } from '@nextcloud/vue'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
import './models'
|
||||
|
||||
// the server snap.js conflicts with vertical scrolling so we disable it
|
||||
document.body.setAttribute('data-snap-ignore', 'true')
|
||||
|
||||
// eslint-disable-next-line
|
||||
__webpack_nonce__ = btoa(OC.requestToken)
|
||||
// eslint-disable-next-line
|
||||
|
||||
@@ -21,8 +21,16 @@
|
||||
*/
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { formatFileSize } from '@nextcloud/files'
|
||||
import PQueue from 'p-queue'
|
||||
|
||||
const queue = new PQueue({ concurrency: 2 })
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploadQueue: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onLocalAttachmentSelected(file) {
|
||||
if (this.maxUploadSize > 0 && file.size > this.maxUploadSize) {
|
||||
@@ -34,20 +42,32 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
this.$set(this.uploadQueue, file.name, { name: file.name, progress: 0 })
|
||||
const bodyFormData = new FormData()
|
||||
bodyFormData.append('cardId', this.cardId)
|
||||
bodyFormData.append('type', 'deck_file')
|
||||
bodyFormData.append('file', file)
|
||||
try {
|
||||
await this.$store.dispatch('createAttachment', { cardId: this.cardId, formData: bodyFormData })
|
||||
} catch (err) {
|
||||
if (err.response.data.status === 409) {
|
||||
this.overwriteAttachment = err.response.data.data
|
||||
this.modalShow = true
|
||||
} else {
|
||||
showError(err.response.data.message)
|
||||
await queue.add(async() => {
|
||||
try {
|
||||
await this.$store.dispatch('createAttachment', { cardId: this.cardId,
|
||||
formData: bodyFormData,
|
||||
onUploadProgress: (e) => {
|
||||
const percentCompleted = Math.round((e.loaded * 100) / e.total)
|
||||
console.debug(percentCompleted)
|
||||
this.$set(this.uploadQueue[file.name], 'progress', percentCompleted)
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
if (err.response.data.status === 409) {
|
||||
this.overwriteAttachment = err.response.data.data
|
||||
this.modalShow = true
|
||||
} else {
|
||||
showError(err.response.data.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$delete(this.uploadQueue, file.name)
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
overrideAttachment() {
|
||||
|
||||
@@ -37,11 +37,12 @@ export class AttachmentApi {
|
||||
return response.data
|
||||
}
|
||||
|
||||
async createAttachment({ cardId, formData }) {
|
||||
async createAttachment({ cardId, formData, onUploadProgress }) {
|
||||
const response = await axios({
|
||||
method: 'POST',
|
||||
url: this.url(`/cards/${cardId}/attachment`),
|
||||
data: formData,
|
||||
onUploadProgress,
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -83,8 +83,8 @@ export default {
|
||||
commit('createAttachments', { cardId, attachments })
|
||||
},
|
||||
|
||||
async createAttachment({ commit }, { cardId, formData }) {
|
||||
const attachment = await apiClient.createAttachment({ cardId, formData })
|
||||
async createAttachment({ commit }, { cardId, formData, onUploadProgress }) {
|
||||
const attachment = await apiClient.createAttachment({ cardId, formData, onUploadProgress })
|
||||
commit('createAttachment', { cardId, attachment })
|
||||
commit('cardIncreaseAttachmentCount', cardId)
|
||||
},
|
||||
|
||||
@@ -76,10 +76,16 @@ export default {
|
||||
|
||||
return true
|
||||
})
|
||||
.filter((card) => card.stackId === id && (getters.getSearchQuery === ''
|
||||
|| (card.title.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
|
||||
|| card.description.toLowerCase().includes(getters.getSearchQuery.toLowerCase()))
|
||||
.sort((a, b) => a.order - b.order)))
|
||||
.filter((card) => card.stackId === id)
|
||||
.filter((card) => {
|
||||
if (getters.getSearchQuery === '') {
|
||||
return true
|
||||
}
|
||||
|
||||
return card.title.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
|
||||
|| card.description.toLowerCase().includes(getters.getSearchQuery.toLowerCase())
|
||||
})
|
||||
.sort((a, b) => a.order - b.order)
|
||||
},
|
||||
cardById: state => (id) => {
|
||||
return state.cards.find((card) => card.id === id)
|
||||
|
||||
@@ -34,6 +34,7 @@ use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\ChangeHelper;
|
||||
use OCA\Deck\Db\LabelMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\Notification\NotificationHelper;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
@@ -260,6 +261,93 @@ class BoardServiceTest extends TestCase {
|
||||
));
|
||||
}
|
||||
|
||||
public function dataAddAclExtendPermission() {
|
||||
return [
|
||||
[[false, false, false], [false, false, false], [false, false, false]],
|
||||
[[false, false, false], [true, true, true], [false, false, false]],
|
||||
|
||||
// user has share permissions -> can only reshare with those
|
||||
[[false, true, false], [false, false, false], [false, false, false]],
|
||||
[[false, true, false], [false, true, false], [false, true, false]],
|
||||
[[false, true, false], [true, true, true], [false, true, false]],
|
||||
|
||||
// user has write permissions -> can only reshare with those
|
||||
[[true, true, false], [false, false, false], [false, false, false]],
|
||||
[[true, true, false], [false, true, false], [false, true, false]],
|
||||
[[true, true, false], [true, true, true], [true, true, false]],
|
||||
|
||||
// user has manage permissions -> can upgrade acl permissions
|
||||
[[false, false, true], [true, true, true], [true, true, true]],
|
||||
[[true, true, true], [false, false, true], [false, false, true]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataAddAclExtendPermission
|
||||
* @param $currentUserAcl
|
||||
* @param $providedAcl
|
||||
* @param $resultingAcl
|
||||
* @throws NoPermissionException
|
||||
* @throws \OCA\Deck\BadRequestException
|
||||
*/
|
||||
public function testAddAclExtendPermission($currentUserAcl, $providedAcl, $resultingAcl) {
|
||||
$existingAcl = new Acl();
|
||||
$existingAcl->setBoardId(123);
|
||||
$existingAcl->setType('user');
|
||||
$existingAcl->setParticipant('admin');
|
||||
$existingAcl->setPermissionEdit($currentUserAcl[0]);
|
||||
$existingAcl->setPermissionShare($currentUserAcl[1]);
|
||||
$existingAcl->setPermissionManage($currentUserAcl[2]);
|
||||
$this->permissionService->expects($this->at(0))
|
||||
->method('checkPermission')
|
||||
->with($this->boardMapper, 123, Acl::PERMISSION_SHARE, null);
|
||||
if ($currentUserAcl[2]) {
|
||||
$this->permissionService->expects($this->at(1))
|
||||
->method('checkPermission')
|
||||
->with($this->boardMapper, 123, Acl::PERMISSION_MANAGE, null);
|
||||
} else {
|
||||
$this->aclMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
->willReturn([$existingAcl]);
|
||||
$this->permissionService->expects($this->at(1))
|
||||
->method('checkPermission')
|
||||
->with($this->boardMapper, 123, Acl::PERMISSION_MANAGE, null)
|
||||
->willThrowException(new NoPermissionException('No permission'));
|
||||
$this->permissionService->expects($this->at(2))
|
||||
->method('userCan')
|
||||
->willReturn($currentUserAcl[0]);
|
||||
$this->permissionService->expects($this->at(3))
|
||||
->method('userCan')
|
||||
->willReturn($currentUserAcl[1]);
|
||||
$this->permissionService->expects($this->at(4))
|
||||
->method('userCan')
|
||||
->willReturn($currentUserAcl[2]);
|
||||
}
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('admin');
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId(123);
|
||||
$acl->setType('user');
|
||||
$acl->setParticipant('admin');
|
||||
$acl->setPermissionEdit($resultingAcl[0]);
|
||||
$acl->setPermissionShare($resultingAcl[1]);
|
||||
$acl->setPermissionManage($resultingAcl[2]);
|
||||
$acl->resolveRelation('participant', function($participant) use (&$user) {
|
||||
return null;
|
||||
});
|
||||
$this->notificationHelper->expects($this->once())
|
||||
->method('sendBoardShared');
|
||||
$expected = clone $acl;
|
||||
$this->aclMapper->expects($this->once())
|
||||
->method('insert')
|
||||
->with($acl)
|
||||
->willReturn($acl);
|
||||
$this->assertEquals($expected, $this->service->addAcl(
|
||||
123, 'user', 'admin', $providedAcl[0], $providedAcl[1], $providedAcl[2]
|
||||
));
|
||||
}
|
||||
|
||||
public function testUpdateAcl() {
|
||||
$acl = new Acl();
|
||||
$acl->setBoardId(123);
|
||||
|
||||
Reference in New Issue
Block a user