Compare commits

..

16 Commits

Author SHA1 Message Date
Luka Trovic
386131774d fix: update comment input ui
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-25 14:20:09 +01:00
Luka Trovic
97b7c1a2f2 Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-03-23 13:22:06 +01:00
Luka Trovic
10a1c68917 feat: show title and tab buttons at the top while scrolling
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-23 13:18:40 +01:00
Luka Trovic
83b739d117 style: update modal UI styles
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-03-21 09:12:43 +01:00
Luka Trovic
eef33ac220 Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-03-21 09:08:40 +01:00
Luka Trovic
69896d5cca feat: improve tabs behavior & style
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-16 09:14:59 +01:00
Luka Trovic
021d55226b fix: feedback
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-14 10:38:55 +01:00
Luka Trovic
6c59f52c31 style: fix some UI issues
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-10 12:36:47 +01:00
Luka Trovic
7d414891f9 feat: add attachments tab 2022-02-09 22:00:44 +01:00
Luka Trovic
3cd94f330f feat: project and comments tabs
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-08 10:42:54 +01:00
Luka Trovic
00a3c8e20f Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-02-06 10:56:31 -08:00
Luka Trovic
309fbc735c Merge branch 'master' of next.github.com:nextcloud/deck into feature/update-card-modal-ui 2022-02-04 15:28:52 +01:00
Luka Trovic
bcaa74b33f feat: add due date tab
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-02-04 15:24:03 +01:00
Luka Trovic
427f960c80 add tags tab
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-31 15:51:47 +01:00
Luka Trovic
3ad4fbd96c feat: add members tab
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-27 13:05:43 +01:00
Luka Trovic
49dccb6199 feat: add a new CardModal component
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2022-01-25 15:54:36 +01:00
51 changed files with 1205 additions and 215 deletions

View File

@@ -20,7 +20,7 @@ jobs:
- name: Set up npm7
run: npm i -g npm@7
- name: Setup PHP
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: '7.4'
tools: composer

View File

@@ -66,7 +66,7 @@ jobs:
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
- name: Set up php ${{ env.PHP_VERSION }}
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ env.PHP_VERSION }}
coverage: none

View File

@@ -18,7 +18,7 @@ jobs:
steps:
- name: Add reaction on start
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
@@ -37,7 +37,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
uses: peter-evans/create-or-update-comment@v2
uses: peter-evans/create-or-update-comment@v1
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}

View File

@@ -61,7 +61,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -13,13 +13,13 @@ jobs:
strategy:
matrix:
php-versions: ['7.4', '8.0', "8.1"]
php-versions: ['7.4', '8.0']
name: php${{ matrix.php-versions }} lint
steps:
- uses: actions/checkout@v3
- name: Set up php${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ matrix.php-versions }}
coverage: none
@@ -33,7 +33,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Set up php
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: 7.4
coverage: none

View File

@@ -25,7 +25,7 @@ jobs:
- name: Set up npm7
run: npm i -g npm@7
- name: Setup PHP
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: '7.4'
tools: composer

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.0', "8.1"]
php-versions: ['7.4', '8.0']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['master']
@@ -62,7 +62,7 @@ jobs:
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit

View File

@@ -18,7 +18,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Set up php
uses: shivammathur/setup-php@2.18.0
uses: shivammathur/setup-php@2.17.1
with:
php-version: 7.4
tools: composer:v1

24
composer.lock generated
View File

