Merge pull request #7933 from nextcloud/update-notification-vuejs

Migrate Update notifications to Vue.js
This commit is contained in:
Joas Schilling 2018-02-26 18:55:08 +01:00 committed by GitHub
commit abb0a08ed5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 6692 additions and 193 deletions

3
.gitignore vendored
View file

@ -32,6 +32,9 @@
!/apps/testing
!/apps/admin_audit
!/apps/updatenotification
/apps/updatenotification/js/merged.js
/apps/updatenotification/js/merged.js.map
/apps/updatenotification/node_modules
!/apps/theming
!/apps/twofactor_backupcodes
!/apps/workflowengine

View file

@ -0,0 +1,47 @@
app_name=updatenotification
project_dir=$(CURDIR)/../$(app_name)
build_dir=$(CURDIR)/build
source_dir=$(build_dir)/$(app_name)
sign_dir=$(build_dir)/sign
all: package
dev-setup: clean npm-update build-js
npm-update:
rm -rf node_modules
npm update
build-js:
npm run dev
build-js-production:
npm run build
clean:
rm -rf $(build_dir)
package: clean build-js-production
mkdir -p $(source_dir)
rsync -a \
--exclude=/build \
--exclude=/docs \
--exclude=/js-src \
--exclude=/l10n/.tx \
--exclude=/tests \
--exclude=/.git \
--exclude=/.github \
--exclude=/CONTRIBUTING.md \
--exclude=/issue_template.md \
--exclude=/README.md \
--exclude=/.gitignore \
--exclude=/.scrutinizer.yml \
--exclude=/.travis.yml \
--exclude=/.drone.yml \
--exclude=/node_modules \
--exclude=/npm-debug.log \
--exclude=/package.json \
--exclude=/package-lock.json \
--exclude=/Makefile \
$(project_dir)/ $(source_dir)

View file

@ -1,3 +1,4 @@
#updatenotification p,
#oca_updatenotification_section p {
margin: 25px 0;
}

View file

@ -0,0 +1,53 @@
/**
* @copyright (c) 2018 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
/* global $, define */
define(function (require) {
"use strict";
return {
/** @type {Vue|null} */
vm: null,
/**
* Initialise the app
*/
initialise: function() {
var data = JSON.parse($('#updatenotification').attr('data-json'));
var Vue = require('vue');
var vSelect = require('vue-select');
Vue.component('v-select', vSelect.VueSelect);
Vue.mixin({
methods: {
t: function(app, text, vars, count, options) {
return OC.L10N.translate(app, text, vars, count, options);
},
n: function(app, textSingular, textPlural, count, vars, options) {
return OC.L10N.translatePlural(app, textSingular, textPlural, count, vars, options);
}
}
});
this.vm = new Vue(require('./components/root.vue'));
this.vm.newVersionString = data.newVersionString;
this.vm.lastCheckedDate = data.lastChecked;
this.vm.isUpdateChecked = data.isUpdateChecked;
this.vm.updaterEnabled = data.updaterEnabled;
this.vm.downloadLink = data.downloadLink;
this.vm.isNewVersionAvailable = data.isNewVersionAvailable;
this.vm.updateServerURL = data.updateServerURL;
this.vm.currentChannel = data.currentChannel;
this.vm.channels = data.channels;
this.vm.notifyGroups = data.notifyGroups;
this.vm.isDefaultUpdateServerURL = data.isDefaultUpdateServerURL;
}
};
});

View file

