diff --git a/.eslintrc.js b/.eslintrc.js index 690fc606a..69b8d1e1c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,6 +8,7 @@ module.exports = { 'jsdoc/require-param-type': ['off'], 'jsdoc/check-param-names': ['off'], 'jsdoc/no-undefined-types': ['off'], - 'jsdoc/require-property-description' : ['off'] + 'jsdoc/require-property-description': ['off'], + 'import/no-named-as-default-member': ['off'] }, } diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 53eb60258..edbd2b3a2 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -36,7 +36,6 @@ use OCA\Deck\Db\AssignmentMapper; use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\CardMapper; -use OCA\Deck\Db\User; use OCA\Deck\Event\AclCreatedEvent; use OCA\Deck\Event\AclDeletedEvent; use OCA\Deck\Event\AclUpdatedEvent; @@ -48,6 +47,7 @@ use OCA\Deck\Middleware\DefaultBoardMiddleware; use OCA\Deck\Middleware\ExceptionMiddleware; use OCA\Deck\Notification\Notifier; +use OCA\Deck\Reference\CardReferenceProvider; use OCA\Deck\Search\CardCommentProvider; use OCA\Deck\Search\DeckProvider; use OCA\Deck\Service\PermissionService; @@ -58,6 +58,7 @@ use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\Collaboration\Reference\RenderReferenceEvent; use OCP\Collaboration\Resources\IProviderManager; use OCP\Comments\CommentsEntityEvent; use OCP\Comments\ICommentsManager; @@ -83,6 +84,14 @@ class Application extends App implements IBootstrap { public function __construct(array $urlParams = []) { parent::__construct(self::APP_ID, $urlParams); + + // TODO move this back to ::register after fixing the autoload issue + // (and use a listener class) + $container = $this->getContainer(); + $eventDispatcher = $container->get(IEventDispatcher::class); + $eventDispatcher->addListener(RenderReferenceEvent::class, function () { + Util::addScript(self::APP_ID, self::APP_ID . '-card-reference'); + }); } public function boot(IBootContext $context): void { @@ -121,8 +130,12 @@ public function register(IRegistrationContext $context): void { $context->registerSearchProvider(CardCommentProvider::class); $context->registerDashboardWidget(DeckWidget::class); + // reference widget + $context->registerReferenceProvider(CardReferenceProvider::class); + // $context->registerEventListener(RenderReferenceEvent::class, CardReferenceListener::class); + $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); - + // Event listening for full text search indexing $context->registerEventListener(CardCreatedEvent::class, FullTextSearchEventListener::class); $context->registerEventListener(CardUpdatedEvent::class, FullTextSearchEventListener::class); diff --git a/lib/Reference/CardReferenceProvider.php b/lib/Reference/CardReferenceProvider.php new file mode 100644 index 000000000..a4e4a9c8b --- /dev/null +++ b/lib/Reference/CardReferenceProvider.php @@ -0,0 +1,113 @@ +<?php +/** + * @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net> + * + * @author Julien Veyssier <eneiluj@posteo.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/>. + */ + +namespace OCA\Deck\Reference; + +use OC\Collaboration\Reference\Reference; +use OCA\Deck\AppInfo\Application; +use OCA\Deck\Service\BoardService; +use OCA\Deck\Service\CardService; +use OCA\Deck\Service\StackService; +use OCP\Collaboration\Reference\IReference; +use OCP\Collaboration\Reference\IReferenceProvider; +use OCP\IURLGenerator; + +class CardReferenceProvider implements IReferenceProvider { + private CardService $cardService; + private IURLGenerator $urlGenerator; + private BoardService $boardService; + private StackService $stackService; + + public function __construct(CardService $cardService, + BoardService $boardService, + StackService $stackService, + IURLGenerator $urlGenerator, + ?string $userId) { + $this->cardService = $cardService; + $this->urlGenerator = $urlGenerator; + $this->boardService = $boardService; + $this->stackService = $stackService; + } + + /** + * @inheritDoc + */ + public function matchReference(string $referenceText): bool { + $start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID); + $startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID); + + // link example: https://nextcloud.local/index.php/apps/deck/#/board/2/card/11 + $noIndexMatch = preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+$/', $referenceText) === 1; + $indexMatch = preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/[0-9]+\/card\/[0-9]+$/', $referenceText) === 1; + + return $noIndexMatch || $indexMatch; + } + + /** + * @inheritDoc + */ + public function resolveReference(string $referenceText): ?IReference { + if ($this->matchReference($referenceText)) { + $cardIds = $this->getBoardCardId($referenceText); + if ($cardIds !== null) { + [$boardId, $cardId] = $cardIds; + $card = $this->cardService->find((int) $cardId); + $board = $this->boardService->find((int) $boardId); + $stack = $this->stackService->find((int) $card->jsonSerialize()['stackId']); + $reference = new Reference($referenceText); + $reference->setRichObject(Application::APP_ID . '-card', [ + 'card' => $card, + 'board' => $board, + 'stack' => $stack, + ]); + return $reference; + } + } + + return null; + } + + private function getBoardCardId(string $url): ?array { + $start = $this->urlGenerator->getAbsoluteURL('/apps/' . Application::APP_ID); + $startIndex = $this->urlGenerator->getAbsoluteURL('/index.php/apps/' . Application::APP_ID); + + preg_match('/^' . preg_quote($start, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)$/', $url, $matches); + if ($matches && count($matches) > 2) { + return [$matches[1], $matches[2]]; + } + + preg_match('/^' . preg_quote($startIndex, '/') . '\/#\/board\/([0-9]+)\/card\/([0-9]+)$/', $url, $matches2); + if ($matches2 && count($matches2) > 2) { + return [$matches2[1], $matches2[2]]; + } + + return null; + } + + public function getCachePrefix(string $referenceId): string { + return $referenceId; + } + + public function getCacheKey(string $referenceId): ?string { + return null; + } +} diff --git a/package-lock.json b/package-lock.json index dfa2caf66..fe9550132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@nextcloud/router": "^2.0.0", "@nextcloud/vue": "^6.0.0-beta.4", "@nextcloud/vue-dashboard": "^2.0.1", + "@nextcloud/vue-richtext": "^2.0.0", "blueimp-md5": "^2.19.0", "dompurify": "^2.4.0", "lodash": "^4.17.21", @@ -53,8 +54,10 @@ "@relative-ci/agent": "^4.1.0", "@vue/test-utils": "^1.3.0", "cypress": "^10.7.0", + "eslint-webpack-plugin": "^3.2.0", "jest": "^29.0.1", "jest-serializer-vue": "^2.0.2", + "stylelint-webpack-plugin": "^3.3.0", "vue-jest": "^3.0.7", "vue-template-compiler": "^2.7.9" }, @@ -3497,6 +3500,24 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/@nextcloud/vue-richtext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue-richtext/-/vue-richtext-2.0.0.tgz", + "integrity": "sha512-Z/AbweVmIB8shWZVLI0kUPrJnkCBlU5xIkkfv+RPFepLY7eZ+ttm5HRhLGqgFyXFNf4RIM7yGt/l6K35XcEX2A==", + "dependencies": { + "@nextcloud/axios": "^2.0.0", + "@nextcloud/router": "^2.0.0", + "clone": "^2.1.2", + "vue": "^2.7.8" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "vue": "^2.7.8" + } + }, "node_modules/@nextcloud/vue/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -3600,7 +3621,6 @@ "version": "2.1.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" @@ -3613,7 +3633,6 @@ "version": "2.0.4", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 8" } @@ -3622,7 +3641,6 @@ "version": "1.2.6", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" @@ -3798,10 +3816,9 @@ } }, "node_modules/@types/eslint": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz", - "integrity": "sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA==", - "peer": true, + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", + "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -4463,7 +4480,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -4481,7 +4497,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dev": true, - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -4497,8 +4512,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -4636,7 +4650,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7438,7 +7451,6 @@ "version": "3.0.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-type": "^4.0.0" }, @@ -7450,7 +7462,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8450,6 +8461,121 @@ "node": ">=10" } }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dev": true, + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/eslint-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/eslint/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -9068,7 +9194,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, - "peer": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -9105,7 +9230,6 @@ "version": "1.11.0", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "reusify": "^1.0.4" } @@ -9625,7 +9749,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -9711,7 +9834,6 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "peer": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -10223,7 +10345,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, - "peer": true, "engines": { "node": ">= 4" } @@ -10533,7 +10654,6 @@ "version": "2.1.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -10590,7 +10710,6 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -13815,7 +13934,6 @@ "version": "1.4.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 8" } @@ -15452,8 +15570,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/quick-lru": { "version": "4.0.1", @@ -15866,7 +15983,6 @@ "version": "2.0.2", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15964,7 +16080,6 @@ "version": "1.0.4", "dev": true, "license": "MIT", - "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -16020,7 +16135,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } @@ -17112,6 +17226,121 @@ "stylelint": "^14.5.1" } }, + "node_modules/stylelint-webpack-plugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-3.3.0.tgz", + "integrity": "sha512-F53bapIZ9zI16ero8IWm6TrUE6SSibZBphJE9b5rR2FxtvmGmm1YmS+a5xjQzn63+cv71GVSCu4byX66fBLpEw==", + "dev": true, + "dependencies": { + "globby": "^11.1.0", + "jest-worker": "^28.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "stylelint": "^13.0.0 || ^14.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/stylelint-webpack-plugin/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/stylelint-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/stylelint-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/stylelint-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/stylelint-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/stylelint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/stylelint/node_modules/@csstools/selector-specificity": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz", @@ -21677,6 +21906,17 @@ } } }, + "@nextcloud/vue-richtext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue-richtext/-/vue-richtext-2.0.0.tgz", + "integrity": "sha512-Z/AbweVmIB8shWZVLI0kUPrJnkCBlU5xIkkfv+RPFepLY7eZ+ttm5HRhLGqgFyXFNf4RIM7yGt/l6K35XcEX2A==", + "requires": { + "@nextcloud/axios": "^2.0.0", + "@nextcloud/router": "^2.0.0", + "clone": "^2.1.2", + "vue": "^2.7.8" + } + }, "@nextcloud/webpack-vue-config": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@nextcloud/webpack-vue-config/-/webpack-vue-config-5.3.0.tgz", @@ -21687,7 +21927,6 @@ "@nodelib/fs.scandir": { "version": "2.1.4", "dev": true, - "peer": true, "requires": { "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" @@ -21695,13 +21934,11 @@ }, "@nodelib/fs.stat": { "version": "2.0.4", - "dev": true, - "peer": true + "dev": true }, "@nodelib/fs.walk": { "version": "1.2.6", "dev": true, - "peer": true, "requires": { "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" @@ -21857,10 +22094,9 @@ } }, "@types/eslint": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz", - "integrity": "sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA==", - "peer": true, + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", + "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -22455,7 +22691,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "peer": true, "requires": { "ajv": "^8.0.0" }, @@ -22465,7 +22700,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", "dev": true, - "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -22477,8 +22711,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true + "dev": true } } }, @@ -22567,8 +22800,7 @@ }, "array-union": { "version": "2.1.0", - "dev": true, - "peer": true + "dev": true }, "array.prototype.flat": { "version": "1.3.0", @@ -24732,15 +24964,13 @@ "dir-glob": { "version": "3.0.1", "dev": true, - "peer": true, "requires": { "path-type": "^4.0.0" }, "dependencies": { "path-type": { "version": "4.0.0", - "dev": true, - "peer": true + "dev": true } } }, @@ -25681,6 +25911,86 @@ "dev": true, "peer": true }, + "eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dev": true, + "requires": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "espree": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", @@ -25947,7 +26257,6 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, - "peer": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -25976,7 +26285,6 @@ "fastq": { "version": "1.11.0", "dev": true, - "peer": true, "requires": { "reusify": "^1.0.4" } @@ -26358,7 +26666,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "peer": true, "requires": { "is-glob": "^4.0.1" } @@ -26420,7 +26727,6 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "peer": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -26787,8 +27093,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "peer": true + "dev": true }, "immutable": { "version": "4.1.0", @@ -26990,8 +27295,7 @@ }, "is-extglob": { "version": "2.1.1", - "dev": true, - "peer": true + "dev": true }, "is-finite": { "version": "1.1.0", @@ -27027,7 +27331,6 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "peer": true, "requires": { "is-extglob": "^2.1.1" } @@ -29365,8 +29668,7 @@ }, "merge2": { "version": "1.4.1", - "dev": true, - "peer": true + "dev": true }, "methods": { "version": "1.1.2", @@ -30559,8 +30861,7 @@ }, "queue-microtask": { "version": "1.2.3", - "dev": true, - "peer": true + "dev": true }, "quick-lru": { "version": "4.0.1", @@ -30869,8 +31170,7 @@ }, "require-from-string": { "version": "2.0.2", - "dev": true, - "peer": true + "dev": true }, "requireindex": { "version": "1.2.0", @@ -30935,8 +31235,7 @@ }, "reusify": { "version": "1.0.4", - "dev": true, - "peer": true + "dev": true }, "rfdc": { "version": "1.3.0", @@ -30967,7 +31266,6 @@ "run-parallel": { "version": "1.2.0", "dev": true, - "peer": true, "requires": { "queue-microtask": "^1.2.2" } @@ -31870,6 +32168,86 @@ "postcss-value-parser": "^4.1.0" } }, + "stylelint-webpack-plugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-3.3.0.tgz", + "integrity": "sha512-F53bapIZ9zI16ero8IWm6TrUE6SSibZBphJE9b5rR2FxtvmGmm1YmS+a5xjQzn63+cv71GVSCu4byX66fBLpEw==", + "dev": true, + "requires": { + "globby": "^11.1.0", + "jest-worker": "^28.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "superstruct": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.16.0.tgz", diff --git a/package.json b/package.json index 7c825036c..13b879b0c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@nextcloud/router": "^2.0.0", "@nextcloud/vue": "^6.0.0-beta.4", "@nextcloud/vue-dashboard": "^2.0.1", + "@nextcloud/vue-richtext": "^2.0.0", "blueimp-md5": "^2.19.0", "dompurify": "^2.4.0", "lodash": "^4.17.21", @@ -79,8 +80,10 @@ "@relative-ci/agent": "^4.1.0", "@vue/test-utils": "^1.3.0", "cypress": "^10.7.0", + "eslint-webpack-plugin": "^3.2.0", "jest": "^29.0.1", "jest-serializer-vue": "^2.0.2", + "stylelint-webpack-plugin": "^3.3.0", "vue-jest": "^3.0.7", "vue-template-compiler": "^2.7.9" }, diff --git a/src/App.vue b/src/App.vue index 1f2e6e90f..cffef1b84 100644 --- a/src/App.vue +++ b/src/App.vue @@ -43,9 +43,9 @@ <script> import { mapState } from 'vuex' -import AppNavigation from './components/navigation/AppNavigation' +import AppNavigation from './components/navigation/AppNavigation.vue' import { NcModal, NcContent, NcAppContent } from '@nextcloud/vue' -import { BoardApi } from './services/BoardApi' +import { BoardApi } from './services/BoardApi.js' import { emit, subscribe } from '@nextcloud/event-bus' const boardApi = new BoardApi() diff --git a/src/CardCreateDialog.vue b/src/CardCreateDialog.vue index ab7b5cc8e..357fa9497 100644 --- a/src/CardCreateDialog.vue +++ b/src/CardCreateDialog.vue @@ -92,11 +92,11 @@ <script> import { generateUrl } from '@nextcloud/router' -import NcModal from '@nextcloud/vue/dist/Components/NcModal' -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect' -import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent' +import NcModal from '@nextcloud/vue/dist/Components/NcModal.js' +import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect.js' +import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' import axios from '@nextcloud/axios' -import { CardApi } from './services/CardApi' +import { CardApi } from './services/CardApi.js' const cardApi = new CardApi() diff --git a/src/components/ActivityEntry.vue b/src/components/ActivityEntry.vue index b6f289b63..c65823ee8 100644 --- a/src/components/ActivityEntry.vue +++ b/src/components/ActivityEntry.vue @@ -39,7 +39,7 @@ import RichText from '@juliushaertl/vue-richtext' import { NcUserBubble } from '@nextcloud/vue' import moment from '@nextcloud/moment' import DOMPurify from 'dompurify' -import relativeDate from '../mixins/relativeDate' +import relativeDate from '../mixins/relativeDate.js' const InternalLink = { name: 'InternalLink', diff --git a/src/components/ActivityList.vue b/src/components/ActivityList.vue index 597058527..fa1eb7df2 100644 --- a/src/components/ActivityList.vue +++ b/src/components/ActivityList.vue @@ -37,7 +37,7 @@ <script> import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' -import ActivityEntry from './ActivityEntry' +import ActivityEntry from './ActivityEntry.vue' import InfiniteLoading from 'vue-infinite-loading' const ACTIVITY_FETCH_LIMIT = 50 diff --git a/src/components/AttachmentDragAndDrop.vue b/src/components/AttachmentDragAndDrop.vue index d5487e285..05ba964b8 100644 --- a/src/components/AttachmentDragAndDrop.vue +++ b/src/components/AttachmentDragAndDrop.vue @@ -63,7 +63,7 @@ <script> import { NcModal } from '@nextcloud/vue' -import attachmentUpload from '../mixins/attachmentUpload' +import attachmentUpload from '../mixins/attachmentUpload.js' import { loadState } from '@nextcloud/initial-state' let maxUploadSizeState diff --git a/src/components/Controls.vue b/src/components/Controls.vue index bc0701a7e..f92961bd6 100644 --- a/src/components/Controls.vue +++ b/src/components/Controls.vue @@ -217,13 +217,13 @@ <script> import { mapState, mapGetters } from 'vuex' import { NcActions, NcActionButton, NcAvatar, NcButton, NcPopover } from '@nextcloud/vue' -import labelStyle from '../mixins/labelStyle' -import CardCreateDialog from '../CardCreateDialog' -import ArchiveIcon from 'vue-material-design-icons/Archive' -import FilterIcon from 'vue-material-design-icons/Filter' -import FilterOffIcon from 'vue-material-design-icons/FilterOff' -import ArrowCollapseVerticalIcon from 'vue-material-design-icons/ArrowCollapseVertical' -import ArrowExpandVerticalIcon from 'vue-material-design-icons/ArrowExpandVertical' +import labelStyle from '../mixins/labelStyle.js' +import CardCreateDialog from '../CardCreateDialog.vue' +import ArchiveIcon from 'vue-material-design-icons/Archive.vue' +import FilterIcon from 'vue-material-design-icons/Filter.vue' +import FilterOffIcon from 'vue-material-design-icons/FilterOff.vue' +import ArrowCollapseVerticalIcon from 'vue-material-design-icons/ArrowCollapseVertical.vue' +import ArrowExpandVerticalIcon from 'vue-material-design-icons/ArrowExpandVertical.vue' export default { name: 'Controls', diff --git a/src/components/board/Board.vue b/src/components/board/Board.vue index 35f55cbc7..145b3e11c 100644 --- a/src/components/board/Board.vue +++ b/src/components/board/Board.vue @@ -73,11 +73,11 @@ import { Container, Draggable } from 'vue-smooth-dnd' import { mapState, mapGetters } from 'vuex' -import Controls from '../Controls' -import Stack from './Stack' +import Controls from '../Controls.vue' +import Stack from './Stack.vue' import { NcEmptyContent } from '@nextcloud/vue' -import GlobalSearchResults from '../search/GlobalSearchResults' -import { showError } from '../../helpers/errors' +import GlobalSearchResults from '../search/GlobalSearchResults.vue' +import { showError } from '../../helpers/errors.js' export default { name: 'Board', diff --git a/src/components/board/BoardSidebar.vue b/src/components/board/BoardSidebar.vue index 0a5ddefd2..6ce0613ee 100644 --- a/src/components/board/BoardSidebar.vue +++ b/src/components/board/BoardSidebar.vue @@ -59,10 +59,10 @@ <script> import { mapState, mapGetters } from 'vuex' -import SharingTabSidebar from './SharingTabSidebar' -import TagsTabSidebar from './TagsTabSidebar' -import DeletedTabSidebar from './DeletedTabSidebar' -import TimelineTabSidebar from './TimelineTabSidebar' +import SharingTabSidebar from './SharingTabSidebar.vue' +import TagsTabSidebar from './TagsTabSidebar.vue' +import DeletedTabSidebar from './DeletedTabSidebar.vue' +import TimelineTabSidebar from './TimelineTabSidebar.vue' import { NcAppSidebar, NcAppSidebarTab } from '@nextcloud/vue' const capabilities = window.OC.getCapabilities() diff --git a/src/components/board/DeletedTabSidebar.vue b/src/components/board/DeletedTabSidebar.vue index a2df35953..4e0d307e9 100644 --- a/src/components/board/DeletedTabSidebar.vue +++ b/src/components/board/DeletedTabSidebar.vue @@ -32,7 +32,7 @@ <script> import { mapState } from 'vuex' -import relativeDate from '../../mixins/relativeDate' +import relativeDate from '../../mixins/relativeDate.js' export default { name: 'DeletedTabSidebar', diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue index a7c97d1c5..7cd674124 100644 --- a/src/components/board/Stack.vue +++ b/src/components/board/Stack.vue @@ -133,10 +133,10 @@ import { Container, Draggable } from 'vue-smooth-dnd' import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue' import { showError, showUndo } from '@nextcloud/dialogs' -import CardItem from '../cards/CardItem' +import CardItem from '../cards/CardItem.vue' import '@nextcloud/dialogs/styles/toast.scss' -import ArchiveIcon from 'vue-material-design-icons/Archive' +import ArchiveIcon from 'vue-material-design-icons/Archive.vue' export default { name: 'Stack', diff --git a/src/components/board/TagsTabSidebar.vue b/src/components/board/TagsTabSidebar.vue index a6d52e862..4313727af 100644 --- a/src/components/board/TagsTabSidebar.vue +++ b/src/components/board/TagsTabSidebar.vue @@ -70,7 +70,7 @@ <script> import { mapGetters } from 'vuex' -import Color from '../../mixins/color' +import Color from '../../mixins/color.js' import { NcColorPicker, NcActions, NcActionButton } from '@nextcloud/vue' export default { diff --git a/src/components/board/TimelineTabSidebar.vue b/src/components/board/TimelineTabSidebar.vue index a65ff3853..5b71f4d24 100644 --- a/src/components/board/TimelineTabSidebar.vue +++ b/src/components/board/TimelineTabSidebar.vue @@ -7,7 +7,7 @@ </template> <script> -import ActivityList from '../ActivityList' +import ActivityList from '../ActivityList.vue' export default { name: 'TimelineTabSidebar', diff --git a/src/components/boards/Boards.vue b/src/components/boards/Boards.vue index 0cd8133a5..512052e91 100644 --- a/src/components/boards/Boards.vue +++ b/src/components/boards/Boards.vue @@ -43,8 +43,8 @@ <script> -import BoardItem from './BoardItem' -import Controls from '../Controls' +import BoardItem from './BoardItem.vue' +import Controls from '../Controls.vue' export default { name: 'Boards', diff --git a/src/components/card/AttachmentList.vue b/src/components/card/AttachmentList.vue index 4df418d54..7f9017b00 100644 --- a/src/components/card/AttachmentList.vue +++ b/src/components/card/AttachmentList.vue @@ -104,14 +104,14 @@ <script> import axios from '@nextcloud/axios' import { NcActions, NcActionButton, ActionLink } from '@nextcloud/vue' -import AttachmentDragAndDrop from '../AttachmentDragAndDrop' -import relativeDate from '../../mixins/relativeDate' +import AttachmentDragAndDrop from '../AttachmentDragAndDrop.vue' +import relativeDate from '../../mixins/relativeDate.js' import { formatFileSize } from '@nextcloud/files' import { getCurrentUser } from '@nextcloud/auth' import { generateUrl, generateOcsUrl, generateRemoteUrl } from '@nextcloud/router' import { mapState, mapActions } from 'vuex' import { loadState } from '@nextcloud/initial-state' -import attachmentUpload from '../../mixins/attachmentUpload' +import attachmentUpload from '../../mixins/attachmentUpload.js' import { getFilePickerBuilder } from '@nextcloud/dialogs' const maxUploadSizeState = loadState('deck', 'maxUploadSize') diff --git a/src/components/card/CardSidebar.vue b/src/components/card/CardSidebar.vue index dc546850f..f08bd147c 100644 --- a/src/components/card/CardSidebar.vue +++ b/src/components/card/CardSidebar.vue @@ -85,11 +85,11 @@ import { NcActionButton, NcAppSidebar, NcAppSidebarTab } from '@nextcloud/vue' import { generateUrl } from '@nextcloud/router' import { mapState, mapGetters } from 'vuex' -import CardSidebarTabDetails from './CardSidebarTabDetails' -import CardSidebarTabAttachments from './CardSidebarTabAttachments' -import CardSidebarTabComments from './CardSidebarTabComments' -import CardSidebarTabActivity from './CardSidebarTabActivity' -import relativeDate from '../../mixins/relativeDate' +import CardSidebarTabDetails from './CardSidebarTabDetails.vue' +import CardSidebarTabAttachments from './CardSidebarTabAttachments.vue' +import CardSidebarTabComments from './CardSidebarTabComments.vue' +import CardSidebarTabActivity from './CardSidebarTabActivity.vue' +import relativeDate from '../../mixins/relativeDate.js' import moment from '@nextcloud/moment' import AttachmentIcon from 'vue-material-design-icons/Paperclip.vue' diff --git a/src/components/card/CardSidebarTabActivity.vue b/src/components/card/CardSidebarTabActivity.vue index 60d18790d..8b6e1f0cb 100644 --- a/src/components/card/CardSidebarTabActivity.vue +++ b/src/components/card/CardSidebarTabActivity.vue @@ -30,7 +30,7 @@ </template> <script> -import ActivityList from '../ActivityList' +import ActivityList from '../ActivityList.vue' export default { name: 'CardSidebarTabActivity', diff --git a/src/components/card/CardSidebarTabAttachments.vue b/src/components/card/CardSidebarTabAttachments.vue index 06dbd6662..54e03be8e 100644 --- a/src/components/card/CardSidebarTabAttachments.vue +++ b/src/components/card/CardSidebarTabAttachments.vue @@ -28,7 +28,7 @@ </template> <script> -import AttachmentList from './AttachmentList' +import AttachmentList from './AttachmentList.vue' export default { name: 'CardSidebarTabAttachments', components: { diff --git a/src/components/card/CardSidebarTabComments.vue b/src/components/card/CardSidebarTabComments.vue index 7e50484f7..ff5fbce3c 100644 --- a/src/components/card/CardSidebarTabComments.vue +++ b/src/components/card/CardSidebarTabComments.vue @@ -36,8 +36,8 @@ <script> import { mapState, mapGetters } from 'vuex' import { NcAvatar } from '@nextcloud/vue' -import CommentItem from './CommentItem' -import CommentForm from './CommentForm' +import CommentItem from './CommentItem.vue' +import CommentForm from './CommentForm.vue' import InfiniteLoading from 'vue-infinite-loading' import { getCurrentUser } from '@nextcloud/auth' diff --git a/src/components/card/CardSidebarTabDetails.vue b/src/components/card/CardSidebarTabDetails.vue index 67c552748..a860de83b 100644 --- a/src/components/card/CardSidebarTabDetails.vue +++ b/src/components/card/CardSidebarTabDetails.vue @@ -129,14 +129,14 @@ import { NcAvatar, NcActions, NcActionButton, NcMultiselect, NcDatetimePicker } import { loadState } from '@nextcloud/initial-state' import { CollectionList } from 'nextcloud-vue-collections' -import Color from '../../mixins/color' +import Color from '../../mixins/color.js' import { getLocale, getDayNamesMin, getFirstDay, getMonthNamesShort, } from '@nextcloud/l10n' -import Description from './Description' +import Description from './Description.vue' export default { name: 'CardSidebarTabDetails', diff --git a/src/components/card/CommentForm.vue b/src/components/card/CommentForm.vue index eb4d32cdb..bf4ab08b1 100644 --- a/src/components/card/CommentForm.vue +++ b/src/components/card/CommentForm.vue @@ -65,7 +65,7 @@ import { mapState } from 'vuex' import { NcUserBubble, NcAvatar } from '@nextcloud/vue' import At from 'vue-at' -import { rawToParsed } from '../../helpers/mentions' +import { rawToParsed } from '../../helpers/mentions.js' export default { name: 'CommentForm', diff --git a/src/components/card/CommentItem.vue b/src/components/card/CommentItem.vue index 915ec37a8..17236639f 100644 --- a/src/components/card/CommentItem.vue +++ b/src/components/card/CommentItem.vue @@ -66,10 +66,10 @@ <script> import { NcAvatar, NcActions, NcActionButton, NcUserBubble } from '@nextcloud/vue' import RichText from '@juliushaertl/vue-richtext' -import CommentForm from './CommentForm' +import CommentForm from './CommentForm.vue' import { getCurrentUser } from '@nextcloud/auth' import md5 from 'blueimp-md5' -import relativeDate from '../../mixins/relativeDate' +import relativeDate from '../../mixins/relativeDate.js' import ReplyIcon from 'vue-material-design-icons/Reply' const AtMention = { diff --git a/src/components/card/Description.vue b/src/components/card/Description.vue index f53cfcc99..3d05db44a 100644 --- a/src/components/card/Description.vue +++ b/src/components/card/Description.vue @@ -78,7 +78,7 @@ import MarkdownIt from 'markdown-it' import MarkdownItTaskCheckbox from 'markdown-it-task-checkbox' import MarkdownItLinkAttributes from 'markdown-it-link-attributes' -import AttachmentList from './AttachmentList' +import AttachmentList from './AttachmentList.vue' import { NcActions, NcActionButton, NcModal } from '@nextcloud/vue' import { formatFileSize } from '@nextcloud/files' import { generateUrl } from '@nextcloud/router' diff --git a/src/components/cards/CardBadges.vue b/src/components/cards/CardBadges.vue index 61a79049f..f0dfb7266 100644 --- a/src/components/cards/CardBadges.vue +++ b/src/components/cards/CardBadges.vue @@ -49,8 +49,8 @@ </div> </template> <script> -import NcAvatarList from './AvatarList' -import CardMenu from './CardMenu' +import NcAvatarList from './AvatarList.vue' +import CardMenu from './CardMenu.vue' import TextIcon from 'vue-material-design-icons/Text.vue' import AttachmentIcon from 'vue-material-design-icons/Paperclip.vue' import CheckmarkIcon from 'vue-material-design-icons/CheckboxMarked.vue' diff --git a/src/components/cards/CardItem.vue b/src/components/cards/CardItem.vue index 972d1632c..6cc18b21e 100644 --- a/src/components/cards/CardItem.vue +++ b/src/components/cards/CardItem.vue @@ -83,12 +83,12 @@ <script> import ClickOutside from 'vue-click-outside' import { mapState, mapGetters } from 'vuex' -import CardBadges from './CardBadges' -import Color from '../../mixins/color' -import labelStyle from '../../mixins/labelStyle' -import AttachmentDragAndDrop from '../AttachmentDragAndDrop' -import CardMenu from './CardMenu' -import DueDate from './badges/DueDate' +import CardBadges from './CardBadges.vue' +import Color from '../../mixins/color.js' +import labelStyle from '../../mixins/labelStyle.js' +import AttachmentDragAndDrop from '../AttachmentDragAndDrop.vue' +import CardMenu from './CardMenu.vue' +import DueDate from './badges/DueDate.vue' export default { name: 'CardItem', diff --git a/src/components/icons/DeckIcon.vue b/src/components/icons/DeckIcon.vue index c5948827b..ad39aef34 100644 --- a/src/components/icons/DeckIcon.vue +++ b/src/components/icons/DeckIcon.vue @@ -1,38 +1,55 @@ <template> - <svg xmlns="http://www.w3.org/2000/svg" - :height="size" - :width="size" - version="1.1" - viewBox="0 0 16 16"> - <rect ry="1" - height="8" - width="14" - y="7" - x="1" /> - <rect ry=".5" - height="1" - width="12" - y="5" - x="2" /> - <rect ry=".5" - height="1" - width="10" - y="3" - x="3" /> - <rect ry=".5" - height="1" - width="8" - y="1" - x="4" /> - </svg> + <span :aria-hidden="!title" + :aria-label="title" + class="material-design-icon deck-icon" + role="img" + v-bind="$attrs" + @click="$emit('click', $event)"> + <svg xmlns="http://www.w3.org/2000/svg" + :fill="fillColor" + :height="size" + :width="size" + version="1.1" + viewBox="0 0 16 16"> + <rect ry="1" + height="8" + width="14" + y="7" + x="1" /> + <rect ry=".5" + height="1" + width="12" + y="5" + x="2" /> + <rect ry=".5" + height="1" + width="10" + y="3" + x="3" /> + <rect ry=".5" + height="1" + width="8" + y="1" + x="4" /> + </svg> + </span> </template> + <script> export default { name: 'DeckIcon', props: { + title: { + type: String, + default: '', + }, + fillColor: { + type: String, + default: 'currentColor', + }, size: { type: Number, - default: 16, + default: 24, }, }, } diff --git a/src/components/navigation/AppNavigation.vue b/src/components/navigation/AppNavigation.vue index a954017a5..31b8d8911 100644 --- a/src/components/navigation/AppNavigation.vue +++ b/src/components/navigation/AppNavigation.vue @@ -107,8 +107,8 @@ import axios from '@nextcloud/axios' import { mapGetters } from 'vuex' import ClickOutside from 'vue-click-outside' import { NcAppNavigation, NcAppNavigationItem, NcAppNavigationSettings, NcMultiselect } from '@nextcloud/vue' -import AppNavigationAddBoard from './AppNavigationAddBoard' -import AppNavigationBoardCategory from './AppNavigationBoardCategory' +import AppNavigationAddBoard from './AppNavigationAddBoard.vue' +import AppNavigationBoardCategory from './AppNavigationBoardCategory.vue' import { loadState } from '@nextcloud/initial-state' import { generateOcsUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' diff --git a/src/components/navigation/AppNavigationBoardCategory.vue b/src/components/navigation/AppNavigationBoardCategory.vue index 71b282dab..805128174 100644 --- a/src/components/navigation/AppNavigationBoardCategory.vue +++ b/src/components/navigation/AppNavigationBoardCategory.vue @@ -34,7 +34,7 @@ </template> <script> -import AppNavigationBoard from './AppNavigationBoard' +import AppNavigationBoard from './AppNavigationBoard.vue' import { NcAppNavigationItem } from '@nextcloud/vue' export default { diff --git a/src/components/overview/Overview.vue b/src/components/overview/Overview.vue index f097034c3..a7dedef39 100644 --- a/src/components/overview/Overview.vue +++ b/src/components/overview/Overview.vue @@ -80,11 +80,11 @@ <script> -import Controls from '../Controls' -import CardItem from '../cards/CardItem' +import Controls from '../Controls.vue' +import CardItem from '../cards/CardItem.vue' import { mapGetters } from 'vuex' import moment from '@nextcloud/moment' -import GlobalSearchResults from '../search/GlobalSearchResults' +import GlobalSearchResults from '../search/GlobalSearchResults.vue' const FILTER_UPCOMING = 'upcoming' diff --git a/src/components/search/GlobalSearchResults.vue b/src/components/search/GlobalSearchResults.vue index 89f8f5398..4fa81e7db 100644 --- a/src/components/search/GlobalSearchResults.vue +++ b/src/components/search/GlobalSearchResults.vue @@ -52,13 +52,13 @@ </template> <script> -import CardItem from '../cards/CardItem' +import CardItem from '../cards/CardItem.vue' import { mapState } from 'vuex' import axios from '@nextcloud/axios' import { generateOcsUrl } from '@nextcloud/router' import InfiniteLoading from 'vue-infinite-loading' import RichText from '@juliushaertl/vue-richtext' -import Placeholder from './Placeholder' +import Placeholder from './Placeholder.vue' import { NcActions, NcActionButton } from '@nextcloud/vue' const createCancelToken = () => axios.CancelToken.source() diff --git a/src/init-card-reference.js b/src/init-card-reference.js new file mode 100644 index 000000000..bb8093f21 --- /dev/null +++ b/src/init-card-reference.js @@ -0,0 +1,51 @@ +/** + * @copyright Copyright (c) 2022 Julien Veyssier <eneiluj@posteo.net> + * + * @author Julien Veyssier <eneiluj@posteo.net> + * + * @license AGPL-3.0-or-later + * + * 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/>. + */ + +import { registerWidget } from '@nextcloud/vue-richtext' +import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js' +import Vue from 'vue' +import CardReferenceWidget from './views/CardReferenceWidget.vue' + +import { translate, translatePlural } from '@nextcloud/l10n' + +Vue.prototype.t = translate +Vue.prototype.n = translatePlural +Vue.prototype.OC = window.OC +Vue.prototype.OCA = window.OCA +Vue.directive('tooltip', Tooltip) + +registerWidget('deck-card', (el, { richObjectType, richObject, accessible }) => { + // trick to change the wrapper element size, otherwise it always is 100% + // which is not very nice with a simple card + el.parentNode.style['max-width'] = '400px' + el.parentNode.style['min-width'] = '200px' + el.parentNode.style['margin-left'] = '0' + el.parentNode.style['margin-right'] = '0' + + const Widget = Vue.extend(CardReferenceWidget) + new Widget({ + propsData: { + richObjectType, + richObject, + accessible, + }, + }).$mount(el) +}) diff --git a/src/init-collections.js b/src/init-collections.js index 8c1f2024c..f26565ee4 100644 --- a/src/init-collections.js +++ b/src/init-collections.js @@ -23,8 +23,8 @@ import Vue from 'vue' import './../css/collections.css' -import FileSharingPicker from './views/FileSharingPicker' -import { buildSelector } from './helpers/selector' +import FileSharingPicker from './views/FileSharingPicker.js' +import { buildSelector } from './helpers/selector.js' // eslint-disable-next-line __webpack_nonce__ = btoa(OC.requestToken); @@ -44,7 +44,7 @@ window.addEventListener('DOMContentLoaded', () => { window.OCP.Collaboration.registerType('deck', { action: () => { - const BoardSelector = () => import('./BoardSelector') + const BoardSelector = () => import('./BoardSelector.vue') return buildSelector(BoardSelector) }, typeString: t('deck', 'Link to a board'), @@ -53,7 +53,7 @@ window.addEventListener('DOMContentLoaded', () => { window.OCP.Collaboration.registerType('deck-card', { action: () => { - const CardSelector = () => import('./CardSelector') + const CardSelector = () => import('./CardSelector.vue') return buildSelector(CardSelector) }, typeString: t('deck', 'Link to a card'), diff --git a/src/init-dashboard.js b/src/init-dashboard.js index e4f6a741b..c618fd17d 100644 --- a/src/init-dashboard.js +++ b/src/init-dashboard.js @@ -23,7 +23,7 @@ import Vue from 'vue' import Vuex from 'vuex' -import overview from './store/overview' +import overview from './store/overview.js' import './css/dashboard.scss' diff --git a/src/init-talk.js b/src/init-talk.js index 3998a4a88..943cbe22b 100644 --- a/src/init-talk.js +++ b/src/init-talk.js @@ -23,9 +23,9 @@ import Vue from 'vue' import { generateUrl } from '@nextcloud/router' -import CardCreateDialog from './CardCreateDialog' -import { buildSelector } from './helpers/selector' -import './init-collections' +import CardCreateDialog from './CardCreateDialog.vue' +import { buildSelector } from './helpers/selector.js' +import './init-collections.js' // eslint-disable-next-line __webpack_nonce__ = btoa(OC.requestToken); diff --git a/src/main.js b/src/main.js index cacbf01aa..2d1a28132 100644 --- a/src/main.js +++ b/src/main.js @@ -20,9 +20,9 @@ * */ import Vue from 'vue' -import App from './App' -import router from './router' -import store from './store/main' +import App from './App.vue' +import router from './router.js' +import store from './store/main.js' import { sync } from 'vuex-router-sync' import { translate, translatePlural } from '@nextcloud/l10n' import { generateFilePath } from '@nextcloud/router' @@ -30,7 +30,7 @@ import { showError } from '@nextcloud/dialogs' import { subscribe } from '@nextcloud/event-bus' import { Tooltip } from '@nextcloud/vue' import ClickOutside from 'vue-click-outside' -import './models' +import './models/index.js' // the server snap.js conflicts with vertical scrolling so we disable it document.body.setAttribute('data-snap-ignore', 'true') diff --git a/src/mixins/labelStyle.js b/src/mixins/labelStyle.js index 8fe1d62b9..3f91b68bb 100644 --- a/src/mixins/labelStyle.js +++ b/src/mixins/labelStyle.js @@ -20,7 +20,7 @@ * */ -import Color from './color' +import Color from './color.js' export default { mixins: [Color], diff --git a/src/router.js b/src/router.js index ca0657395..4a4fa961a 100644 --- a/src/router.js +++ b/src/router.js @@ -23,13 +23,13 @@ import Vue from 'vue' import Router from 'vue-router' import { generateUrl } from '@nextcloud/router' -import { BOARD_FILTERS } from './store/main' -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 Overview from './components/overview/Overview' +import { BOARD_FILTERS } from './store/main.js' +import Boards from './components/boards/Boards.vue' +import Board from './components/board/Board.vue' +import Sidebar from './components/Sidebar.vue' +import BoardSidebar from './components/board/BoardSidebar.vue' +import CardSidebar from './components/card/CardSidebar.vue' +import Overview from './components/overview/Overview.vue' Vue.use(Router) diff --git a/src/services/BoardApi.js b/src/services/BoardApi.js index f6e2e8091..38b1d746f 100644 --- a/src/services/BoardApi.js +++ b/src/services/BoardApi.js @@ -22,7 +22,7 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' -import './../models' +import '../models/index.js' /** * This class handles all the api communication with the Deck backend. diff --git a/src/services/StackApi.js b/src/services/StackApi.js index 5e2322f93..26d3e7e04 100644 --- a/src/services/StackApi.js +++ b/src/services/StackApi.js @@ -22,7 +22,7 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' -import './../models' +import '../models/index.js' export class StackApi { diff --git a/src/store/attachment.js b/src/store/attachment.js index 1115b3405..c0db49e37 100644 --- a/src/store/attachment.js +++ b/src/store/attachment.js @@ -20,7 +20,7 @@ * */ -import { AttachmentApi } from './../services/AttachmentApi' +import { AttachmentApi } from './../services/AttachmentApi.js' import Vue from 'vue' const apiClient = new AttachmentApi() diff --git a/src/store/card.js b/src/store/card.js index 1c339ece0..4c2a902a6 100644 --- a/src/store/card.js +++ b/src/store/card.js @@ -20,7 +20,7 @@ * */ -import { CardApi } from './../services/CardApi' +import { CardApi } from './../services/CardApi.js' import moment from 'moment' import Vue from 'vue' diff --git a/src/store/comment.js b/src/store/comment.js index ad58a5719..69a0b35b4 100644 --- a/src/store/comment.js +++ b/src/store/comment.js @@ -20,7 +20,7 @@ * */ -import { CommentApi } from '../services/CommentApi' +import { CommentApi } from '../services/CommentApi.js' import Vue from 'vue' const apiClient = new CommentApi() diff --git a/src/store/main.js b/src/store/main.js index 9c65509bc..52c26ab79 100644 --- a/src/store/main.js +++ b/src/store/main.js @@ -27,14 +27,14 @@ import Vue from 'vue' import Vuex from 'vuex' import axios from '@nextcloud/axios' import { generateOcsUrl, generateUrl } from '@nextcloud/router' -import { BoardApi } from '../services/BoardApi' -import actions from './actions' -import stack from './stack' -import card from './card' -import comment from './comment' -import trashbin from './trashbin' -import attachment from './attachment' -import overview from './overview' +import { BoardApi } from '../services/BoardApi.js' +import actions from './actions.js' +import stack from './stack.js' +import card from './card.js' +import comment from './comment.js' +import trashbin from './trashbin.js' +import attachment from './attachment.js' +import overview from './overview.js' Vue.use(Vuex) const apiClient = new BoardApi() diff --git a/src/store/overview.js b/src/store/overview.js index 214ec2901..e6eeb3c02 100644 --- a/src/store/overview.js +++ b/src/store/overview.js @@ -22,7 +22,7 @@ import Vue from 'vue' import Vuex from 'vuex' -import { OverviewApi } from '../services/OverviewApi' +import { OverviewApi } from '../services/OverviewApi.js' Vue.use(Vuex) const apiClient = new OverviewApi() diff --git a/src/store/stack.js b/src/store/stack.js index bbb0486be..f55d6211d 100644 --- a/src/store/stack.js +++ b/src/store/stack.js @@ -21,8 +21,8 @@ */ import Vue from 'vue' -import { StackApi } from './../services/StackApi' -import applyOrderToArray from './../helpers/applyOrderToArray' +import { StackApi } from './../services/StackApi.js' +import applyOrderToArray from './../helpers/applyOrderToArray.js' const apiClient = new StackApi() diff --git a/src/store/trashbin.js b/src/store/trashbin.js index c508b88a5..fc90df623 100644 --- a/src/store/trashbin.js +++ b/src/store/trashbin.js @@ -20,8 +20,8 @@ * */ -import { StackApi } from '../services/StackApi' -import { CardApi } from '../services/CardApi' +import { StackApi } from '../services/StackApi.js' +import { CardApi } from '../services/CardApi.js' const stackApi = new StackApi() const cardApi = new CardApi() diff --git a/src/views/CardReferenceWidget.vue b/src/views/CardReferenceWidget.vue new file mode 100644 index 000000000..a57ee0537 --- /dev/null +++ b/src/views/CardReferenceWidget.vue @@ -0,0 +1,224 @@ +<!-- + - @copyright Copyright (c) 2022 2022 Julien Veyssier <eneiluj@posteo.net> + - + - @author 2022 Julien Veyssier <eneiluj@posteo.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 class="deck-card-reference"> + <div class="line"> + <DeckIcon :size="20" class="title-icon" /> + <strong> + <a :href="cardLink" + :title="cardTooltip" + target="_blank" + class="link"> + {{ card.title }} + </a> + </strong> + <div v-if="dueDate" class="spacer" /> + <span v-if="dueDate" + v-tooltip.top="{ content: formattedDueDate }" + class="due-date"> + <CalendarBlankIcon :size="20" + class="icon" /> + {{ dueDate }} + </span> + </div> + <div class="line"> + <a v-tooltip.top="{ content: stackTooltip }" + :href="boardLink" + target="_blank" + class="link"> + {{ t('deck', '{stack} in {board}', { stack: stack.title, board: board.title }) }} + </a> + </div> + <div> + <transition-group v-if="card.labels && card.labels.length" + name="zoom" + tag="ul" + class="labels" + @click.stop="openCard"> + <li v-for="label in labelsSorted" :key="label.id" :style="labelStyle(label)"> + <span>{{ label.title }}</span> + </li> + </transition-group> + </div> + <div class="line description-assignees"> + <TextIcon v-if="card.description" :size="20" class="icon" /> + <div v-if="card.description" + class="description" + :title="card.description"> + {{ card.description }} + </div> + <div v-if="card.assignedUsers .length > 0" + class="spacer" /> + <AvatarList v-if="card.assignedUsers .length > 0" + :users="card.assignedUsers" + class="card-assignees" /> + </div> + </div> +</template> + +<script> +import CalendarBlankIcon from 'vue-material-design-icons/CalendarBlank.vue' +import TextIcon from 'vue-material-design-icons/Text.vue' + +import DeckIcon from '../components/icons/DeckIcon.vue' +import AvatarList from '../components/cards/AvatarList.vue' +import labelStyle from '../mixins/labelStyle.js' + +import moment from '@nextcloud/moment' +import { generateUrl } from '@nextcloud/router' + +export default { + name: 'CardReferenceWidget', + + components: { + AvatarList, + DeckIcon, + CalendarBlankIcon, + TextIcon, + }, + + mixins: [labelStyle], + + props: { + richObjectType: { + type: String, + default: '', + }, + richObject: { + type: Object, + default: null, + }, + accessible: { + type: Boolean, + default: true, + }, + }, + + data() { + return { + } + }, + + computed: { + card() { + return this.richObject.card + }, + board() { + return this.richObject.board + }, + stack() { + return this.richObject.stack + }, + cardLink() { + return generateUrl('/apps/deck/#/board/{boardId}/card/{cardId}', { boardId: this.board.id, cardId: this.card.id }) + }, + boardLink() { + return generateUrl('/apps/deck/#/board/{boardId}', { boardId: this.board.id }) + }, + cardTooltip() { + return t('deck', '* Created on {created}\n* Last modified on {lastMod}\n* {nbAttachments} attachments\n* {nbComments} comments', { + created: moment.unix(this.card.createdAt).format('LLL'), + lastMod: moment.unix(this.card.lastModified).format('LLL'), + nbAttachments: this.card.attachments.length, + nbComments: this.card.commentsCount, + }) + }, + stackTooltip() { + return t('deck', '{nbCards} cards', { nbCards: this.stack.cards.length }) + }, + dueDate() { + return this.card.duedate + ? moment(this.card.duedate).fromNow() + : null + }, + formattedDueDate() { + return this.card.duedate + ? t('deck', 'Due on {date}', { date: moment(this.card.duedate).format('LLL') }) + : null + }, + labelsSorted() { + return [...this.card.labels].sort((a, b) => (a.title < b.title) ? -1 : 1) + }, + }, + + methods: { + }, +} +</script> + +<style scoped lang="scss"> +/* stylelint-disable-next-line no-invalid-position-at-import-rule */ +@import '../css/labels'; + +.deck-card-reference { + width: 100%; + white-space: normal; + padding: 12px; + + .link { + text-decoration: underline; + } + + .line { + display: flex; + align-items: center; + .icon { + margin-right: 4px; + } + .title-icon { + margin-right: 8px; + } + } + + .due-date { + display: flex; + align-items: center; + } + + .labels { + margin: 8px 0; + } + + .description-assignees { + width: 100%; + display: flex; + align-items: center; + + .description { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin-right: 8px; + } + + .card-assignees { + margin-top: 0; + height: 36px; + flex-grow: unset; + } + } + + .spacer { + flex-grow: 1; + } +} +</style> diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 4daf7a000..958d07d8c 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -59,10 +59,10 @@ <script> import { NcDashboardWidget } from '@nextcloud/vue' import { mapGetters } from 'vuex' -import labelStyle from './../mixins/labelStyle' -import DueDate from '../components/cards/badges/DueDate' +import labelStyle from './../mixins/labelStyle.js' +import DueDate from '../components/cards/badges/DueDate.vue' import { generateUrl } from '@nextcloud/router' -import CardCreateDialog from '../CardCreateDialog' +import CardCreateDialog from '../CardCreateDialog.vue' export default { name: 'Dashboard', diff --git a/src/views/FileSharingPicker.js b/src/views/FileSharingPicker.js index 099d1f4b9..af6eeb90b 100644 --- a/src/views/FileSharingPicker.js +++ b/src/views/FileSharingPicker.js @@ -21,7 +21,7 @@ */ import Vue from 'vue' -import { createShare } from '../services/SharingApi' +import { createShare } from '../services/SharingApi.js' export default { icon: 'icon-deck', @@ -33,7 +33,7 @@ export default { container.id = 'deck-board-select' const body = document.getElementById('body-user') body.append(container) - const CardSelector = () => import('./../CardSelector') + const CardSelector = () => import('./../CardSelector.vue') const ComponentVM = new Vue({ render: (h) => h(CardSelector, { title: t('deck', 'Share {file} with a Deck card', { file: decodeURIComponent(self.fileInfo.name) }), diff --git a/webpack.js b/webpack.js index d1c817be6..d5db80b35 100644 --- a/webpack.js +++ b/webpack.js @@ -1,5 +1,10 @@ const webpackConfig = require('@nextcloud/webpack-vue-config') const path = require('path') +const ESLintPlugin = require('eslint-webpack-plugin') +const StyleLintPlugin = require('stylelint-webpack-plugin') + +const buildMode = process.env.NODE_ENV +const isDev = buildMode === 'development' webpackConfig.entry = { ...webpackConfig.entry, @@ -7,6 +12,7 @@ webpackConfig.entry = { dashboard: path.join(__dirname, 'src', 'init-dashboard.js'), calendar: path.join(__dirname, 'src', 'init-calendar.js'), talk: path.join(__dirname, 'src', 'init-talk.js'), + 'card-reference': path.join(__dirname, 'src', 'init-card-reference.js'), } webpackConfig.stats = { @@ -17,4 +23,18 @@ webpackConfig.stats = { modules: true, } +webpackConfig.plugins.push( + new ESLintPlugin({ + extensions: ['js', 'vue'], + files: 'src', + failOnError: !isDev, + }) +) +webpackConfig.plugins.push( + new StyleLintPlugin({ + files: 'src/**/*.{css,scss,vue}', + failOnError: !isDev, + }), +) + module.exports = webpackConfig