Browse Source

Add a dedicated page for the recommended apps installation

Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Signed-off-by: npmbuildbot[bot] <npmbuildbot[bot]@users.noreply.github.com>
Christoph Wurst 4 years ago
parent
commit
302558cfd2

File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-4.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-4.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-5.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-5.js.map


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-apps-users-management.js


File diff suppressed because it is too large
+ 0 - 0
apps/settings/js/vue-settings-apps-users-management.js.map


+ 16 - 36
apps/settings/src/components/AppList.vue

@@ -96,11 +96,9 @@
 </template>
 
 <script>
-import pLimit from 'p-limit'
-
 import AppItem from './AppList/AppItem'
 import PrefixMixin from './PrefixMixin'
-import recommended from '../recommendedApps'
+import pLimit from 'p-limit'
 
 export default {
 	name: 'AppList',
@@ -131,26 +129,26 @@ export default {
 					return OC.Util.naturalSortCompare(sortStringA, sortStringB)
 				})
 
-			switch (this.category) {
-			case 'installed':
+			if (this.category === 'installed') {
 				return apps.filter(app => app.installed)
-			case 'recommended':
-				return apps.filter(app => recommended.includes(app.id))
-			case 'enabled':
+			}
+			if (this.category === 'enabled') {
 				return apps.filter(app => app.active && app.installed)
-			case 'disabled':
+			}
+			if (this.category === 'disabled') {
 				return apps.filter(app => !app.active && app.installed)
-			case 'app-bundles':
+			}
+			if (this.category === 'app-bundles') {
 				return apps.filter(app => app.bundles)
-			case 'updates':
+			}
+			if (this.category === 'updates') {
 				return apps.filter(app => app.update)
-			default:
-				// filter app store categories
-				return apps.filter(app => {
-					return app.appstore && app.category !== undefined
-							&& (app.category === this.category || app.category.indexOf(this.category) > -1)
-				})
 			}
+			// filter app store categories
+			return apps.filter(app => {
+				return app.appstore && app.category !== undefined
+					&& (app.category === this.category || app.category.indexOf(this.category) > -1)
+			})
 		},
 		bundles() {
 			return this.$store.getters.getServerData.bundles.filter(bundle => this.bundleApps(bundle.id).length > 0)
@@ -177,7 +175,7 @@ export default {
 			return !this.useListView && !this.useBundleView
 		},
 		useListView() {
-			return ['installed', 'recommended', 'enabled', 'disabled', 'updates'].includes(this.category)
+			return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates')
 		},
 		useBundleView() {
 			return (this.category === 'app-bundles')
@@ -198,24 +196,6 @@ export default {
 			}
 		}
 	},