@ -0,0 +1,180 @@
<template>
<div id="updatenotification" class="followupsection">
<p>
<template v-if="isNewVersionAvailable">
<strong>{{newVersionAvailableString}}</strong>
<input v-if="updaterEnabled" type="button" @click="clickUpdaterButton" id="oca_updatenotification_button" :value="t('updatenotification', 'Open updater')">
<a v-if="downloadLink" :href="downloadLink" class="button" :class="{ hidden: !updaterEnabled }">{{ t('updatenotification', 'Download now') }}</a>
</template>
<template v-else-if="!isUpdateChecked">{{ t('updatenotification', 'The update check is not yet finished. Please refresh the page.') }}</template>
<template v-else>
{{ t('updatenotification', 'Your version is up to date.') }}
<span class="icon-info svg" :title="lastCheckedOnString"></span>
</template>
<template v-if="!isDefaultUpdateServerURL">
<br />
<em>{{ t('updatenotification', 'A non-default update server is in use to be checked for updates:') }} <code>{{updateServerURL}}</code></em>
</template>
</p>
<p>
<label for="release-channel">{{ t('updatenotification', 'Update channel:') }}</label>
<select id="release-channel" v-model="currentChannel" @change="changeReleaseChannel">
<option v-for="channel in channels" :value="channel">{{channel}}</option>
</select>
<span id="channel_save_msg" class="msg"></span><br />
<em>{{ t('updatenotification', 'You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel.') }}</em><br />
<em>{{ t('updatenotification', 'Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found.') }}</em>
</p>
<p id="oca_updatenotification_groups">
{{ t('updatenotification', 'Notify members of the following groups about available updates:') }}
<v-select multiple :value="notifyGroups" :options="availableGroups"></v-select><br />
<em v-if="currentChannel === 'daily' || currentChannel === 'git'">{{ t('updatenotification', 'Only notification for app updates are available.') }}</em>
<em v-if="currentChannel === 'daily'">{{ t('updatenotification', 'The selected update channel makes dedicated notifications for the server obsolete.') }}</em>
<em v-if="currentChannel === 'git'">{{ t('updatenotification', 'The selected update channel does not support updates of the server.') }}</em>
</p>
</div>
</template>
<script>
export default {
name: "root",
el: '#updatenotification',
data: function () {
return {
newVersionString: '',
lastCheckedDate: '',
isUpdateChecked: false,
updaterEnabled: true,
downloadLink: '',
isNewVersionAvailable: false,
updateServerURL: '',
currentChannel: '',
channels: [],
notifyGroups: '',
availableGroups: [],
isDefaultUpdateServerURL: true,
enableChangeWatcher: false
};
},
_$el: null,
_$releaseChannel: null,
_$notifyGroups: null,
watch: {
notifyGroups: function(selectedOptions) {
if (!this.enableChangeWatcher) {
return;
}
var selectedGroups = [];
_.each(selectedOptions, function(group) {
selectedGroups.push(group.value);
});
OCP.AppConfig.setValue('updatenotification', 'notify_groups', JSON.stringify(selectedGroups));
}
},
computed: {
newVersionAvailableString: function() {
return t('updatenotification', 'A new version is available: {newVersionString}', {
newVersionString: this.newVersionString
});
},
lastCheckedOnString: function() {
return t('updatenotification', 'Checked on {lastCheckedDate}', {
lastCheckedDate: this.lastCheckedDate
});
}
},
methods: {
/**
* Creates a new authentication token and loads the updater URL
*/
clickUpdaterButton: function() {
$.ajax({
url: OC.generateUrl('/apps/updatenotification/credentials')
}).success(function(data) {
$.ajax({
url: OC.getRootPath()+'/updater/',
headers: {
'X-Updater-Auth': data
},
method: 'POST',
success: function(data){
if(data !== 'false') {
var body = $('body');
$('head').remove();
body.html(data);
// Eval the script elements in the response
var dom = $(data);
dom.filter('script').each(function() {
eval(this.text || this.textContent || this.innerHTML || '');
});
body.removeAttr('id');
body.attr('id', 'body-settings');
}
},
error: function() {
OC.Notification.showTemporary(t('updatenotification', 'Could not start updater, please try the manual update'));
this.updaterEnabled = false;
}.bind(this)
});
}.bind(this));
},
changeReleaseChannel: function() {
this.currentChannel = this._$releaseChannel.val();
$.ajax({
url: OC.generateUrl('/apps/updatenotification/channel'),
type: 'POST',
data: {
'channel': this.currentChannel
},
success: function (data) {
OC.msg.finishedAction('#channel_save_msg', data);
}
});
}
},
mounted: function () {
this._$el = $(this.$el);
this._$releaseChannel = this._$el.find('#release-channel');
this._$notifyGroups = this._$el.find('#oca_updatenotification_groups_list');
this._$notifyGroups.on('change', function () {
this.$emit('input');
}.bind(this));
$.ajax({
url: OC.generateUrl('/settings/users/groups'),
dataType: 'json',
success: function(data) {
var results = [];
$.each(data.data.adminGroups, function(i, group) {
results.push({value: group.id, label: group.name});
});
$.each(data.data.groups, function(i, group) {
results.push({value: group.id, label: group.name});
});
this.availableGroups = results;
this.enableChangeWatcher = true;
}.bind(this)
});
},
updated: function () {
this._$el.find('.icon-info').tooltip({placement: 'right'});
}
}
</script>

