Move updatenotifications to webpack with .vue file
Signed-off-by: Joas Schilling <coding@schilljs.com>
This commit is contained in:
parent
b12b391d7c
commit
308c7db333
|
@ -32,6 +32,8 @@
|
|||
!/apps/testing
|
||||
!/apps/admin_audit
|
||||
!/apps/updatenotification
|
||||
/apps/updatenotification/js/merged.js
|
||||
/apps/updatenotification/node_modules
|
||||
!/apps/theming
|
||||
!/apps/twofactor_backupcodes
|
||||
!/apps/workflowengine
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
app_name=notifications
|
||||
|
||||
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)
|
|
@ -7,12 +7,12 @@
|
|||
* later. See the COPYING file.
|
||||
*/
|
||||
|
||||
(function(OC, OCA, Vue, $) {
|
||||
/* global $, define */
|
||||
|
||||
define(function (require) {
|
||||
"use strict";
|
||||
|
||||
OCA.UpdateNotification = OCA.UpdateNotification || {};
|
||||
|
||||
OCA.UpdateNotification.App = {
|
||||
return {
|
||||
|
||||
|
||||
/** @type {number|null} */
|
||||
|
@ -26,7 +26,8 @@
|
|||
*/
|
||||
initialise: function() {
|
||||
var data = JSON.parse($('#updatenotification').attr('data-json'));
|
||||
this.vm = new Vue(OCA.UpdateNotification.Components.Root);
|
||||
var Vue = require('vue');
|
||||
this.vm = new Vue(require('./components/root.vue'));
|
||||
|
||||
this.vm.newVersionString = data.newVersionString;
|
||||
this.vm.lastCheckedDate = data.lastChecked;
|
||||
|
@ -41,8 +42,4 @@
|
|||
this.vm.isDefaultUpdateServerURL = data.isDefaultUpdateServerURL;
|
||||
}
|
||||
};
|
||||
})(OC, OCA, Vue, $);
|
||||
|
||||
$(document).ready(function () {
|
||||
OCA.UpdateNotification.App.initialise();
|
||||
});
|
|
@ -0,0 +1,188 @@
|
|||
<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="l_open_updater">
|
||||
<a v-if="downloadLink" :href="downloadLink" class="button" :class="{ hidden: !updaterEnabled }">{{l_download_now}}</a>
|
||||
</template>
|
||||
<template v-else-if="!isUpdateChecked">{{l_check_in_progress}}</template>
|
||||
<template v-else>
|
||||
{{l_up_to_date}}
|
||||
<span class="icon-info svg" :title="lastCheckedOnString"></span>
|
||||
</template>
|
||||
|
||||
<template v-if="!isDefaultUpdateServerURL">
|
||||
<br />
|
||||
<em>{{l_non_default_updater}} <code>{{updateServerURL}}</code></em>
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="release-channel">{{l_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>{{l_update_channel_newer}}</em><br />
|
||||
<em>{{l_update_channel_delay}}</em>
|
||||
</p>
|
||||
|
||||
<p id="oca_updatenotification_groups">
|
||||
{{l_notify_groups}}
|
||||
<input name="oca_updatenotification_groups_list" type="hidden" id="oca_updatenotification_groups_list" @change="saveNotifyGroups" :value="notifyGroups" style="width: 400px"><br />
|
||||
<em v-if="currentChannel === 'daily' || currentChannel === 'git'">{{l_only_app_updates}}</em>
|
||||
<em v-if="currentChannel === 'daily'">{{l_update_channel_daily}}</em>
|
||||
<em v-if="currentChannel === 'git'">{{l_update_channel_git}}</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: '',
|
||||
isDefaultUpdateServerURL: true
|
||||
};
|
||||
},
|
||||
|
||||
_$el: null,
|
||||
_$releaseChannel: null,
|
||||
_$notifyGroups: null,
|
||||
|
||||
computed: {
|
||||
l_check_in_progress: function() {
|
||||
return t('updatenotification', 'The update check is not yet finished. Please refresh the page.');
|
||||
},
|
||||
l_download_now: function() {
|
||||
return t('updatenotification', 'Download now');
|
||||
},
|
||||
l_non_default_updater: function() {
|
||||
return t('updatenotification', 'A non-default update server is in use to be checked for updates:');
|
||||
},
|
||||
l_notify_groups: function() {
|
||||
return t('updatenotification', 'Notify members of the following groups about available updates:');
|
||||
},
|
||||
l_only_app_updates: function() {
|
||||
return t('updatenotification', 'Only notification for app updates are available.');
|
||||
},
|
||||
l_open_updater: function() {
|
||||
return t('updatenotification', 'Open updater');
|
||||
},
|
||||
l_up_to_date: function() {
|
||||
return t('updatenotification', 'Your version is up to date.');
|
||||
},
|
||||
l_update_channel: function() {
|
||||
return t('updatenotification', 'Update channel:');
|
||||
},
|
||||
l_update_channel_daily: function() {
|
||||
return t('updatenotification', 'The selected update channel makes dedicated notifications for the server obsolete.');
|
||||
},
|
||||
l_update_channel_git: function() {
|
||||
return t('updatenotification', 'The selected update channel does not support updates of the server.');
|
||||
},
|
||||
l_update_channel_newer: function() {
|
||||
return t('updatenotification', 'You can always update to a newer version / experimental channel. But you can never downgrade to a more stable channel.');
|
||||
},
|
||||
l_update_channel_delay: function() {
|
||||
return 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.');
|
||||
},
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
saveNotifyGroups: function(e) {
|
||||
var groups = e.val || [];
|
||||
groups = JSON.stringify(groups);
|
||||
OCP.AppConfig.setValue('updatenotification', 'notify_groups', groups);
|
||||
}
|
||||
},
|
||||
|
||||
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));
|
||||
},
|
||||
|
||||
updated: function () {
|
||||
OC.Settings.setupGroupsSelect(this._$notifyGroups);
|
||||
this._$el.find('.icon-info').tooltip({placement: 'right'});
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
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': 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
|
||||
})
|
||||
]);
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
/**
|
||||
* @copyright (c) 2018 Joas Schilling <coding@schilljs.com>
|
||||
* @copyright (c) 2016 ownCloud Inc
|
||||
*
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*/
|
||||
|
||||
(function(OC, OCA, OCP, t, $) {
|
||||
"use strict";
|
||||
|
||||
OCA.UpdateNotification = OCA.UpdateNotification || {};
|
||||
OCA.UpdateNotification.Components = OCA.UpdateNotification.Components || {};
|
||||
|
||||
OCA.UpdateNotification.Components.Root = {
|
||||
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:') +
|
||||
' <input name="oca_updatenotification_groups_list" type="hidden" id="oca_updatenotification_groups_list" v-model="notifyGroups" @change="saveNotifyGroups" :value="notifyGroups" style="width: 400px"><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>',
|
||||
|
||||
el: '#updatenotification',
|
||||
data: {
|
||||
newVersionString: '',
|
||||
lastCheckedDate: '',
|
||||
isUpdateChecked: false,
|
||||
updaterEnabled: true,
|
||||
downloadLink: '',
|
||||
isNewVersionAvailable: false,
|
||||
updateServerURL: '',
|
||||
currentChannel: '',
|
||||
channels: [],
|
||||
notifyGroups: '',
|
||||
isDefaultUpdateServerURL: true
|
||||
},
|
||||
|
||||
_$el: null,
|
||||
_$releaseChannel: null,
|
||||
_$notifyGroups: null,
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
saveNotifyGroups: function(e) {
|
||||
var groups = e.val || [];
|
||||
groups = JSON.stringify(groups);
|
||||
OCP.AppConfig.setValue('updatenotification', 'notify_groups', groups);
|
||||
}
|
||||
},
|
||||
|
||||
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));
|
||||
},
|
||||
|
||||
updated: function () {
|
||||
OC.Settings.setupGroupsSelect(this._$notifyGroups);
|
||||
this._$el.find('.icon-info').tooltip({placement: 'right'});
|
||||
}
|
||||
};
|
||||
})(OC, OCA, OCP, t, $);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -8,15 +8,7 @@ declare(strict_types=1);
|
|||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later. See the COPYING file.
|
||||
*/
|
||||
if (\OC::$server->getConfig()->getSystemValue('debug', false)) {
|
||||
script('updatenotification', 'vue');
|
||||
} else {
|
||||
script('updatenotification', 'vue.min');
|
||||
}
|
||||
script('updatenotification', [
|
||||
'components/root',
|
||||
'admin',
|
||||
]);
|
||||
script('updatenotification', 'merged');
|
||||
style('updatenotification', 'admin');
|
||||
/** @var array $_ */
|
||||
?>
|
||||
|
|
Loading…
Reference in New Issue