@@ -1675,16 +1675,16 @@
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.6.1",
"version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "77a32518733312af16a44300404e945338981de3"
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
"reference": "77a32518733312af16a44300404e945338981de3",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"shasum": ""
},
"require": {
@@ -1719,9 +1719,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0"
},
"time": "2022-03-15T21:29:03+00:00"
"time": "2022-01-04T19:58:01+00:00"
},
{
"name": "phpspec/prophecy",
@@ -2110,16 +2110,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.5.20",
"version": "9.5.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba"
"reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba",
"reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35ea4b7f3acabb26f4bb640f8c30866c401da807",
"reference": "35ea4b7f3acabb26f4bb640f8c30866c401da807",
"shasum": ""
},
"require": {
@@ -2197,7 +2197,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.19"
},
"funding": [
{
@@ -2209,7 +2209,7 @@
"type": "github"
}
],
"time": "2022-04-01T12:37:26+00:00"
"time": "2022-03-15T09:57:31+00:00"
},
{
"name": "psr/cache",

3
img/flash-black.svg Normal file
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"><g><g><polygon points="426.667,213.333 288.36,213.333 333.706,0 148.817,0 85.333,298.667 227.556,298.667 227.556,512 " /></g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></svg>

After

Width:  |  Height:  |  Size: 594 B

1
img/plus.svg Normal file
View File

@@ -0,0 +1 @@
<svg fill="#26e07f" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px"><path fill-rule="evenodd" d="M 11 2 L 11 11 L 2 11 L 2 13 L 11 13 L 11 22 L 13 22 L 13 13 L 22 13 L 22 11 L 13 11 L 13 2 Z"/></svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "Může upravovat",
"Can share" : "Může sdílet",
"Can manage" : "Může spravovat",
"Owner" : "Vlastník",
"Delete" : "Smazat",
"Failed to create share with {displayName}" : "Nepodařilo se vytvořit sdílení s {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Opravdu chcete předat vlastnictví tabule {title} uživateli {user}?",
"Transfer the board." : "Předat vlastnictví tabule.",
"Transfer" : "Předat vlastnictví",
"Transfer the board for {user} successfully" : "Předání vlastnictví tabule uživateli {user} úspěšné",
"Failed to transfer the board for {user}" : "Nepodařilo se předat vlastnictví tabule uživateli {user}",
"Add a new list" : "Přidat nový sloupec",
"Archive all cards" : "Archivovat všechny karty",
"Delete list" : "Smazat seznam",

View File

@@ -168,14 +168,8 @@
"Can edit" : "Může upravovat",
"Can share" : "Může sdílet",
"Can manage" : "Může spravovat",
"Owner" : "Vlastník",
"Delete" : "Smazat",
"Failed to create share with {displayName}" : "Nepodařilo se vytvořit sdílení s {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Opravdu chcete předat vlastnictví tabule {title} uživateli {user}?",
"Transfer the board." : "Předat vlastnictví tabule.",
"Transfer" : "Předat vlastnictví",
"Transfer the board for {user} successfully" : "Předání vlastnictví tabule uživateli {user} úspěšné",
"Failed to transfer the board for {user}" : "Nepodařilo se předat vlastnictví tabule uživateli {user}",
"Add a new list" : "Přidat nový sloupec",
"Archive all cards" : "Archivovat všechny karty",
"Delete list" : "Smazat seznam",

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "kann bearbeiten",
"Can share" : "kann teilen",
"Can manage" : "kann verwalten",
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Möchtest Du wirklich das Board {title} an {user} übertragen?",
"Transfer the board." : "Board übertragen",
"Transfer" : "Übertragen",
"Transfer the board for {user} successfully" : "Das Board wurde erfolgreich an {user} übertragen",
"Failed to transfer the board for {user}" : "Board konnte nicht an {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -168,14 +168,8 @@
"Can edit" : "kann bearbeiten",
"Can share" : "kann teilen",
"Can manage" : "kann verwalten",
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Möchtest Du wirklich das Board {title} an {user} übertragen?",
"Transfer the board." : "Board übertragen",
"Transfer" : "Übertragen",
"Transfer the board for {user} successfully" : "Das Board wurde erfolgreich an {user} übertragen",
"Failed to transfer the board for {user}" : "Board konnte nicht an {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "kann bearbeiten",
"Can share" : "kann teilen",
"Can manage" : "kann verwalten",
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Möchten Sie wirklich Das Board {title} an {user} übertragen?",
"Transfer the board." : "Board übertragen.",
"Transfer" : "Übertragen",
"Transfer the board for {user} successfully" : "Das Board wurde erfolgreich an {user} übertragen",
"Failed to transfer the board for {user}" : "Board konnte nicht an {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -168,14 +168,8 @@
"Can edit" : "kann bearbeiten",
"Can share" : "kann teilen",
"Can manage" : "kann verwalten",
"Owner" : "Besitzer",
"Delete" : "Löschen",
"Failed to create share with {displayName}" : "Fehler beim Erstellen der Freigabe mit dem Namen {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Möchten Sie wirklich Das Board {title} an {user} übertragen?",
"Transfer the board." : "Board übertragen.",
"Transfer" : "Übertragen",
"Transfer the board for {user} successfully" : "Das Board wurde erfolgreich an {user} übertragen",
"Failed to transfer the board for {user}" : "Board konnte nicht an {user} übertragen werden",
"Add a new list" : "Eine neue Liste hinzufügen",
"Archive all cards" : "Alle Karten archivieren",
"Delete list" : "Liste löschen",

View File

@@ -170,11 +170,8 @@ OC.L10N.register(
"Can edit" : "Μπορεί να επεξεργαστεί",
"Can share" : "Μπορεί να διαμοιράσει",
"Can manage" : "Μπορεί να διαχειριστεί",
"Owner" : "Κάτοχος",
"Delete" : "Διαγραφή",
"Failed to create share with {displayName}" : "Αποτυχία δημιουργίας κοινής χρήσης με το {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} για {user}? ",
"Transfer" : "Μεταφορά",
"Add a new list" : "Προσθήκη νέας λίστας",
"Archive all cards" : "Αρχειοθέτηση όλων των καρτελών.",
"Delete list" : "Διαγραφή λίστας",

View File

@@ -168,11 +168,8 @@
"Can edit" : "Μπορεί να επεξεργαστεί",
"Can share" : "Μπορεί να διαμοιράσει",
"Can manage" : "Μπορεί να διαχειριστεί",
"Owner" : "Κάτοχος",
"Delete" : "Διαγραφή",
"Failed to create share with {displayName}" : "Αποτυχία δημιουργίας κοινής χρήσης με το {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Είστε σίγουροι ότι θέλετε να μεταφέρετε τον πίνακα {title} για {user}? ",
"Transfer" : "Μεταφορά",
"Add a new list" : "Προσθήκη νέας λίστας",
"Archive all cards" : "Αρχειοθέτηση όλων των καρτελών.",
"Delete list" : "Διαγραφή λίστας",

View File

@@ -168,10 +168,8 @@ OC.L10N.register(
"Can edit" : "Editatu dezake",
"Can share" : "Partekatu dezake",
"Can manage" : "Kudeatu dezake",
"Owner" : "Jabea",
"Delete" : "Ezabatu",
"Failed to create share with {displayName}" : "Ezin izan da {displayName}-(r)ekin partekatzea sortu",
"Transfer" : "Transferitu",
"Add a new list" : "Gehitu zerrenda berria",
"Archive all cards" : "Artxibatu txartel guztiak",
"Delete list" : "Zerrenda ezabatu",

View File

@@ -166,10 +166,8 @@
"Can edit" : "Editatu dezake",
"Can share" : "Partekatu dezake",
"Can manage" : "Kudeatu dezake",
"Owner" : "Jabea",
"Delete" : "Ezabatu",
"Failed to create share with {displayName}" : "Ezin izan da {displayName}-(r)ekin partekatzea sortu",
"Transfer" : "Transferitu",
"Add a new list" : "Gehitu zerrenda berria",
"Archive all cards" : "Artxibatu txartel guztiak",
"Delete list" : "Zerrenda ezabatu",

View File

@@ -148,9 +148,7 @@ OC.L10N.register(
"Can edit" : "Voi muokata",
"Can share" : "Voi jakaa",
"Can manage" : "Voi hallita",
"Owner" : "Omistaja",
"Delete" : "Poista",
"Transfer" : "Siirrä",
"Add a new list" : "Lisää uusi lista",
"Archive all cards" : "Arkistoi kaikki kortit",
"Delete list" : "Poista lista",
@@ -165,7 +163,6 @@ OC.L10N.register(
"Members" : "Jäsenet",
"Upload new files" : "Lähetä uusia tiedostoja",
"Add this attachment" : "Lisää tämä liite",
"Download" : "Lataa",
"Remove attachment" : "Poista liite",
"Delete Attachment" : "Poista liite",
"Restore Attachment" : "Palauta liite",
@@ -191,8 +188,6 @@ OC.L10N.register(
"Save" : "Tallenna",
"The comment cannot be empty." : "Kommentti ei voi olla tyhjä.",
"The comment cannot be longer than 1000 characters." : "Kommentin on oltava alle 1000 merkkiä pitkä.",
"In reply to" : "Vastauksena",
"Cancel reply" : "Peru vastaus",
"Reply" : "Vastaa",
"Update" : "Päivitä",
"Description" : "Kuvaus",
@@ -213,7 +208,6 @@ OC.L10N.register(
"Archive card" : "Arkistoi kortti",
"Delete card" : "Poista kortti",
"Move card to another board" : "Siirrä kortti toiselle taululle",
"List is empty" : "Lista on tyhjä",
"Card deleted" : "Kortti poistettu",
"seconds ago" : "sekuntia sitten",
"All boards" : "Kaikki taulut",
@@ -244,7 +238,6 @@ OC.L10N.register(
"Maximum file size of {size} exceeded" : "Tiedoston enimmäiskoko {size} ylitetty",
"Error creating the share" : "Virhe jakoa luotaessa",
"Share" : "Jaa",
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Pakka on Nextcloudissa työtään hallinnoivien tiimien käyttöön tarkoitettu kanban-tyyppinen organisointityökalu.\n\n\n- 📥 Lisää tehtävät korteille ja järjestele ne mielesi mukaan\n- 📄 Kirjoita lisätietoja markdown-kielellä\n- 🔖 Määritä tunnisteita helpottaaksesi hallintaa\n- 👥 Jaa tiimin, perheen tai kavereiden kanssa\n- 📎 Lisää tiedostoja ja upota ne lisätietoihin\n- 💬 Keskustele tiimisi kanssa kommenteilla\n- ⚡ Pidä kirjaa muutoksista tapahtumavirran avulla\n- 🚀 Pidä projektisi hallinnassa",
"(circle)" : "(piiri)"
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Pakka on Nextcloudissa työtään hallinnoivien tiimien käyttöön tarkoitettu kanban-tyyppinen organisointityökalu.\n\n\n- 📥 Lisää tehtävät korteille ja järjestele ne mielesi mukaan\n- 📄 Kirjoita lisätietoja markdown-kielellä\n- 🔖 Määritä tunnisteita helpottaaksesi hallintaa\n- 👥 Jaa tiimin, perheen tai kavereiden kanssa\n- 📎 Lisää tiedostoja ja upota ne lisätietoihin\n- 💬 Keskustele tiimisi kanssa kommenteilla\n- ⚡ Pidä kirjaa muutoksista tapahtumavirran avulla\n- 🚀 Pidä projektisi hallinnassa"
},
"nplurals=2; plural=(n != 1);");

View File

@@ -146,9 +146,7 @@
"Can edit" : "Voi muokata",
"Can share" : "Voi jakaa",
"Can manage" : "Voi hallita",
"Owner" : "Omistaja",
"Delete" : "Poista",
"Transfer" : "Siirrä",
"Add a new list" : "Lisää uusi lista",
"Archive all cards" : "Arkistoi kaikki kortit",
"Delete list" : "Poista lista",
@@ -163,7 +161,6 @@
"Members" : "Jäsenet",
"Upload new files" : "Lähetä uusia tiedostoja",
"Add this attachment" : "Lisää tämä liite",
"Download" : "Lataa",
"Remove attachment" : "Poista liite",
"Delete Attachment" : "Poista liite",
"Restore Attachment" : "Palauta liite",
@@ -189,8 +186,6 @@
"Save" : "Tallenna",
"The comment cannot be empty." : "Kommentti ei voi olla tyhjä.",
"The comment cannot be longer than 1000 characters." : "Kommentin on oltava alle 1000 merkkiä pitkä.",
"In reply to" : "Vastauksena",
"Cancel reply" : "Peru vastaus",
"Reply" : "Vastaa",
"Update" : "Päivitä",
"Description" : "Kuvaus",
@@ -211,7 +206,6 @@
"Archive card" : "Arkistoi kortti",
"Delete card" : "Poista kortti",
"Move card to another board" : "Siirrä kortti toiselle taululle",
"List is empty" : "Lista on tyhjä",
"Card deleted" : "Kortti poistettu",
"seconds ago" : "sekuntia sitten",
"All boards" : "Kaikki taulut",
@@ -242,7 +236,6 @@
"Maximum file size of {size} exceeded" : "Tiedoston enimmäiskoko {size} ylitetty",
"Error creating the share" : "Virhe jakoa luotaessa",
"Share" : "Jaa",
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Pakka on Nextcloudissa työtään hallinnoivien tiimien käyttöön tarkoitettu kanban-tyyppinen organisointityökalu.\n\n\n- 📥 Lisää tehtävät korteille ja järjestele ne mielesi mukaan\n- 📄 Kirjoita lisätietoja markdown-kielellä\n- 🔖 Määritä tunnisteita helpottaaksesi hallintaa\n- 👥 Jaa tiimin, perheen tai kavereiden kanssa\n- 📎 Lisää tiedostoja ja upota ne lisätietoihin\n- 💬 Keskustele tiimisi kanssa kommenteilla\n- ⚡ Pidä kirjaa muutoksista tapahtumavirran avulla\n- 🚀 Pidä projektisi hallinnassa",
"(circle)" : "(piiri)"
"Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.\n\n\n- 📥 Add your tasks to cards and put them in order\n- 📄 Write down additional notes in markdown\n- 🔖 Assign labels for even better organization\n- 👥 Share with your team, friends or family\n- 📎 Attach files and embed them in your markdown description\n- 💬 Discuss with your team using comments\n- ⚡ Keep track of changes in the activity stream\n- 🚀 Get your project organized" : "Pakka on Nextcloudissa työtään hallinnoivien tiimien käyttöön tarkoitettu kanban-tyyppinen organisointityökalu.\n\n\n- 📥 Lisää tehtävät korteille ja järjestele ne mielesi mukaan\n- 📄 Kirjoita lisätietoja markdown-kielellä\n- 🔖 Määritä tunnisteita helpottaaksesi hallintaa\n- 👥 Jaa tiimin, perheen tai kavereiden kanssa\n- 📎 Lisää tiedostoja ja upota ne lisätietoihin\n- 💬 Keskustele tiimisi kanssa kommenteilla\n- ⚡ Pidä kirjaa muutoksista tapahtumavirran avulla\n- 🚀 Pidä projektisi hallinnassa"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -292,4 +292,4 @@ OC.L10N.register(
"\"{card}\" was added to \"{board}\"" : "La carte \"{card}\" a été ajoutée au tableau \"{board}\"",
"(circle)" : "(cercle)"
},
"nplurals=3; plural=(n==0 || n==1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;");
"nplurals=2; plural=(n > 1);");

View File

@@ -289,5 +289,5 @@
"Creating the new card…" : "Création de la nouvelle carte…",
"\"{card}\" was added to \"{board}\"" : "La carte \"{card}\" a été ajoutée au tableau \"{board}\"",
"(circle)" : "(cercle)"
},"pluralForm" :"nplurals=3; plural=(n==0 || n==1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
}

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "Szerkesztheti",
"Can share" : "Megoszthatja",
"Can manage" : "Kezelheti",
"Owner" : "Tulajdonos",
"Delete" : "Törlés",
"Failed to create share with {displayName}" : "Nem lehet létrehozni a következő megosztást: {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Biztos, hogy átadja a(z) {board} tábla tulajdonjogát {user} számára?",
"Transfer the board." : "A tábla átadása.",
"Transfer" : "Átadás",
"Transfer the board for {user} successfully" : "A tábla átadása {user} számára sikeres",
"Failed to transfer the board for {user}" : "A tábla átadása {user} számára sikertelen",
"Add a new list" : "Új lista hozzáadása",
"Archive all cards" : "Az összes kártya archiválása",
"Delete list" : "Lista törlése",

View File

@@ -168,14 +168,8 @@
"Can edit" : "Szerkesztheti",
"Can share" : "Megoszthatja",
"Can manage" : "Kezelheti",
"Owner" : "Tulajdonos",
"Delete" : "Törlés",
"Failed to create share with {displayName}" : "Nem lehet létrehozni a következő megosztást: {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Biztos, hogy átadja a(z) {board} tábla tulajdonjogát {user} számára?",
"Transfer the board." : "A tábla átadása.",
"Transfer" : "Átadás",
"Transfer the board for {user} successfully" : "A tábla átadása {user} számára sikeres",
"Failed to transfer the board for {user}" : "A tábla átadása {user} számára sikertelen",
"Add a new list" : "Új lista hozzáadása",
"Archive all cards" : "Az összes kártya archiválása",
"Delete list" : "Lista törlése",

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "Może edytować",
"Can share" : "Może udostępnić",
"Can manage" : "Może zarządzać",
"Owner" : "Właściciel",
"Delete" : "Usuń",
"Failed to create share with {displayName}" : "Nie udało się utworzyć udostępnienia dla {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Czy na pewno chcesz przenieść tablicę {title} dla {user}?",
"Transfer the board." : "Przeniesienie tablicy.",
"Transfer" : "Przenieś",
"Transfer the board for {user} successfully" : "Przeniesienie tablicy dla {user} pomyślne",
"Failed to transfer the board for {user}" : "Nie udało się przenieść tablicy dla {user}",
"Add a new list" : "Dodaj nową listę",
"Archive all cards" : "Zarchiwizuj wszystkie karty",
"Delete list" : "Usuń listę",

View File

@@ -168,14 +168,8 @@
"Can edit" : "Może edytować",
"Can share" : "Może udostępnić",
"Can manage" : "Może zarządzać",
"Owner" : "Właściciel",
"Delete" : "Usuń",
"Failed to create share with {displayName}" : "Nie udało się utworzyć udostępnienia dla {displayName}",
"Are you sure you want to transfer the board {title} for {user}?" : "Czy na pewno chcesz przenieść tablicę {title} dla {user}?",
"Transfer the board." : "Przeniesienie tablicy.",
"Transfer" : "Przenieś",
"Transfer the board for {user} successfully" : "Przeniesienie tablicy dla {user} pomyślne",
"Failed to transfer the board for {user}" : "Nie udało się przenieść tablicy dla {user}",
"Add a new list" : "Dodaj nową listę",
"Archive all cards" : "Zarchiwizuj wszystkie karty",
"Delete list" : "Usuń listę",

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "Düzenleyebilir",
"Can share" : "Paylaşabilir",
"Can manage" : "Yönetebilir",
"Owner" : "Sahibi",
"Delete" : "Sil",
"Failed to create share with {displayName}" : "{displayName} ile paylaşılamadı",
"Are you sure you want to transfer the board {title} for {user}?" : "{title} panosunu {user} kullanıcısına aktarmak istediğinize emin misiniz?",
"Transfer the board." : "Panoyu aktar.",
"Transfer" : "Aktar",
"Transfer the board for {user} successfully" : "Pano {user} kullanıcısına aktarıldı",
"Failed to transfer the board for {user}" : "Pano {user} kullanıcısına aktarılamadı",
"Add a new list" : "Yeni liste ekle",
"Archive all cards" : "Tüm kartları arşivle",
"Delete list" : "Listeyi sil",

View File

@@ -168,14 +168,8 @@
"Can edit" : "Düzenleyebilir",
"Can share" : "Paylaşabilir",
"Can manage" : "Yönetebilir",
"Owner" : "Sahibi",
"Delete" : "Sil",
"Failed to create share with {displayName}" : "{displayName} ile paylaşılamadı",
"Are you sure you want to transfer the board {title} for {user}?" : "{title} panosunu {user} kullanıcısına aktarmak istediğinize emin misiniz?",
"Transfer the board." : "Panoyu aktar.",
"Transfer" : "Aktar",
"Transfer the board for {user} successfully" : "Pano {user} kullanıcısına aktarıldı",
"Failed to transfer the board for {user}" : "Pano {user} kullanıcısına aktarılamadı",
"Add a new list" : "Yeni liste ekle",
"Archive all cards" : "Tüm kartları arşivle",
"Delete list" : "Listeyi sil",

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "可以編輯",
"Can share" : "可以分享",
"Can manage" : "可以管理",
"Owner" : "所有者",
"Delete" : "刪除",
"Failed to create share with {displayName}" : "無法為 {displayName} 創建分享",
"Are you sure you want to transfer the board {title} for {user}?" : "您想要轉移 {user} 的面板 {title} 嗎?",
"Transfer the board." : "轉移面板。",
"Transfer" : "轉移",
"Transfer the board for {user} successfully" : "轉移 {user} 的面板成功",
"Failed to transfer the board for {user}" : "轉移 {user} 的面板失敗",
"Add a new list" : "添加一張新清單",
"Archive all cards" : "封存所有卡片",
"Delete list" : "刪除清單",

View File

@@ -168,14 +168,8 @@
"Can edit" : "可以編輯",
"Can share" : "可以分享",
"Can manage" : "可以管理",
"Owner" : "所有者",
"Delete" : "刪除",
"Failed to create share with {displayName}" : "無法為 {displayName} 創建分享",
"Are you sure you want to transfer the board {title} for {user}?" : "您想要轉移 {user} 的面板 {title} 嗎?",
"Transfer the board." : "轉移面板。",
"Transfer" : "轉移",
"Transfer the board for {user} successfully" : "轉移 {user} 的面板成功",
"Failed to transfer the board for {user}" : "轉移 {user} 的面板失敗",
"Add a new list" : "添加一張新清單",
"Archive all cards" : "封存所有卡片",
"Delete list" : "刪除清單",

View File

@@ -170,14 +170,8 @@ OC.L10N.register(
"Can edit" : "可以編輯",
"Can share" : "可以分享",
"Can manage" : "可以管理",
"Owner" : "擁有者",
"Delete" : "刪除",
"Failed to create share with {displayName}" : "無法建立與 {displayName} 的分享",
"Are you sure you want to transfer the board {title} for {user}?" : "您想要轉移 {user} 的看板 {title} 嗎?",
"Transfer the board." : "轉移看板。",
"Transfer" : "轉移",
"Transfer the board for {user} successfully" : "轉移 {user} 的看板成功",
"Failed to transfer the board for {user}" : "轉移 {user} 的看板失敗",
"Add a new list" : "新增列表",
"Archive all cards" : "封存所有卡片",
"Delete list" : "刪除列表",

View File

@@ -168,14 +168,8 @@
"Can edit" : "可以編輯",
"Can share" : "可以分享",
"Can manage" : "可以管理",
"Owner" : "擁有者",
"Delete" : "刪除",
"Failed to create share with {displayName}" : "無法建立與 {displayName} 的分享",
"Are you sure you want to transfer the board {title} for {user}?" : "您想要轉移 {user} 的看板 {title} 嗎?",
"Transfer the board." : "轉移看板。",
"Transfer" : "轉移",
"Transfer the board for {user} successfully" : "轉移 {user} 的看板成功",
"Failed to transfer the board for {user}" : "轉移 {user} 的看板失敗",
"Add a new list" : "新增列表",
"Archive all cards" : "封存所有卡片",
"Delete list" : "刪除列表",

View File

@@ -47,11 +47,9 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IL10N;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Event\CardCreatedEvent;
use OCP\IUserManager;
use OCA\Deck\BadRequestException;
use OCP\IURLGenerator;
@@ -675,39 +673,15 @@ class BoardService {
$stacks = $this->stackMapper->findAll($id);
foreach ($stacks as $stack) {
$this->cloneStack($stack, $newBoard->getId());
$newStack = new Stack();
$newStack->setTitle($stack->getTitle());
$newStack->setBoardId($newBoard->getId());
$this->stackMapper->insert($newStack);
}
return $newBoard;
}
private function cloneStack($stack, $newBoardId)
{
$newStack = new Stack();
$newStack->setTitle($stack->getTitle());
$newStack->setBoardId($newBoardId);
$insertedStack = $this->stackMapper->insert($newStack);
$cards = $this->cardMapper->findAll($stack->getId());
foreach ($cards as $card) {
$newCard = new Card();
$newCard->setTitle($card->getTitle());
$newCard->setStackId($insertedStack->getId());
$newCard->setType($card->getType());
$newCard->setOrder($card->getOrder());
$newCard->setOwner($card->getOwner());
$newCard->setDescription($card->getDescription());
$newCard->setDuedate($card->getDuedate());
$newCard = $this->cardMapper->insert($newCard);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_CREATE);
$this->changeHelper->cardChanged($newCard->getId(), false);
$this->eventDispatcher->dispatchTyped(new CardCreatedEvent($newCard));
}
return $stack;
}
public function transferBoardOwnership(int $boardId, string $newOwner, bool $changeContent = false): Board {
\OC::$server->getDatabaseConnection()->beginTransaction();
try {

23
package-lock.json generated
View File

@@ -20,7 +20,7 @@
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/moment": "^1.2.0",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^5.3.0",
"@nextcloud/vue": "^5.1.1",
"@nextcloud/vue-dashboard": "^2.0.1",
"blueimp-md5": "^2.19.0",
"dompurify": "^2.3.6",
@@ -2968,9 +2968,9 @@
}
},
"node_modules/@nextcloud/vue": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-5.3.0.tgz",
"integrity": "sha512-yRxEeQIgNHafvD2MC//BQXCkyyXiNQQZUGErlwOuuKpZbTX2BYTdvrN15On7rTqJNzwmwuzXAIPIFc0D8c2h+g==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-5.1.1.tgz",
"integrity": "sha512-BKJTCqlxRxFvUlc1H95vIoUFXUb6Bcub/VGlIccDg0upm/IKFkyDbuhj7gBUDz4j/uqmKAKffzPO2Q9JFFQ8wg==",
"dependencies": {
"@nextcloud/auth": "^1.2.3",
"@nextcloud/axios": "^1.3.2",
@@ -13415,9 +13415,8 @@
}
},
"node_modules/minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"version": "1.2.5",
"license": "MIT"
},
"node_modules/minimist-options": {
"version": "4.1.0",
@@ -21191,9 +21190,9 @@
}
},
"@nextcloud/vue": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-5.3.0.tgz",
"integrity": "sha512-yRxEeQIgNHafvD2MC//BQXCkyyXiNQQZUGErlwOuuKpZbTX2BYTdvrN15On7rTqJNzwmwuzXAIPIFc0D8c2h+g==",
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-5.1.1.tgz",
"integrity": "sha512-BKJTCqlxRxFvUlc1H95vIoUFXUb6Bcub/VGlIccDg0upm/IKFkyDbuhj7gBUDz4j/uqmKAKffzPO2Q9JFFQ8wg==",
"requires": {
"@nextcloud/auth": "^1.2.3",
"@nextcloud/axios": "^1.3.2",
@@ -28913,9 +28912,7 @@
}
},
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"version": "1.2.5"
},
"minimist-options": {
"version": "4.1.0",

View File

@@ -40,7 +40,7 @@
"@nextcloud/l10n": "^1.4.1",
"@nextcloud/moment": "^1.2.0",
"@nextcloud/router": "^2.0.0",
"@nextcloud/vue": "^5.3.0",
"@nextcloud/vue": "^5.1.1",
"@nextcloud/vue-dashboard": "^2.0.1",
"blueimp-md5": "^2.19.0",
"dompurify": "^2.3.6",

View File

@@ -157,10 +157,9 @@ export default {
.modal__card {
min-width: 320px;
width: 50vw;
max-width: 800px;
width: 100%;
min-height: 200px;
height: 80vh;
height: auto;
}
</style>
@@ -170,4 +169,10 @@ export default {
width: 100%;
}
.modal-wrapper--normal .modal-container{
width: 900px !important;
height: 800px !important;
overflow-y: hidden !important;
}
</style>

View File

@@ -269,7 +269,7 @@ export default {
padding-left: 44px;
background-position: 16px center;
flex-grow: 1;
height: 44px;
height: auto;
margin-bottom: 12px;
text-align: left;
}

View File

@@ -0,0 +1,39 @@
<template>
<div v-if="activeTabs.includes('attachment')" class="section-details">
<AttachmentList
:card-id="card.id"
:removable="true"
@delete-attachment="deleteAttachment"
@restore-attachment="restoreAttachment" />
</div>
</template>
<script>
import AttachmentList from './AttachmentList'
export default {
components: { AttachmentList },
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
},
data() {
return {
}
},
methods: {
deleteAttachment(attachment) {
this.$store.dispatch('deleteAttachment', attachment)
},
restoreAttachment(attachment) {
this.$store.dispatch('restoreAttachment', attachment)
},
},
}
</script>

View File

@@ -0,0 +1,460 @@
<!--
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div v-if="currentCard" ref="modalContainer" class="container">
<div :class="{defaultTop: scrollPosition < 100, fixedTop: scrollPosition > 100}">
<div class="top">
<h1 class="top-title">
{{ currentCard.title }}
</h1>
<p class="top-modified">
{{ t('deck', 'Modified') }}: {{ currentCard.lastModified | fromNow }}. {{ t('deck', 'Created') }} {{ currentCard.createdAt | fromNow }}
</p>
</div>
<div class="tabs">
<div class="tab members" :class="{active: activeTabs.includes('members')}" @click="changeActiveTab('members')">
<i class="icon-user icon" />
{{ t('deck', 'Members') }}
</div>
<div class="tab tags" :class="{active: activeTabs.includes('tags')}" @click="changeActiveTab('tags')">
<i class="icon icon-tag" />
{{ t('deck', 'Tags') }}
</div>
<div class="tab due-date" :class="{active: activeTabs.includes('duedate')}" @click="changeActiveTab('duedate')">
<i class="icon icon-calendar-dark" />
{{ t('deck', 'Due date') }}
</div>
<div class="tab project" :class="{active: activeTabs.includes('project')}" @click="changeActiveTab('project')">
<i class="icon icon-deck" />
{{ t('deck', 'Project') }}
</div>
<div class="tab attachments" :class="{active: activeTabs.includes('attachment')}" @click="changeActiveTab('attachment')">
<i class="icon-attach icon icon-attach-dark" />
{{ t('deck', 'Attachments') }}
</div>
</div>
</div>
<div class="content">
<div :class="[currentCard.labels.length > 3 ? 'content-two-tabs' : 'content-three-tabs']">
<MembersTab
:card="currentCard"
:active-tabs="activeTabs"
:current-tab="currentTab"
@click="activeTabs.push('members')"
@active-tab="changeActiveTab"
@remove-active-tab="removeActiveTab" />
<TagsTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('tags')"
@active-tab="changeActiveTab"
@remove-active-tab="removeActiveTab" />
<DueDateTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('duedate')"
@active-tab="changeActiveTab"
@remove-active-tab="removeActiveTab" />
<ProjectTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('project')"
@active-tab="changeActiveTab" />
<AttachmentsTab
:active-tabs="activeTabs"
:card="currentCard"
:current-tab="currentTab"
@click="activeTabs.push('attachment')"
@active-tab="changeActiveTab" />
</div>
<Description :key="currentCard.id" :card="currentCard" @change="descriptionChanged" />
</div>
<div class="activities">
<div class="activities-header">
<div class="activities-title">
<i class="icon-activity" /> {{ t('deck', 'Activity') }}
</div>
<div class="show-details-btn" @click="showDetails = !showDetails">
{{ showDetails ? t('deck', 'Hide details') : t('deck', 'Show details') }}
</div>
</div>
<template v-if="showDetails">
<CardSidebarTabComments :card="currentCard" :tab-query="tabQuery" />
<ActivityList v-if="hasActivity"
filter="deck"
:object-id="currentBoard.id"
object-type="deck"
type="deck" />
</template>
</div>
</div>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import { mapState, mapGetters } from 'vuex'
import relativeDate from '../../mixins/relativeDate'
import { showError } from '@nextcloud/dialogs'
import { getCurrentUser } from '@nextcloud/auth'
import MembersTab from './MembersTab.vue'
import TagsTab from './TagsTab.vue'
import DueDateTab from './DueDateTab.vue'
import Description from './Description.vue'
import ProjectTab from './ProjectTab.vue'
import AttachmentsTab from './AttachmentsTab.vue'
import CardSidebarTabComments from './CardSidebarTabComments'
import moment from '@nextcloud/moment'
import ActivityList from '../ActivityList'
const capabilities = window.OC.getCapabilities()
export default {
name: 'CardModal',
components: {
MembersTab,
Description,
TagsTab,
DueDateTab,
ProjectTab,
AttachmentsTab,
CardSidebarTabComments,
ActivityList,
},
filters: {
fromNow(value) {
return moment.unix(value).fromNow()
},
},
mixins: [relativeDate],
props: {
id: {
type: Number,
required: true,
},
tabId: {
type: String,
required: false,
default: null,
},
tabQuery: {
type: String,
required: false,
default: null,
},
},
data() {
return {
newComment: '',
titleEditable: false,
titleEditing: '',
hasActivity: capabilities && capabilities.activity,
currentUser: getCurrentUser(),
comment: '',
currentTab: null,
activeTabs: [],
showDetails: false,
scrollPosition: null,
}
},
computed: {
...mapGetters([
'getCommentsForCard',
'hasMoreComments',
]),
...mapState({
currentBoard: state => state.currentBoard,
replyTo: state => state.comment.replyTo,
}),
...mapGetters(['canEdit', 'assignables', 'cardActions', 'stackById']),
title() {
return this.titleEditable ? this.titleEditing : this.currentCard.title
},
currentCard() {
return this.$store.getters.cardById(this.id)
},
subtitle() {
return t('deck', 'Modified') + ': ' + this.relativeDate(this.currentCard.lastModified * 1000) + ' ' + t('deck', 'Created') + ': ' + this.relativeDate(this.currentCard.createdAt * 1000)
},
cardRichObject() {
return {
id: '' + this.currentCard.id,
name: this.currentCard.title,
boardname: this.currentBoard.title,
stackname: this.stackById(this.currentCard.stackId)?.title,
link: window.location.protocol + '//' + window.location.host + generateUrl('/apps/deck/') + `#/board/${this.currentBoard.id}/card/${this.currentCard.id}`,
}
},
cardDetailsInModal: {
get() {
return this.$store.getters.config('cardDetailsInModal')
},
set(newValue) {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
},
mounted() {
this.$nextTick(() => {
this.$refs.modalContainer.addEventListener('scroll', this.onScroll)
})
this.loadComments()
},
methods: {
cancelReply() {
this.$store.dispatch('setReplyTo', null)
},
async createComment(content) {
const commentObj = {
cardId: this.currentCard.id,
comment: content,
}
await this.$store.dispatch('createComment', commentObj)
this.$store.dispatch('setReplyTo', null)
this.newComment = ''
await this.loadComments()
},
async loadComments() {
this.$store.dispatch('setReplyTo', null)
this.error = null
this.isLoading = true
try {
await this.$store.dispatch('fetchComments', { cardId: this.currentCard.id })
this.isLoading = false
if (this.currentCard.commentsUnread > 0) {
await this.$store.dispatch('markCommentsAsRead', this.currentCard.id)
}
} catch (e) {
this.isLoading = false
console.error('Failed to fetch more comments during infinite loading', e)
this.error = t('deck', 'Failed to load comments')
}
},
descriptionChanged(newDesc) {
this.copiedCard.description = newDesc
},
handleUpdateTitleEditable(value) {
this.titleEditable = value
if (value) {
this.titleEditing = this.currentCard.title
}
},
handleUpdateTitle(value) {
this.titleEditing = value
},
handleSubmitTitle(value) {
if (value.trim === '') {
showError(t('deck', 'The title cannot be empty.'))
return
}
this.titleEditable = false
this.$store.dispatch('updateCardTitle', { ...this.currentCard, title: this.titleEditing })
},
closeSidebar() {
this.$router.push({ name: 'board' })
},
showModal() {
this.$store.dispatch('setConfig', { cardDetailsInModal: true })
},
closeModal() {
this.$store.dispatch('setConfig', { cardDetailsInModal: false })
},
changeActiveTab(tab) {
this.currentTab = tab
this.activeTabs = this.activeTabs.filter((item) => !['project', 'attachment'].includes(item))
if (!this.activeTabs.includes(tab)) {
this.activeTabs.push(tab)
}
},
removeActiveTab(tab) {
const index = this.activeTabs.indexOf(tab)
if (index > -1) {
this.activeTabs = this.activeTabs.splice(index, 1)
}
},
onScroll() {
this.scrollPosition = this.$refs.modalContainer.scrollTop
},
},
}
</script>
<style lang="scss" scoped>
.show-details-btn {
cursor: pointer;
text-decoration: underline;
color: var(--color-text-maxcontrast);
}
.activities-header{
display: flex;
justify-content: space-between;
}
.content-two-tabs, .content-three-tabs {
display: grid;
align-items: flex-start;
}
.content-two-tabs {
grid-template-columns: 1fr 2fr;
}
.content-three-tabs {
grid-template-columns: 1fr 2fr 1fr;
}
.icon-activity {
background-image: url(../../../img/flash-black.svg);
width: 15px;
height: 15px;
margin-right: 5px;
}
.icon {
margin-right: 5px;
}
.icon-plus {
background-image: url(../../../img/plus.svg);
width: 15px;
height: 15px;
margin-right: 5px;
}
.log-item {
display: flex;
justify-content: flex-start;
line-height: 45px;
align-items: center;
}
.activities {
&-title{
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 15px;
font-weight: bold;
}
margin-top: 100px;
padding-left: 20px !important;
padding-right: 20px !important;
}
.content{
padding-left: 20px;
padding-right: 20px;
}
.comments {
display: flex;
justify-content: space-between;
align-items: center;
&-input{
width: 100%;
margin-left: 10px;
}
.comment-form{
width: 95%;
}
}
.container {
overflow-y: scroll;
height: 800px;
}
.top {
padding: 20px 20px 0px 20px;
&-title {
color: black;
font-size: 20px;
font-weight: bold;
}
&-modified {
color: #767676;
line-height: 40px;
}
}
.tabs {
padding-left: 20px;
padding-right: 20px;
margin-top: 20px;
margin-bottom: 5px;
display: flex;
}
.tab {
cursor: pointer;
font-weight: bold;
background-color: #ededed;
color: rgb(0, 0, 0);
flex-grow: 0;
flex-shrink: 1;
display: flex;
flex-direction: row;
overflow: hidden;
padding: 10px 20px;
border-radius: 10px;
font-size: 85%;
margin-bottom: 3px;
margin-right: 15px;
}
.action-btn {
list-style: none;
}
.edit-btns {
display: flex;
align-items: center;
}
.description {
display: flex;
justify-content: space-between;
margin-top: 30px;
}
.active {
color: #409eff;
background-color: #ecf5ff;
}
.fixedTop {
position: sticky;
top: 0;
background-color: #ffffff;
z-index: 1000;
margin-top: 0px;
padding-bottom: 5px;
}
</style>

View File

@@ -1,19 +1,16 @@
<template>
<div>
<div class="comment--header">
<Avatar :user="currentUser.uid" />
<span class="has-tooltip username">
{{ currentUser.displayName }}
</span>
<div class="comment-wrapper">
<div class="comment--header">
<Avatar :user="currentUser.uid" />
</div>
<CommentItem v-if="replyTo"
:comment="replyTo"
:reply="true"
:preview="true"
@cancel="cancelReply" />
<CommentForm v-model="newComment" @submit="createComment" />
</div>
<CommentItem v-if="replyTo"
:comment="replyTo"
:reply="true"
:preview="true"
@cancel="cancelReply" />
<CommentForm v-model="newComment" @submit="createComment" />
<ul v-if="getCommentsForCard(card.id).length > 0" id="commentsFeed">
<CommentItem v-for="comment in getCommentsForCard(card.id)"
:key="comment.id"
@@ -26,8 +23,7 @@
</InfiniteLoading>
</ul>
<div v-else-if="isLoading" class="icon icon-loading" />
<div v-else class="emptycontent">
<div :class="{ 'icon-comment': !error, 'icon-error': error }" />
<div v-else>
<p>{{ error || t('deck', 'No comments yet. Begin the discussion!') }}</p>
</div>
</div>

View File

@@ -280,7 +280,9 @@ h5 {
border-bottom: 1px solid var(--color-border);
margin-top: 20px;
margin-bottom: 5px;
color: var(--color-text-maxcontrast);
color: var(--color-main-text);
font-weight: bold;
font-size: 20px;
.icon-info {
display: inline-block;

View File

@@ -0,0 +1,187 @@
<template>
<div v-if="activeTabs.includes('duedate') || (copiedCard && copiedCard.duedate)"
v-show="!['project', 'attachment'].includes(currentTab)"
class="section-details">
<div @click="$emit('active-tab', 'duedate')">
<DatetimePicker v-model="duedate"
:placeholder="t('deck', 'Set a due date')"
type="datetime"
:minute-step="5"
:show-second="false"
:lang="lang"
:disabled="saving || !canEdit"
:shortcuts="shortcuts"
confirm />
</div>
<Actions v-if="canEdit">
<ActionButton v-if="copiedCard.duedate" icon="icon-delete" @click="removeDue()">
{{ t('deck', 'Remove due date') }}
</ActionButton>
</Actions>
</div>
</template>
<script>
import { DatetimePicker, Actions, ActionButton } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
import Color from '../../mixins/color'
import labelStyle from '../../mixins/labelStyle'
import {
getDayNamesMin,
getFirstDay,
getMonthNamesShort,
} from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
export default {
components: { DatetimePicker, Actions, ActionButton },
mixins: [Color, labelStyle],
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
currentTab: {
type: String,
default: '',
},
},
data() {
return {
saving: false,
copiedCard: null,
lang: {
days: getDayNamesMin(),
months: getMonthNamesShort(),
formatLocale: {
firstDayOfWeek: getFirstDay() === 0 ? 7 : getFirstDay(),
},
placeholder: {
date: t('deck', 'Select Date'),
},
},
format: {
stringify: this.stringify,
parse: this.parse,
},
shortcuts: [
{
text: t('deck', 'Today'),
onClick() {
const date = new Date()
date.setDate(date.getDate())
date.setHours(23)
date.setMinutes(59)
return date
},
},
{
text: t('deck', 'Tomorrow'),
onClick() {
const date = new Date()
date.setDate(date.getDate() + 1)
date.setHours(23)
date.setMinutes(59)
return date
},
},
{
text: t('deck', 'Next week'),
onClick() {
const date = new Date()
date.setDate(date.getDate() + 7)
date.setHours(23)
date.setMinutes(59)
return date
},
},
{
text: t('deck', 'Next month'),
onClick() {
const date = new Date()
date.setDate(date.getDate() + 30)
date.setHours(23)
date.setMinutes(59)
return date
},
},
],
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
}),
...mapGetters(['canEdit']),
labelsSorted() {
return [...this.currentBoard.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
duedate: {
get() {
return this.card.duedate ? new Date(this.card.duedate) : null
},
async set(val) {
this.saving = true
await this.$store.dispatch('updateCardDue', {
...this.copiedCard,
duedate: val ? moment(val).format('YYYY-MM-DD H:mm:ss') : null,
})
this.saving = false
},
},
},
watch: {
card() {
this.initialize()
if (this.copiedCard.duedate) {
this.$emit('active-tab', 'duedate')
} else {
this.$emit('remove-active-tab', 'duedate')
}
},
},
mounted() {
this.initialize()
if (this.copiedCard.duedate) {
this.$emit('active-tab', 'duedate')
} else {
this.$emit('remove-active-tab', 'duedate')
}
},
methods: {
async initialize() {
if (!this.card) {
return
}
this.copiedCard = JSON.parse(JSON.stringify(this.card))
},
removeDue() {
this.copiedCard.duedate = null
this.$store.dispatch('updateCardDue', this.copiedCard)
},
},
}
</script>
<style lang="scss" scoped>
.section-details{
margin-right: 5px;
display: flex;
align-items: flex-start;
margin-top: 10px;
}
</style>
<style>
.section-details .mx-input{
height: 36px !important;
margin: 0;
}
.section-details .action-item {
height: 30px !important;
}
</style>

View File

@@ -0,0 +1,208 @@
<template>
<div v-if="activeTabs.includes('members') || (assignedUsers && assignedUsers.length > 0)"
v-show="!['project', 'attachment'].includes(currentTab)"
class="section-details">
<div v-if="showSelelectMembers" @mouseleave="showSelelectMembers = false">
<Multiselect v-if="canEdit"
v-model="assignedUsers"
:multiple="true"
:options="formatedAssignables"
:user-select="true"
:auto-limit="false"
:placeholder="t('deck', 'Assign a user to this card…')"
label="displayname"
track-by="multiselectKey"
@select="assignUserToCard"
@remove="removeUserFromCard">
<template #tag="scope">
<div class="avatarlist--inline">
<Avatar :user="scope.option.uid"
:display-name="scope.option.displayname"
:size="24"
:is-no-user="scope.option.isNoUser"
:disable-menu="true" />
</div>
</template>
</Multiselect>
<div v-else class="avatar-list--readonly">
<Avatar v-for="option in assignedUsers"
:key="option.primaryKey"
:user="option.uid"
:display-name="option.displayname"
:is-no-user="option.isNoUser"
:size="32" />
</div>
</div>
<template v-else>
<div class="members">
<Avatar v-for="option in assignedUsers"
:key="option.primaryKey"
:user="option.uid"
:display-name="option.displayname"
:is-no-user="option.isNoUser"
:size="32" />
<div class="button new select-member-btn" @click="selectMembers">
<span class="icon icon-add" />
<span class="hidden-visually" />
</div>
</div>
</template>
</div>
</template>
<script>
import { Multiselect, Avatar } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
export default {
name: 'MembersTab',
components: {
Multiselect,
Avatar,
},
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
currentTab: {
type: String,
default: '',
},
},
data() {
return {
assignedUsers: null,
copiedCard: null,
showSelelectMembers: false,
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
}),
...mapGetters(['canEdit', 'assignables']),
formatedAssignables() {
return this.assignables.map(item => {
const assignable = {
...item,
user: item.primaryKey,
displayName: item.displayname,
icon: 'icon-user',
isNoUser: false,
multiselectKey: item.type + ':' + item.uid,
}
if (item.type === 1) {
assignable.icon = 'icon-group'
assignable.isNoUser = true
}
if (item.type === 7) {
assignable.icon = 'icon-circles'
assignable.isNoUser = true
}
return assignable
})
},
},
watch: {
card() {
this.initialize()
},
assignedUsers(value) {
if (value.length > 0) {
this.$emit('active-tab', 'members')
} else {
this.$emit('remove-active-tab', 'members')
}
},
},
mounted() {
this.initialize()
},
methods: {
selectMembers() {
this.showSelelectMembers = true
this.$emit('active-tab', 'members')
},
removeUserFromCard(user) {
this.$store.dispatch('removeUserFromCard', {
card: this.copiedCard,
assignee: {
userId: user.uid,
type: user.type,
},
})
},
addLabelToCard(newLabel) {
this.copiedCard.labels.push(newLabel)
const data = {
card: this.copiedCard,
labelId: newLabel.id,
}
this.$store.dispatch('addLabel', data)
},
assignUserToCard(user) {
this.$store.dispatch('assignCardToUser', {
card: this.copiedCard,
assignee: {
userId: user.uid,
type: user.type,
},
})
},
async initialize() {
if (!this.card) {
return
}
this.copiedCard = JSON.parse(JSON.stringify(this.card))
this.assignedLabels = [...this.card.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
if (this.card.assignedUsers && this.card.assignedUsers.length > 0) {
this.assignedUsers = this.card.assignedUsers.map((item) => ({
...item.participant,
isNoUser: item.participant.type !== 0,
multiselectKey: item.participant.type + ':' + item.participant.primaryKey,
}))
} else {
this.assignedUsers = []
}
},
},
}
</script>
<style lang="scss" scoped>
.select-member-btn {
box-sizing: border-box;
display: flex;
height: 32px;
width: 34px;
padding: 5px 9px;
align-items: center;
justify-content: center;
margin-left: 5px;
}
.section-details {
width: 100%;
margin-top: 10px;
}
.members {
display: flex;
align-items: center;
}
</style>
<style>
.members .multiselect__tags{
height: 34px !important;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div v-if="activeTabs.includes('project')" class="section-details">
<div class="section-wrapper project-tab">
<CollectionList v-if="card.id"
:id="`${card.id}`"
:name="card.title"
type="deck-card" />
</div>
</div>
</template>
<script>
import { CollectionList } from 'nextcloud-vue-collections'
export default {
components: { CollectionList },
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
},
}
</script>
<style scoped>
.section-details{
margin-top: 10px;
min-width: 500px;
}
</style>
<style lang="scss">
#collection-select-container p {
display: none;
}
#collection-list li {
align-items: flex-end;
}
.project-tab .collection-list-item {
display: flex !important;
align-items: center !important;
}
.project-tab .linked-icons {
img {
height: 20px !important;
}
}
</style>

View File

@@ -0,0 +1,171 @@
<template>
<div v-if="activeTabs.includes('tags') || card.labels.length > 0"
v-show="!['project', 'attachment'].includes(currentTab)"
class="section-details">
<div v-if="showSelelectTags || card.labels.length <= 0" @mouseleave="showSelelectTags = false">
<Multiselect v-model="assignedLabels"
:multiple="true"
:disabled="!canEdit"
:options="labelsSorted"
:placeholder="t('deck', 'Assign a tag to this card')"
:taggable="true"
label="title"
track-by="id"
@select="addLabelToCard"
@remove="removeLabelFromCard">
<template #option="scope">
<div :style="{ backgroundColor: '#' + scope.option.color, color: textColor(scope.option.color)}" class="tag">
{{ scope.option.title }}
</div>
</template>
<template #tag="scope">
<div :style="{ backgroundColor: '#' + scope.option.color, color: textColor(scope.option.color)}" class="tag">
{{ scope.option.title }}
</div>
</template>
</Multiselect>
</div>
<div v-else-if="card.labels.length > 0" class="labels">
<div v-for="label in card.labels"
:key="label.id"
:style="labelStyle(label)"
class="labels-item">
<span @click.stop="applyLabelFilter(label)">{{ label.title }}</span>
</div>
<div class="button new select-tag" @click="add">
<span class="icon icon-add" />
<span class="hidden-visually" />
</div>
</div>
</div>
</template>
<script>
import { Multiselect } from '@nextcloud/vue'
import { mapState, mapGetters } from 'vuex'
import Color from '../../mixins/color'
import labelStyle from '../../mixins/labelStyle'
export default {
components: { Multiselect },
mixins: [Color, labelStyle],
props: {
card: {
type: Object,
default: null,
},
activeTabs: {
type: Array,
default: () => [],
},
currentTab: {
type: String,
default: '',
},
},
data() {
return {
assignedLabels: null,
showSelelectTags: false,
copiedCard: null,
}
},
computed: {
...mapState({
currentBoard: state => state.currentBoard,
}),
...mapGetters(['canEdit']),
labelsSorted() {
return [...this.currentBoard.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
},
watch: {
card(value) {
if (value.labels.length > 0) {
this.$emit('active-tab', 'tags')
} else {
this.$emit('remove-active-tab', 'tags')
}
},
},
mounted() {
this.initialize()
if (this.card.labels.length > 0) {
this.$emit('active-tab', 'tags')
}
},
methods: {
add() {
this.showSelelectTags = true
this.$emit('active-tab', 'tags')
},
async initialize() {
if (!this.card) {
return
}
this.copiedCard = JSON.parse(JSON.stringify(this.card))
this.assignedLabels = [...this.card.labels].sort((a, b) => (a.title < b.title) ? -1 : 1)
},
openCard() {
const boardId = this.card && this.card.boardId ? this.card.boardId : this.$route.params.id
this.$router.push({ name: 'card', params: { id: boardId, cardId: this.card.id } }).catch(() => {})
},
addLabelToCard(newLabel) {
this.copiedCard.labels.push(newLabel)
const data = {
card: this.copiedCard,
labelId: newLabel.id,
}
this.$store.dispatch('addLabel', data)
},
removeLabelFromCard(removedLabel) {
const removeIndex = this.copiedCard.labels.findIndex((label) => {
return label.id === removedLabel.id
})
if (removeIndex !== -1) {
this.copiedCard.labels.splice(removeIndex, 1)
}
const data = {
card: this.copiedCard,
labelId: removedLabel.id,
}
this.$store.dispatch('removeLabel', data)
},
},
}
</script>
<style lang="scss" scoped>
.labels {
display: flex;
justify-content: flex-start;
align-items: center;
&-item {
border-radius: 15px;
margin-right: 5px;
min-width: 110px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
}
}
.select-tag {
height: 32px;
width: 34px;
padding: 5px 8px;
}
.tag{
padding: 0px 5px;
border-radius: 15px;
}
.section-details{
margin-top: 10px;
}
</style>

View File

@@ -135,7 +135,7 @@ export default {
},
activeBoards() {
return this.$store.getters.boards.filter((item) => item.deletedAt === 0 && item.archived === false)
}
},
},
methods: {
openCard() {

View File

@@ -34,6 +34,7 @@
display: flex;
align-items: center;
color: var(--color-text-light);
margin-right: 10px;
.username {
padding: 10px;
@@ -50,3 +51,16 @@
margin-left: 44px;
word-break: break-word;
}
.comment-wrapper {
display: flex;
align-items: center;
}
.comment-form {
width: 100%;
}
.comment-form .comment-form__contenteditable {
border-radius: 4px;
}

View File

@@ -28,7 +28,7 @@ import Boards from './components/boards/Boards'
import Board from './components/board/Board'
import Sidebar from './components/Sidebar'
import BoardSidebar from './components/board/BoardSidebar'
import CardSidebar from './components/card/CardSidebar'
import CardModal from './components/card/CardModal'
import Overview from './components/overview/Overview'
Vue.use(Router)
@@ -119,7 +119,7 @@ export default new Router({
path: 'card/:cardId/:tabId?/:tabQuery?',
name: 'card',
components: {
sidebar: CardSidebar,
sidebar: CardModal,
},
props: {
default: (route) => {