View file

@ -0,0 +1,31 @@
/**
* @copyright Copyright (c) 2018 Joas Schilling <coding@schilljs.com>
*
* @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/>.
*
*/
/* global define, $ */
define(function(require) {
'use strict';
var App = require('./app');
$(function() {
App.initialise();
});
});

View file

@ -0,0 +1,56 @@
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './js-src/init.js',
output: {
path: path.resolve(__dirname, '../js'),
publicPath: '/',
filename: 'merged.js'
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
},
esModule: false
// other vue-loader options go here
}
}
]
},
resolve: {
alias: {
'vue-select': 'vue-select/dist/vue-select.js',
'vue': process.env.NODE_ENV === 'production' ? 'vue/dist/vue.min.js' : 'vue/dist/vue.js'
}
},
performance: {
hints: false
},
devtool: '#eval-source-map'
};
if (process.env.NODE_ENV === 'production') {
module.exports.devtool = '#source-map';
// http://vue-loader.vuejs.org/en/workflow/production.html
module.exports.plugins = (module.exports.plugins || []).concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
compress: {
warnings: false
}
}),
new webpack.LoaderOptionsPlugin({
minimize: true
})
]);
}

View file

@ -1,84 +0,0 @@
/**
* Copyright (c) 2016 ownCloud Inc
*
* @author Lukas Reschke <lukas@owncloud.com>
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
/**
* Creates a new authentication token and loads the updater URL
*/
var loginToken = '';
$(document).ready(function(){
$('#oca_updatenotification_button').click(function() {
// Load the new token
$.ajax({
url: OC.generateUrl('/apps/updatenotification/credentials')
}).success(function(data) {
loginToken = data;
$.ajax({
url: OC.webroot+'/updater/',
headers: {
'X-Updater-Auth': loginToken
},
method: 'POST',
success: function(data){
if(data !== 'false') {
var body = $('body');
$('head').remove();
body.html(data);
// Eval the script elements in the response
var dom = $(data);
dom.filter('script').each(function() {
eval(this.text || this.textContent || this.innerHTML || '');
});
body.removeAttr('id');
body.attr('id', 'body-settings');
}
},
error: function(){
OC.Notification.showTemporary(t('updatenotification', 'Could not start updater, please try the manual update'));
$('#oca_updatenotification_button').addClass('hidden');
$('#oca_updatenotification_section .button').removeClass('hidden');
}
});
});
});
$('#release-channel').change(function() {
var newChannel = $('#release-channel').find(":selected").val();
if (newChannel === 'git' || newChannel === 'daily') {
$('#oca_updatenotification_groups em').removeClass('hidden');
} else {
$('#oca_updatenotification_groups em').addClass('hidden');
}
$.post(
OC.generateUrl('/apps/updatenotification/channel'),
{
'channel': newChannel
},
function(data){
OC.msg.finishedAction('#channel_save_msg', data);
}
);
});
var $notificationTargetGroups = $('#oca_updatenotification_groups_list');
OC.Settings.setupGroupsSelect($notificationTargetGroups);
$notificationTargetGroups.change(function(ev) {
var groups = ev.val || [];
groups = JSON.stringify(groups);
OCP.AppConfig.setValue('updatenotification', 'notify_groups', groups);
});
$('#oca_updatenotification_section .icon-info').tooltip({placement: 'right'});
});

View file