-	mounted() {
-		if (this.category === 'recommended' && 'download' in this.$route.query) {
-			const limit = pLimit(1)
-			const installing = this.apps
-				.filter(app => !app.active && app.canInstall)
-				.map(app => limit(() => this.$store.dispatch('enableApp', { appId: app.id, groups: [] })))
-			console.debug(`installing ${installing.length} recommended apps`)
-			Promise.all(installing)
-				.then(() => {
-					console.info('recommended apps installed')
-
-					if ('returnTo' in this.$route.query) {
-						window.location = this.$route.query.returnTo
-					}
-				})
-				.catch(e => console.error('could not install recommended apps', e))
-		}
-	},
 	methods: {
 		toggleBundle(id) {
 			if (this.allBundlesEnabled(id)) {

+ 2 - 13
apps/settings/src/views/Apps.vue

@@ -31,10 +31,7 @@
 			</ul>
 		</AppNavigation>
 		<AppContent class="app-settings-content" :class="{ 'icon-loading': loadingList }">
-			<AppList v-if="!loadingList"
-				:category="category"
-				:app="currentApp"
-				:search="searchQuery" />
+			<AppList :category="category" :app="currentApp" :search="searchQuery" />
 		</AppContent>
 		<AppSidebar v-if="id && currentApp" @close="hideAppDetails">
 			<AppDetails :category="category" :app="currentApp" />
@@ -136,21 +133,13 @@ export default {
 					icon: 'icon-category-installed',
 					text: t('settings', 'Your apps')
 				},
-				{
-					id: 'app-category-recommended',
-					classes: [],
-					router: { name: 'apps-category', params: { category: 'recommended' } },
-					icon: 'icon-category-installed',
-					text: t('settings', 'Recommended apps')
-				},
 				{
 					id: 'app-category-enabled',
 					classes: [],
 					icon: 'icon-category-enabled',
 					router: { name: 'apps-category', params: { category: 'enabled' } },
 					text: t('settings', 'Active apps')
-				},
-				{
+				}, {
 					id: 'app-category-disabled',
 					classes: [],
 					icon: 'icon-category-disabled',

+ 53 - 0
core/Controller/RecommendedAppsController.php

@@ -0,0 +1,53 @@
+<?php declare(strict_types=1);
+
+/**
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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 OC\Core\Controller;
+
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\StandaloneTemplateResponse;
+use OCP\IInitialStateService;
+use OCP\IRequest;
+
+class RecommendedAppsController extends Controller {
+
+	/** @var IInitialStateService */
+	private $initialStateService;
+
+	public function __construct(IRequest $request,
+								IInitialStateService $initialStateService) {
+		parent::__construct('core', $request);
+		$this->initialStateService = $initialStateService;
+	}
+
+	/**
+	 * @NoCSRFRequired
+	 * @return Response
+	 */
+	public function index(): Response {
+		$this->initialStateService->provideInitialState('core', 'defaultPageUrl', \OC_Util::getDefaultPageUrl());
+		return new StandaloneTemplateResponse($this->appName, 'recommendedapps', [], 'guest');
+	}
+
+}

+ 1 - 1
core/Controller/SetupController.php

@@ -123,7 +123,7 @@ class SetupController {
 
 		if ($installRecommended) {
 			$urlGenerator = \OC::$server->getURLGenerator();
-			$location = $urlGenerator->getAbsoluteURL('/index.php/settings/apps/recommended?download&returnTo=' . urlencode(\OC_Util::getDefaultPageUrl()));
+			$location = $urlGenerator->getAbsoluteURL('index.php/core/apps/recommended');
 			header('Location: ' . $location);
 			exit();
 		}

File diff suppressed because it is too large
+ 1 - 1
core/js/dist/login.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/login.js.map


File diff suppressed because it is too large
+ 1 - 1
core/js/dist/main.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/main.js.map


File diff suppressed because it is too large
+ 1 - 1
core/js/dist/maintenance.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/maintenance.js.map


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/recommendedapps.js


File diff suppressed because it is too large
+ 0 - 0
core/js/dist/recommendedapps.js.map


+ 1 - 0
core/routes.php

@@ -74,6 +74,7 @@ $application->registerRoutes($this, [
 		['name' => 'OCJS#getConfig', 'url' => '/core/js/oc.js', 'verb' => 'GET'],
 		['name' => 'Preview#getPreviewByFileId', 'url' => '/core/preview', 'verb' => 'GET'],
 		['name' => 'Preview#getPreview', 'url' => '/core/preview.png', 'verb' => 'GET'],
+		['name' => 'RecommendedApps#index', 'url' => '/core/apps/recommended', 'verb' => 'GET'],
 		['name' => 'Svg#getSvgFromCore', 'url' => '/svg/core/{folder}/{fileName}', 'verb' => 'GET'],
 		['name' => 'Svg#getSvgFromApp', 'url' => '/svg/{app}/{fileName}', 'verb' => 'GET'],
 		['name' => 'Css#getCss', 'url' => '/css/{appName}/{fileName}', 'verb' => 'GET'],

+ 194 - 0
core/src/components/setup/RecommendedApps.vue

@@ -0,0 +1,194 @@
+<!--
+  - @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+  -
+  - @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+  -
+  - @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="update">
+		<h2>{{ t('core', 'Recommended apps') }}</h2>
+		<p v-if="loadingApps" class="loading">
+			{{ t('core', 'Loading apps …') }}
+		</p>
+		<p v-else-if="loadingAppsError" class="loading-error">
+			{{ t('core', 'Could not fetch list of apps from the app store.') }}
+		</p>
+		<p v-else>
+			{{ t('core', 'Installing recommended apps …') }}
+		</p>
+		<div v-for="app in recommendedApps" :key="app.id" class="app">
+			<img :src="customIcon(app.id)" :alt="t('core', 'Nextcloud app {app}', { app: app.name })">
+			<div class="info">
+				<h3>
+					{{ app.name }}
+					<span v-if="app.loading" class="icon icon-loading-small" />
+					<span v-else-if="app.active" class="icon icon-checkmark-white" />
+				</h3>
+				<p v-html="customDescription(app.id)" />
+				<p v-if="app.installationError" class="error">
+					{{ t('core', 'App download or installation failed') }}
+				</p>
+				<p v-else-if="!app.isCompatible" class="error">
+					{{ t('core', 'Can\'t install this app because it is not compatible') }}
+				</p>
+				<p v-else-if="!app.canInstall" class="error">
+					{{ t('core', 'Can\'t install this app') }}
+				</p>
+			</div>
+		</div>
+		<a :href="defaultPageUrl">{{ t('core', 'Go back') }}</a>
+	</div>
+</template>
+
+<script>
+import axios from '@nextcloud/axios'
+import { generateUrl, imagePath } from '@nextcloud/router'
+import { loadState } from '@nextcloud/initial-state'
+import pLimit from 'p-limit'
+import { translate as t } from '@nextcloud/l10n'
+
+import logger from '../../logger'
+
+const recommended = {
+	calendar: {
+		description: t('core', 'Schedule work & meetings, synced with all your devices.'),
+		icon: imagePath('core', 'places/calendar.svg')
+	},
+	contacts: {
+		description: t('core', 'Keep your colleagues and friends in one place without leaking their private info.'),
+		icon: imagePath('core', 'places/contacts.svg')
+	},
+	mail: {
+		description: t('core', 'Simple email app nicely integrated with Files, Contacts and Calendar.'),
+		icon: imagePath('core', 'actions/mail.svg')
+	},
+	talk: {
+		description: t('core', 'Screensharing, online meetings and web conferencing – on desktop and with mobile apps.')
+	}
+}
+const recommendedIds = Object.keys(recommended)
+const defaultPageUrl = loadState('core', 'defaultPageUrl')
+
+export default {
+	name: 'RecommendedApps',
+	data() {
+		return {
+			loadingApps: true,
+			loadingAppsError: false,
+			apps: [],
+			defaultPageUrl
+		}
+	},
+	computed: {
+		recommendedApps() {
+			return this.apps.filter(app => recommendedIds.includes(app.id))
+		}
+	},
+	mounted() {
+		return axios.get(generateUrl('settings/apps/list'))
+			.then(resp => resp.data)
+			.then(data => {
+				logger.info(`${data.apps.length} apps fetched`)
+
+				this.apps = data.apps.map(app => Object.assign(app, { loading: false, installationError: false }))
+				logger.debug(`${this.recommendedApps.length} recommended apps found`, { apps: this.recommendedApps })
+
+				this.installApps()
+			})
+			.catch(error => {
+				logger.error('could not fetch app list', { error })
+
+				this.loadingAppsError = true
+			})
+			.then(() => {
+				this.loadingApps = false
+			})
+	},
+	methods: {
+		installApps() {
+			const limit = pLimit(1)
+			const installing = this.recommendedApps
+				.filter(app => !app.active && app.isCompatible && app.canInstall)
+				.map(app => limit(() => {
+					logger.info(`installing ${app.id}`)
+					app.loading = true
+					return axios.post(generateUrl(`settings/apps/enable`), { appIds: [app.id], groups: [] })
+						.catch(error => {
+							logger.error(`could not install ${app.id}`, { error })
+							app.installationError = true
+						})
+						.then(() => {
+							logger.info(`installed ${app.id}`)
+							app.loading = false
+						})
+				}))
+			logger.debug(`installing ${installing.length} recommended apps`)
+			Promise.all(installing)
+				.then(() => {
+					logger.info('all recommended apps installed, redirecting …')
+
+					window.location = defaultPageUrl
+				})
+				.catch(error => logger.error('could not install recommended apps', { error }))
+		},
+		customIcon(appId) {
+			if (!(appId in recommended)) {
+				logger.warn(`no app icon for recommended app ${appId}`)
+				return imagePath('core', 'places/default-app-icon.svg')
+			}
+			return recommended[appId].icon
+		},
+		customDescription(appId) {
+			if (!(appId in recommended)) {
+				logger.warn(`no app description for recommended app ${appId}`)
+				return ''
+			}
+			return recommended[appId].description
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+p.loading, p.loading-error {
+	height: 100px;
+}
+.app {
+	display: flex;
+	flex-direction: row;
+
+	img {
+		height: 64px;
+		width: 64px;
+	}
+
+	img, .info {
+		padding: 12px;
+	}
+
+	.info {
+		h3 {
+			text-align: left;
+		}
+
+		h3 > span.icon {
+			display: inline-block;
+		}
+	}
+}
+</style>

+ 37 - 0
core/src/logger.js

@@ -0,0 +1,37 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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/>.
+ */
+
+import { getCurrentUser } from '@nextcloud/auth'
+import { getLoggerBuilder } from '@nextcloud/logger'
+
+const getLogger = user => {
+	if (user === null) {
+		return getLoggerBuilder()
+			.setApp('core')
+			.build()
+	}
+	return getLoggerBuilder()
+		.setApp('core')
+		.setUid(user.uid)
+		.build()
+}
+
+export default getLogger(getCurrentUser())

+ 44 - 0
core/src/recommendedapps.js

@@ -0,0 +1,44 @@
+/*
+ * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @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/>.
+ */
+
+import { getRequestToken } from '@nextcloud/auth'
+import { generateFilePath } from '@nextcloud/router'
+import { translate as t } from '@nextcloud/l10n'
+import Vue from 'vue'
+
+import logger from './logger'
+import RecommendedApps from './components/setup/RecommendedApps'
+
+// eslint-disable-next-line camelcase
+__webpack_nonce__ = btoa(getRequestToken())
+// eslint-disable-next-line camelcase
+__webpack_public_path__ = generateFilePath('core', '', 'js/')
+
+Vue.mixin({
+	methods: {
+		t
+	}
+})
+
+const View = Vue.extend(RecommendedApps)
+new View().$mount('#recommended-apps')
+
+logger.debug('recommended apps view rendered')

+ 8 - 6
apps/settings/src/recommendedApps.js → core/templates/recommendedapps.php

@@ -1,4 +1,6 @@
-/*
+<?php declare(strict_types=1);
+
+/**
  * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  *
  * @author 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
@@ -19,8 +21,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-export default [
-	'contacts',
-	'calendar',
-	'mail'
-]
+script('core', 'dist/recommendedapps');
+
+?>
+
+<div id="recommended-apps"></div>

+ 1 - 0
core/webpack.js

@@ -7,6 +7,7 @@ module.exports = [
 			login: path.join(__dirname, 'src/login.js'),
 			main: path.join(__dirname, 'src/main.js'),
 			maintenance: path.join(__dirname, 'src/maintenance.js'),
+			recommendedapps: path.join(__dirname, 'src/recommendedapps.js'),
 		},
 		output: {
 			filename: '[name].js',

+ 1 - 0
lib/composer/composer/autoload_classmap.php

@@ -789,6 +789,7 @@ return array(
     'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php',
     'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php',
     'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php',
+    'OC\\Core\\Controller\\RecommendedAppsController' => $baseDir . '/core/Controller/RecommendedAppsController.php',
     'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php',
     'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php',
     'OC\\Core\\Controller\\SvgController' => $baseDir . '/core/Controller/SvgController.php',

+ 1 - 0
lib/composer/composer/autoload_static.php

@@ -818,6 +818,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php',
         'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php',
         'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php',
+        'OC\\Core\\Controller\\RecommendedAppsController' => __DIR__ . '/../../..' . '/core/Controller/RecommendedAppsController.php',
         'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php',
         'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php',
         'OC\\Core\\Controller\\SvgController' => __DIR__ . '/../../..' . '/core/Controller/SvgController.php',

+ 29 - 14
package-lock.json

@@ -2168,6 +2168,21 @@
         }
       }
     },
+    "@nextcloud/l10n": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-0.2.1.tgz",
+      "integrity": "sha512-iLdyxluCehsRibR4R/nH3O8T9CcGoAaW3eWEdQW2qPtn6eEiBXASek5nWhXa5hko1GvE7koYia4FoTWuL85/Ng==",
+      "requires": {
+        "core-js": "3.2.1"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
+          "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw=="
+        }
+      }
+    },
     "@nextcloud/logger": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-0.1.0.tgz",
@@ -3122,7 +3137,7 @@
     },
     "browserify-aes": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
       "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
       "dev": true,
       "requires": {
@@ -3159,7 +3174,7 @@
     },
     "browserify-rsa": {
       "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+      "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
       "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
       "dev": true,
       "requires": {
@@ -3203,7 +3218,7 @@
     },
     "buffer": {
       "version": "4.9.1",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+      "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
       "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
       "dev": true,
       "requires": {
@@ -3644,7 +3659,7 @@
     },
     "create-hash": {
       "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+      "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
       "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
       "dev": true,
       "requires": {
@@ -3657,7 +3672,7 @@
     },
     "create-hmac": {
       "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+      "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
       "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
       "dev": true,
       "requires": {
@@ -3918,7 +3933,7 @@
     },
     "diffie-hellman": {
       "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+      "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
       "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
       "dev": true,
       "requires": {
@@ -4514,7 +4529,7 @@
     },
     "events": {
       "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/events/-/events-3.0.0.tgz",
       "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
       "dev": true
     },
@@ -5551,7 +5566,7 @@
     },
     "get-stream": {
       "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
       "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
       "dev": true,
       "requires": {
@@ -8420,7 +8435,7 @@
     },
     "safe-regex": {
       "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+      "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
       "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
       "requires": {
         "ret": "~0.1.10"
@@ -8748,7 +8763,7 @@
     },
     "sha.js": {
       "version": "2.4.11",
-      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+      "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
       "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
       "dev": true,
       "requires": {
@@ -9062,7 +9077,7 @@
     },
     "stream-browserify": {
       "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
       "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
       "dev": true,
       "requires": {
@@ -9162,7 +9177,7 @@
     },
     "strip-eof": {
       "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
       "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
       "dev": true
     },
@@ -9408,7 +9423,7 @@
     },
     "tty-browserify": {
       "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+      "resolved": "http://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
       "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
       "dev": true
     },
@@ -9462,7 +9477,7 @@
     },
     "underscore": {
       "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
+      "resolved": "http://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",
       "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg=="
     },
     "unicode-canonical-property-names-ecmascript": {

+ 1 - 0
package.json

@@ -30,6 +30,7 @@
     "@nextcloud/dialogs": "^0.1.1",
     "@nextcloud/event-bus": "^0.2.1",
     "@nextcloud/initial-state": "^0.2.0",
+    "@nextcloud/l10n": "^0.2.1",
     "@nextcloud/logger": "^0.1.0",
     "@nextcloud/paths": "^0.2.0",
     "@nextcloud/router": "^0.1.0",

Some files were not shown because too many files changed in this diff