@ -11,7 +11,7 @@
*/
/**
* this gets only loaded if an update is available and then shows a temporary notification
* This only gets loaded if an update is available and the notifications app is not enabled for the user.
*/
$(document).ready(function(){
var text = t('core', '{version} is available. Get more information on how to update.', {version: oc_updateState.updateVersion}),

View file

@ -63,7 +63,7 @@ class Application extends App {
}
if ($updateChecker->getUpdateState() !== []) {
Util::addScript('updatenotification', 'notification');
Util::addScript('updatenotification', 'legacy-notification');
\OC_Hook::connect('\OCP\Config', 'js', $updateChecker, 'populateJavaScriptVariables');
}
}

View file

@ -30,6 +30,7 @@ use OCA\UpdateNotification\UpdateChecker;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
use OCP\IGroupManager;
use OCP\Settings\ISettings;
use OCP\Util;
@ -38,19 +39,24 @@ class Admin implements ISettings {
private $config;
/** @var UpdateChecker */
private $updateChecker;
/** @var IGroupManager */
private $groupManager;
/** @var IDateTimeFormatter */
private $dateTimeFormatter;
/**
* @param IConfig $config
* @param UpdateChecker $updateChecker
* @param IGroupManager $groupManager
* @param IDateTimeFormatter $dateTimeFormatter
*/
public function __construct(IConfig $config,
UpdateChecker $updateChecker,
IGroupManager $groupManager,
IDateTimeFormatter $dateTimeFormatter) {
$this->config = $config;
$this->updateChecker = $updateChecker;
$this->groupManager = $groupManager;
$this->dateTimeFormatter = $dateTimeFormatter;
}
@ -68,11 +74,10 @@ class Admin implements ISettings {
'production',
];
$currentChannel = Util::getChannel();
// Remove the currently used channel from the channels list
if(($key = array_search($currentChannel, $channels, true)) !== false) {
unset($channels[$key]);
if ($currentChannel === 'git') {
$channels[] = 'git';
}
$updateState = $this->updateChecker->getUpdateState();
$notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]'), true);
@ -91,12 +96,35 @@ class Admin implements ISettings {
'updaterEnabled' => empty($updateState['updaterEnabled']) ? false : $updateState['updaterEnabled'],
'isDefaultUpdateServerURL' => $updateServerURL === $defaultUpdateServerURL,
'updateServerURL' => $updateServerURL,
'notify_groups' => implode('|', $notifyGroups),
'notifyGroups' => $this->getSelectedGroups($notifyGroups),
];
$params = [
'json' => json_encode($params),
];
return new TemplateResponse('updatenotification', 'admin', $params, '');
}
/**
* @param array $groupIds
* @return array
*/
protected function getSelectedGroups(array $groupIds): array {
$result = [];
foreach ($groupIds as $groupId) {
$group = $this->groupManager->get($groupId);
if ($group === null) {
continue;
}
$result[] = ['value' => $group->getGID(), 'label' => $group->getDisplayName()];
}
return $result;
}
/**
* @return string the section ID, e.g. 'sharing'
*/

6127
apps/updatenotification/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
{
"name": "notifications",
"version": "2.2.0",
"description": "This app provides a backend and frontend for the notification API available in Nextcloud.",
"main": "init.js",
"directories": {
"lib": "lib",
"test": "tests"
},
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --progress --hot --config js-src/webpack.config.js --watch",
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules --config js-src/webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nextcloud/notifications.git"
},
"author": "Joas Schilling",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/nextcloud/notifications/issues"
},
"homepage": "https://github.com/nextcloud/notifications#readme",
"dependencies": {
"vue": "^2.5.13",
"vue-select": "^2.4.0"
},
"devDependencies": {
"cross-env": "^5.1.3",
"css-loader": "^0.28.8",
"file-loader": "^1.1.6",
"vue-loader": "^13.7.0",
"vue-template-compiler": "^2.5.13",
"webpack": "^3.6.0"
}
}

View file

@ -1,75 +1,15 @@
<?php
declare(strict_types=1);
script('updatenotification', 'admin');
style('updatenotification', 'admin');
/** @var array $_ */
/** @var bool $isNewVersionAvailable */
$isNewVersionAvailable = $_['isNewVersionAvailable'];
/** @var string $newVersionString */
$newVersionString = $_['newVersionString'];
/** @var bool $isUpdateChecked */
$isUpdateChecked = $_['isUpdateChecked'];
/** @var string $lastCheckedDate */
$lastCheckedDate = $_['lastChecked'];
/** @var array $channels */
$channels = $_['channels'];
/** @var string $currentChannel */
$currentChannel = $_['currentChannel'];
/** @var string $updateServerURL */
$updateServerURL = $_['updateServerURL'];
/** @var bool $isDefaultUpdateServerURL */
$isDefaultUpdateServerURL = $_['isDefaultUpdateServerURL'];
/**
* @copyright (c) 2018 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
script('updatenotification', 'merged');
style('updatenotification', 'admin');
/** @var array $_ */
?>
<form id="oca_updatenotification_section" class="followupsection">
<p>
<?php if ($isNewVersionAvailable === true) { ?>
<strong><?php p($l->t('A new version is available: %s', [$newVersionString])); ?></strong>
<?php if ($_['updaterEnabled']) { ?>
<input type="button" id="oca_updatenotification_button" value="<?php p($l->t('Open updater')) ?>">
<?php } ?>
<?php if (!empty($_['downloadLink'])) { ?>
<a href="<?php p($_['downloadLink']); ?>" class="button<?php if ($_['updaterEnabled']) { p(' hidden'); } ?>"><?php p($l->t('Download now')) ?></a>
<?php } ?>
<?php } elseif (!$isUpdateChecked) { ?>
<?php p($l->t('The update check is not yet finished. Please refresh the page.')); ?>
<?php } else { ?>
<?php p($l->t('Your version is up to date.')); ?>
<span class="icon-info svg" title="<?php p($l->t('Checked on %s', [$lastCheckedDate])) ?>"></span>
<?php } ?>
<?php if (!$isDefaultUpdateServerURL) { ?>
<br />
<em>
<?php p($l->t('A non-default update server is in use to be checked for updates:')); ?>
<code><?php p($updateServerURL); ?></code>
</em>
<?php } ?>
</p>
<p>
<label for="release-channel"><?php p($l->t('Update channel:')) ?></label>
<select id="release-channel">
<option value="<?php p($currentChannel); ?>"><?php p($currentChannel); ?></option>
<?php foreach ($channels as $channel => $channelTitle){ ?>
<option value="<?php p($channelTitle) ?>">
<?php p($channelTitle) ?>
</option>
<?php } ?>
</select>
<span id="channel_save_msg" class="msg"></span><br />
<em><?php p($l->t('You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel.')); ?></em><br />
<em><?php p($l->t('Note that after a new release it can take some time before it shows up here. We roll out new versions spread out over time to our users and sometimes skip a version when issues are found.')); ?></em>
</p>
<p id="oca_updatenotification_groups">
<?php p($l->t('Notify members of the following groups about available updates:')); ?>
<input name="oca_updatenotification_groups_list" type="hidden" id="oca_updatenotification_groups_list" value="<?php p($_['notify_groups']) ?>" style="width: 400px"><br />
<em class="<?php if (!\in_array($currentChannel, ['daily', 'git'], true)) { p('hidden'); } ?>">
<?php p($l->t('Only notification for app updates are available.')); ?>
<?php if ($currentChannel === 'daily') { p($l->t('The selected update channel makes dedicated notifications for the server obsolete.')); } ?>
<?php if ($currentChannel === 'git') { p($l->t('The selected update channel does not support updates of the server.')); } ?>
</em>
</p>
</form>
<div id="updatenotification" data-json="<?php p($_['json']); ?>"></div>

View file

@ -30,6 +30,8 @@ use OCA\UpdateNotification\UpdateChecker;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\Util;
use Test\TestCase;
@ -40,6 +42,8 @@ class AdminTest extends TestCase {
private $config;
/** @var UpdateChecker|\PHPUnit_Framework_MockObject_MockObject */
private $updateChecker;
/** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
private $groupManager;
/** @var IDateTimeFormatter|\PHPUnit_Framework_MockObject_MockObject */
private $dateTimeFormatter;
@ -48,11 +52,13 @@ class AdminTest extends TestCase {
$this->config = $this->createMock(IConfig::class);
$this->updateChecker = $this->createMock(UpdateChecker::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->dateTimeFormatter = $this->createMock(IDateTimeFormatter::class);
$this->admin = new Admin(
$this->config,
$this->updateChecker,
$this->groupManager,
$this->dateTimeFormatter
);
}
@ -65,10 +71,8 @@ class AdminTest extends TestCase {
'production',
];
$currentChannel = Util::getChannel();
// Remove the currently used channel from the channels list
if(($key = array_search($currentChannel, $channels, true)) !== false) {
unset($channels[$key]);
if ($currentChannel === 'git') {
$channels[] = 'git';
}
$this->config
@ -98,18 +102,34 @@ class AdminTest extends TestCase {
'updaterEnabled' => true,
]);
$group = $this->createMock(IGroup::class);
$group->expects($this->any())
->method('getDisplayName')
->willReturn('Administrators');
$group->expects($this->any())
->method('getGID')
->willReturn('admin');
$this->groupManager->expects($this->once())
->method('get')
->with('admin')
->willReturn($group);
$params = [
'isNewVersionAvailable' => true,
'isUpdateChecked' => true,
'lastChecked' => 'LastCheckedReturnValue',
'currentChannel' => Util::getChannel(),
'channels' => $channels,
'newVersionString' => '8.1.2',
'downloadLink' => 'https://downloads.nextcloud.org/server',
'updaterEnabled' => true,
'isDefaultUpdateServerURL' => true,
'updateServerURL' => 'https://updates.nextcloud.com/updater_server/',
'notify_groups' => 'admin',
'json' => json_encode([
'isNewVersionAvailable' => true,
'isUpdateChecked' => true,
'lastChecked' => 'LastCheckedReturnValue',
'currentChannel' => Util::getChannel(),
'channels' => $channels,
'newVersionString' => '8.1.2',
'downloadLink' => 'https://downloads.nextcloud.org/server',
'updaterEnabled' => true,
'isDefaultUpdateServerURL' => true,
'updateServerURL' => 'https://updates.nextcloud.com/updater_server/',
'notifyGroups' => [
['value' => 'admin', 'label' => 'Administrators'],
],
]),
];
$expected = new TemplateResponse('updatenotification', 'admin', $params, '');

View file

@ -681,21 +681,6 @@ kbd {
}
}
/* DROPDOWN ----------------------------------------------------------------- */
.dropdown {
background: nc-darken($color-main-background, 8%);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
box-shadow: 0 1px 1px $color-box-shadow;
display: block;
margin-right: 0;
position: absolute;
right: 0;
width: 420px;
z-index: 500;
padding: 16px;
}
/* TABS --------------------------------------------------------------------- */
.tabHeaders {
display: inline-block;

View file

@ -483,6 +483,81 @@ input {
}
}
/* Vue v-select */
.v-select {
margin: 3px 3px 3px 0;
display: inline-block;
.dropdown-toggle {
display: flex !important;
flex-wrap: wrap;
.selected-tag {
line-height: 20px;
padding-left: 5px;
background-image: none;
background-color: $color-main-background;
color: nc-lighten($color-main-text, 33%);
border: 1px solid nc-darken($color-main-background, 14%);
display: inline-flex;
align-items: center;
.close {
margin-left: 3px;
}
}
}
.dropdown-menu {
padding: 0;
li {
padding: 5px;
position: relative;
display: list-item;
background-color: transparent;
cursor: pointer;
color: nc-lighten($color-main-text, 33%);
a {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 25px;
padding: 3px 7px 4px 2px;
margin: 0;
cursor: pointer;
min-height: 1em;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
display: inline-flex;
align-items: center;
background-color: transparent !important;
color: inherit !important;
&::before {
content: ' ';
background-image: url('../img/actions/checkmark.svg?v=1');
background-repeat: no-repeat;
background-position: center;
min-width: 16px;
min-height: 16px;
display: block;
opacity: 0.5;
margin-right: 5px;
visibility: hidden;
}
}
&.highlight {
color: $color-main-text;
}
&.active > a {
background-color: nc-darken($color-main-background, 3%);
color: $color-main-text;
&::before {
visibility: visible;
}
}
}
}
}
/* Progressbar */
progress {
display: block;