From fee8a8816952336e659f65e0a52c838f953b67e5 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 9 Mar 2021 18:27:12 +0100 Subject: [PATCH 001/172] Instance notification prototype --- customize.dist/src/less2/include/forms.less | 2 +- lib/commands/channel.js | 5 + lib/hk-util.js | 4 + www/admin/app-admin.less | 27 +- www/admin/inner.js | 283 +++++++++++++++++++- www/common/common-util.js | 10 + www/common/notifications.js | 81 ++++++ www/common/outer/mailbox.js | 67 +++-- www/common/sframe-common-mailbox.js | 2 +- www/common/toolbar.js | 2 +- www/lib/datepicker/flatpickr.js | 2 + www/lib/datepicker/flatpickr.min.css | 13 + www/support/ui.js | 3 + 13 files changed, 479 insertions(+), 22 deletions(-) create mode 100644 www/lib/datepicker/flatpickr.js create mode 100644 www/lib/datepicker/flatpickr.min.css diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index ae1096946..7a2b90e42 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -8,7 +8,7 @@ & { @alertify_padding-base: @variables_padding; - input:not(.form-control):not([type="checkbox"]), textarea, div.cp-textarea { + input:not(.numInput):not(.form-control):not([type="checkbox"]), textarea, div.cp-textarea { // background-color: @alertify-input-fg; color: @cp_forms-fg; background-color: @cp_forms-bg; diff --git a/lib/commands/channel.js b/lib/commands/channel.js index 86ab7bc8c..43929a838 100644 --- a/lib/commands/channel.js +++ b/lib/commands/channel.js @@ -254,6 +254,11 @@ Channel.writePrivateMessage = function (Env, args, _cb, Server, netfluxId) { var session = HK.getNetfluxSession(Env, netfluxId); var allowed = HK.listAllowedUsers(metadata); + // Special broadcast channel + if (channelId === '00000000000000000000000000000000') { + allowed = Env.admins; + } + if (HK.isUserSessionAllowed(allowed, session)) { return; } w.abort(); diff --git a/lib/hk-util.js b/lib/hk-util.js index b66e7e0b9..f7aee6af8 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -883,6 +883,10 @@ HK.onChannelMessage = function (Env, Server, channel, msgStruct, cb) { // don't store messages if the channel id indicates that it's an ephemeral message if (!channel.id || channel.id.length === EPHEMERAL_CHANNEL_LENGTH) { return void cb(); } + if (channel.id === '00000000000000000000000000000000' && msgStruct[1] !== null) { + return void cb('ERESTRICTED_ADMIN'); + } + const isCp = /^cp\|/.test(msgStruct[4]); let id; if (isCp) { diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index c9b8bdc02..64f977eca 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -15,7 +15,7 @@ display: flex; flex-flow: column; - .cp-admin-setlimit-form { + .cp-admin-setlimit-form, .cp-admin-broadcast-form { label { font-weight: normal !important; } @@ -199,5 +199,30 @@ } } + .cp-admin-broadcast-form { + margin-top: 30px; + & > button:last-child { + margin-top: 30px !important; + } + .cp-broadcast-container { + display: flex; + flex-flow: column; + } + .cp-broadcast-lang { + margin: 30px; + margin-bottom: 0; + display: flex; + flex-flow: column; + align-items: baseline; + .cp-checkmark { + margin: 5px 0; + } + } + div.cp-broadcast-languages { + & > label.cp-checkmark:not(:last-child) { + margin-right: 20px; + } + } + } } diff --git a/www/admin/inner.js b/www/admin/inner.js index 6f0b9d2df..7d260e681 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -14,6 +14,9 @@ define([ '/common/common-signing-keys.js', '/support/ui.js', + '/lib/datepicker/flatpickr.js', + + 'css!/lib/datepicker/flatpickr.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/admin/app-admin.less', @@ -31,7 +34,8 @@ define([ Util, Hash, Keys, - Support + Support, + Flatpickr ) { var APP = { @@ -67,6 +71,9 @@ define([ 'cp-admin-support-list', 'cp-admin-support-init' ], + 'broadcast': [ // Msg.admin_cat_support + 'cp-admin-broadcast' + ], 'performance': [ // Msg.admin_cat_performance 'cp-admin-refresh-performance', 'cp-admin-performance-profiling', @@ -930,6 +937,279 @@ define([ return; }; + // Messages.admin_cat_broadcast // XXX + // Messages.admin_broadcastHint // XXX + // Messages.admin_broadcastTitle // XXX + Messages.broadcast_maintenance = 'maintenance';// XXX + Messages.broadcast_survey = 'survey'; // XXX + Messages.broadcast_version = 'version'; // XXX + Messages.broadcast_custom = 'custom'; // XXX + Messages.broadcast_newVersionReload = 'Force a worker reload on all clients'; // XXX + Messages.broadcast_surveyURL = 'Survey URL'; + Messages.broadcast_translations = 'Translations'; + Messages.broadcast_defaultLanguage = 'Default language'; + Messages.broadcast_start = 'Start time'; + Messages.broadcast_end = 'End time'; + + var getBroadcastForm = function ($form, key) { + $form.empty(); + + var getData = function () { + return false; + }; + var reset = function () {}; + + var button = h('button.btn.btn-primary', Messages.support_formButton); + var $button = $(button); + + var send = function () { + var data = getData(); + if (data === false) { return void UI.warn(Messages.error); } + $button.prop('disabled', 'disabled'); + common.mailbox.sendTo('BROADCAST_'+key.toUpperCase(), data, {}, function (err) { + $button.prop('disabled', ''); + if (err) { return UI.warn(Messages.error); } + reset(); + UI.log(Messages.saved); + }); + }; + $button.click(function () { + send(); + }); + + if (key === 'custom') { + (function () { + var container = h('div.cp-broadcast-container'); + var $container = $(container); + var languages = Messages._languages; + var keys = Object.keys(languages).sort(); + + var onPreview = function (l) { +// XXX + }; + + var reorder = function () { + $container.find('.cp-broadcast-lang').each(function (i, el) { + var $el = $(el); + var l = $el.attr('data-lang'); + $el.css('order', keys.indexOf(l)); + }); + }; + var removeLang = function (l) { + $container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove(); + }; + var addLang = function (l) { + if ($container.find('.cp-broadcast-lang[data-lang="'+l+'"]').length) { return; } + var preview = h('button.btn.btn-secondary', Messages.share_linkOpen); + $(preview).click(function () { + onPreview(l); + }); + var bcastDefault = Messages.broadcast_defaultLanguage; + $container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [ + h('h4', languages[l]), + h('label', Messages.kanban_body), + h('textarea'), + UI.createRadio('broadcastDefault', null, bcastDefault, false, { + 'data-lang': l, + label: {class: 'noTitle'} + }), + preview + ])); + reorder(); + }; + + + var boxes = keys.map(function (l) { + var $cbox = $(UI.createCheckbox('cp-broadcast-custom-lang-'+l, + languages[l], false, { label: { class: 'noTitle' } })); + var $check = $cbox.find('input').on('change', function () { + var c = $check.is(':checked'); + if (c) { return void addLang(l); } + removeLang(l); + }); + if (l === 'en') { + setTimeout(function () { + $check.click(); + }); + } + return $cbox[0]; + }); + + getData = function () { + var map = {}; + var defaultLanguage; + var error = false; + $container.find('.cp-broadcast-lang').each(function (i, el) { + var $el = $(el); + var l = $el.attr('data-lang'); + if (!l) { error = true; return; } + var text = $el.find('textarea').val(); + if (!text.trim()) { error = true; return; } + if ($el.find('.cp-checkmark input').is(':checked')) { + defaultLanguage = l; + } + map[l] = text; + }); + if (!Object.keys(map).length) { + console.error('You must select at least one language'); + return false; + } + if (error) { + console.error('One of the selected languages has no data'); + return false; + } + return { + defaultLanguage: defaultLanguage, + content: map + }; + }; + reset = function () { + $container.find('.cp-broadcast-lang textarea').each(function (i, el) { + $(el).val(''); + }); + }; + $form.append([ + h('label', Messages.broadcast_translations), + h('div.cp-broadcast-languages', boxes), + container, + button + ]); + })(); + return; + } + if (key === 'maintenance') { + (function () { + var start = h('input'); + var end = h('input'); + var $start = $(start); + var $end = $(end); + + var endPickr = Flatpickr(end, { + enableTime: true, + minDate: new Date() + }); + Flatpickr(start, { + enableTime: true, + minDate: new Date(), + onChange: function () { + endPickr.set('minDate', new Date($start.val())); + } + }); + getData = function () { + var start = +new Date($start.val()); + var end = +new Date($start.val()); + if (isNaN(start) || isNaN(end)) { + console.error('Invalid dates'); + return false; + } + return { + start: start, + end: end + }; + }; + reset = function () { + $start.val(''); + $end.val(''); + }; + $form.append([ + h('label', Messages.broadcast_start), + start, + h('label', Messages.broadcast_end), + end, + h('br'), + button + ]); + })(); + return; + } + if (key === 'version') { + (function () { + var $cbox = $(UI.createCheckbox('cp-admin-version-reload', + Messages.broadcast_newVersionReload, + false, { label: { class: 'noTitle' } })); + var $checkbox = $cbox.find('input'); + getData = function () { + return { + reload: $checkbox.is(':checked') + }; + }; + reset = function () { + $checkbox[0].checked = false; + }; + $form.append([ + $cbox[0], + h('br'), + button + ]); + })(); + return; + } + if (key === 'survey') { + (function () { + var label = h('label', Messages.broadcast_surveyURL); + var input = h('input'); + var $input = $(input); + getData = function () { + var url = $input.val(); + if (!Util.isValidURL(url)) { + console.error('Invalid URL'); + return false; + } + return { + url: url + }; + }; + reset = function () { + $input.val(''); + }; + $form.append([ + label, + input, + h('br'), + button + ]); + })(); + return; + } + + }; + create['broadcast'] = function () { + var key = 'broadcast'; + var $div = makeBlock(key); + + var form = h('div.cp-admin-broadcast-form') + var $select = $(h('div.cp-dropdown-container')).appendTo($div); + var $form = $(form).appendTo($div); + + var categories = [ + 'maintenance', + 'survey', + 'version', + 'custom' + ]; + categories = categories.map(function (key) { + return { + tag: 'a', + content: h('span', Messages['broadcast_'+key]), + action: function () { + getBroadcastForm($form, key); + } + }; + }); + var dropdownCfg = { + text: Messages.support_category, + angleDown: 1, + options: categories, + container: $select, + isSelect: true + }; + UIElements.createDropdown(dropdownCfg); + + + return $div; + }; + + var onRefreshPerformance = Util.mkEvent(); create['refresh-performance'] = function () { @@ -1010,6 +1290,7 @@ define([ stats: 'fa fa-line-chart', quota: 'fa fa-hdd-o', support: 'fa fa-life-ring', + broadcast: 'fa fa-bullhorn', performance: 'fa fa-heartbeat', }; diff --git a/www/common/common-util.js b/www/common/common-util.js index 9ea892702..a6fe7afdf 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -554,6 +554,16 @@ return false; }; + Util.isValidURL = function (str) { + var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string + '(\\#[-a-z\\d_]*)?$','i'); // fragment locator + return !!pattern.test(str); + }; + var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/; var isEmoji = function (str) { return emoji_patt.test(str); diff --git a/www/common/notifications.js b/www/common/notifications.js index 478b765c7..93da2d9b9 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -406,6 +406,87 @@ define([ } }; + Messages.broadcast_newSurvey = "A new survey is available."; // XXX + handlers['BROADCAST_SURVEY'] = function (common, data) { + var content = data.content; + var msg = content.msg.content; + content.getFormatText = function () { + return Messages.broadcast_newSurvey; + }; + content.handler = function () { + common.openUnsafeURL(msg.url); + // XXX dismiss on click? + }; + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + + Messages.broadcast_newMaintenance = "A maintenance is planned between {0} and {1}"; // XXX + handlers['BROADCAST_MAINTENANCE'] = function (common, data) { + var content = data.content; + var msg = content.msg.content; + content.getFormatText = function () { + return Messages._getKey('broadcast_newMaintenance', [ + new Date(msg.start).toLocaleString(), + new Date(msg.end).toLocaleString(), + ]); + }; + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + + Messages.broadcast_newVersion = "A new version is available. Reload the page to discover the new features!"; // XXX + handlers['BROADCAST_VERSION'] = function (common, data) { + var content = data.content; + content.getFormatText = function () { + return Messages.broadcast_newVersion; + }; + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + + Messages.broadcast_newCustom = "Message from the administrators"; // XXX + handlers['BROADCAST_CUSTOM'] = function (common, data) { + var content = data.content; + var msg = content.msg.content; + var text = msg.content; + var defaultL = msg.defaultLanguage; + // Check if our language is available + var toShow = text[Messages._languageUsed]; + // Otherwise, fallback to the default language if it exists + if (!toShow && defaultL) { toShow = text[defaultL]; } + // No translation available, dismiss + if (!toShow) { defaultDismiss(common, data)(); } + + var slice = toShow.length > 500; + toShow = Util.fixHTML(toShow); + + content.getFormatText = function () { + // XXX Add a title to custom messages? Or use a generic key in the notification and only display the text in the alert? + if (slice) { + return toShow.slice(0, 500) + '...'; + } + return toShow; + }; + if (slice) { + content.handler = function () { + // XXX Allow markdown (sanitized)? + var content = h('div', [ + h('h4', Messages.broadcast_newCustom), + h('div', toShow) + ]); + UI.alert(content); + // XXX Dismiss on click? + }; + } + if (!content.archived) { + content.dismissHandler = defaultDismiss(common, data); + } + }; + // NOTE: don't forget to fixHTML everything returned by "getFormatText" return { diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index d9c0dc4e2..680eddd73 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -1,4 +1,5 @@ define([ + '/api/config', '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', @@ -7,17 +8,20 @@ define([ '/common/outer/mailbox-handlers.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) { +], function (Config, Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) { var Mailbox = {}; var TYPES = [ 'notifications', 'supportadmin', - 'support' + 'support', + 'broadcast' ]; var BLOCKING_TYPES = [ ]; + var BROADCAST_CHAN = '00000000000000000000000000000000'; + var initializeMailboxes = function (ctx, mailboxes) { if (!mailboxes['notifications']) { mailboxes.notifications = { @@ -39,6 +43,16 @@ define([ if (res.error) { console.error(res); } }); } + if (!mailboxes['broadcast']) { + mailboxes.broadcast = { + channel: BROADCAST_CHAN, + lastKnownHash: '', // XXX load /api/brooadcast to set this hash + decrypted: true, + viewed: [] + }; + } else { + // XXX update lastKnownHash from /api/broadcast + } }; /* @@ -80,33 +94,49 @@ proxy.mailboxes = { // Send a message to someone else var sendTo = Mailbox.sendTo = function (ctx, type, msg, user, _cb) { + user = user || {}; var cb = _cb || function (obj) { if (obj && obj.error) { console.error(obj.error); } }; + if (!Crypto.Mailbox) { return void cb({error: "chainpad-crypto is outdated and doesn't support mailboxes."}); } - var keys = getMyKeys(ctx); - if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); } - if (!user || !user.channel || !user.curvePublic) { return void cb({error: "no notification channel"}); } var anonRpc = Util.find(ctx, [ 'store', 'anon_rpc', ]); if (!anonRpc) { return void cb({error: "anonymous rpc session not ready"}); } - var crypto = Crypto.Mailbox.createEncryptor(keys); + // Broadcast mailbox doesn't use encryption. Sending messages there is restricted + // to admins in the server directly + var crypto = { encrypt: function (x) { return x; } }; + var channel = BROADCAST_CHAN; + var obj = { + uid: Util.uid(), // add uid at the beginning to have a unique server hash + type: type, + content: msg + }; - // Always send your data - if (typeof(msg) === "object" && !msg.user) { - var myData = Messaging.createData(ctx.store.proxy, false); - msg.user = myData; + if (!/^BROADCAST/.test(type)) { + var keys = getMyKeys(ctx); + if (!keys) { return void cb({error: "missing asymmetric encryption keys"}); } + if (!user || !user.channel || !user.curvePublic) { return void cb({error: "no notification channel"}); } + channel = user.channel; + crypto = Crypto.Mailbox.createEncryptor(keys); + + // Always send your data + if (typeof(msg) === "object" && !msg.user) { + var myData = Messaging.createData(ctx.store.proxy, false); + msg.user = myData; + } + obj = { + type: type, + content: msg + }; } - var text = JSON.stringify({ - type: type, - content: msg - }); + var text = JSON.stringify(obj); var ciphertext = crypto.encrypt(text, user.curvePublic); // If we've sent this message to one of our teams' mailbox, we may want to "dismiss" it @@ -121,7 +151,7 @@ proxy.mailboxes = { } anonRpc.send("WRITE_PRIVATE_MESSAGE", [ - user.channel, + channel, ciphertext ], function (err /*, response */) { if (err) { @@ -239,8 +269,11 @@ proxy.mailboxes = { return void console.error("chainpad-crypto is outdated and doesn't support mailboxes."); } var keys = m.keys || getMyKeys(ctx); - if (!keys) { return void console.error("missing asymmetric encryption keys"); } - var crypto = Crypto.Mailbox.createEncryptor(keys); + if (!keys && !m.decrypted) { return void console.error("missing asymmetric encryption keys"); } + var crypto = m.decrypted ? { + encrypt: function (x) { return x; }, + decrypt: function (x) { return x; } + } : Crypto.Mailbox.createEncryptor(keys); box.encryptor = crypto; var cfg = { network: ctx.store.network, diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index c89bdff3c..5f35b5a2c 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -106,7 +106,7 @@ define([ // Call the onMessage handlers var isNotification = function (type) { - return type === "notifications" || /^team-/.test(type); + return type === "notifications" || /^team-/.test(type) || type === "broadcast"; }; var pushMessage = function (data, handler) { var todo = function (f) { diff --git a/www/common/toolbar.js b/www/common/toolbar.js index c42aa36cb..452130223 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1081,7 +1081,7 @@ MessengerUI, Messages) { $button.addClass('fa-bell'); }; - Common.mailbox.subscribe(['notifications', 'team'], { + Common.mailbox.subscribe(['notifications', 'team', 'broadcast'], { onMessage: function (data, el) { if (el) { $(div).prepend(el); diff --git a/www/lib/datepicker/flatpickr.js b/www/lib/datepicker/flatpickr.js new file mode 100644 index 000000000..345297fa1 --- /dev/null +++ b/www/lib/datepicker/flatpickr.js @@ -0,0 +1,2 @@ +/* flatpickr v4.6.9,, @license MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).flatpickr=t()}(this,(function(){"use strict";var e=function(){return(e=Object.assign||function(e){for(var t,n=1,a=arguments.length;n",noCalendar:!1,now:new Date,onChange:[],onClose:[],onDayCreate:[],onDestroy:[],onKeyDown:[],onMonthChange:[],onOpen:[],onParseConfig:[],onReady:[],onValueUpdate:[],onYearChange:[],onPreCalendarPosition:[],plugins:[],position:"auto",positionElement:void 0,prevArrow:"",shorthandCurrentMonth:!1,showMonths:1,static:!1,time_24hr:!1,weekNumbers:!1,wrap:!1},i={weekdays:{shorthand:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],longhand:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},months:{shorthand:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],longhand:["January","February","March","April","May","June","July","August","September","October","November","December"]},daysInMonth:[31,28,31,30,31,30,31,31,30,31,30,31],firstDayOfWeek:0,ordinal:function(e){var t=e%100;if(t>3&&t<21)return"th";switch(t%10){case 1:return"st";case 2:return"nd";case 3:return"rd";default:return"th"}},rangeSeparator:" to ",weekAbbreviation:"Wk",scrollTitle:"Scroll to increment",toggleTitle:"Click to toggle",amPM:["AM","PM"],yearAriaLabel:"Year",monthAriaLabel:"Month",hourAriaLabel:"Hour",minuteAriaLabel:"Minute",time_24hr:!1},o=function(e,t){return void 0===t&&(t=2),("000"+e).slice(-1*t)},r=function(e){return!0===e?1:0};function l(e,t){var n;return function(){var a=this;clearTimeout(n),n=setTimeout((function(){return e.apply(a,arguments)}),t)}}var c=function(e){return e instanceof Array?e:[e]};function d(e,t,n){if(!0===n)return e.classList.add(t);e.classList.remove(t)}function s(e,t,n){var a=window.document.createElement(e);return t=t||"",n=n||"",a.className=t,void 0!==n&&(a.textContent=n),a}function u(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function f(e,t){return t(e)?e:e.parentNode?f(e.parentNode,t):void 0}function m(e,t){var n=s("div","numInputWrapper"),a=s("input","numInput "+e),i=s("span","arrowUp"),o=s("span","arrowDown");if(-1===navigator.userAgent.indexOf("MSIE 9.0")?a.type="number":(a.type="text",a.pattern="\\d*"),void 0!==t)for(var r in t)a.setAttribute(r,t[r]);return n.appendChild(a),n.appendChild(i),n.appendChild(o),n}function g(e){try{return"function"==typeof e.composedPath?e.composedPath()[0]:e.target}catch(t){return e.target}}var p=function(){},h=function(e,t,n){return n.months[t?"shorthand":"longhand"][e]},v={D:p,F:function(e,t,n){e.setMonth(n.months.longhand.indexOf(t))},G:function(e,t){e.setHours(parseFloat(t))},H:function(e,t){e.setHours(parseFloat(t))},J:function(e,t){e.setDate(parseFloat(t))},K:function(e,t,n){e.setHours(e.getHours()%12+12*r(new RegExp(n.amPM[1],"i").test(t)))},M:function(e,t,n){e.setMonth(n.months.shorthand.indexOf(t))},S:function(e,t){e.setSeconds(parseFloat(t))},U:function(e,t){return new Date(1e3*parseFloat(t))},W:function(e,t,n){var a=parseInt(t),i=new Date(e.getFullYear(),0,2+7*(a-1),0,0,0,0);return i.setDate(i.getDate()-i.getDay()+n.firstDayOfWeek),i},Y:function(e,t){e.setFullYear(parseFloat(t))},Z:function(e,t){return new Date(t)},d:function(e,t){e.setDate(parseFloat(t))},h:function(e,t){e.setHours(parseFloat(t))},i:function(e,t){e.setMinutes(parseFloat(t))},j:function(e,t){e.setDate(parseFloat(t))},l:p,m:function(e,t){e.setMonth(parseFloat(t)-1)},n:function(e,t){e.setMonth(parseFloat(t)-1)},s:function(e,t){e.setSeconds(parseFloat(t))},u:function(e,t){return new Date(parseFloat(t))},w:p,y:function(e,t){e.setFullYear(2e3+parseFloat(t))}},D={D:"(\\w+)",F:"(\\w+)",G:"(\\d\\d|\\d)",H:"(\\d\\d|\\d)",J:"(\\d\\d|\\d)\\w+",K:"",M:"(\\w+)",S:"(\\d\\d|\\d)",U:"(.+)",W:"(\\d\\d|\\d)",Y:"(\\d{4})",Z:"(.+)",d:"(\\d\\d|\\d)",h:"(\\d\\d|\\d)",i:"(\\d\\d|\\d)",j:"(\\d\\d|\\d)",l:"(\\w+)",m:"(\\d\\d|\\d)",n:"(\\d\\d|\\d)",s:"(\\d\\d|\\d)",u:"(.+)",w:"(\\d\\d|\\d)",y:"(\\d{2})"},w={Z:function(e){return e.toISOString()},D:function(e,t,n){return t.weekdays.shorthand[w.w(e,t,n)]},F:function(e,t,n){return h(w.n(e,t,n)-1,!1,t)},G:function(e,t,n){return o(w.h(e,t,n))},H:function(e){return o(e.getHours())},J:function(e,t){return void 0!==t.ordinal?e.getDate()+t.ordinal(e.getDate()):e.getDate()},K:function(e,t){return t.amPM[r(e.getHours()>11)]},M:function(e,t){return h(e.getMonth(),!0,t)},S:function(e){return o(e.getSeconds())},U:function(e){return e.getTime()/1e3},W:function(e,t,n){return n.getWeek(e)},Y:function(e){return o(e.getFullYear(),4)},d:function(e){return o(e.getDate())},h:function(e){return e.getHours()%12?e.getHours()%12:12},i:function(e){return o(e.getMinutes())},j:function(e){return e.getDate()},l:function(e,t){return t.weekdays.longhand[e.getDay()]},m:function(e){return o(e.getMonth()+1)},n:function(e){return e.getMonth()+1},s:function(e){return e.getSeconds()},u:function(e){return e.getTime()},w:function(e){return e.getDay()},y:function(e){return String(e.getFullYear()).substring(2)}},b=function(e){var t=e.config,n=void 0===t?a:t,o=e.l10n,r=void 0===o?i:o,l=e.isMobile,c=void 0!==l&&l;return function(e,t,a){var i=a||r;return void 0===n.formatDate||c?t.split("").map((function(t,a,o){return w[t]&&"\\"!==o[a-1]?w[t](e,i,n):"\\"!==t?t:""})).join(""):n.formatDate(e,t,i)}},C=function(e){var t=e.config,n=void 0===t?a:t,o=e.l10n,r=void 0===o?i:o;return function(e,t,i,o){if(0===e||e){var l,c=o||r,d=e;if(e instanceof Date)l=new Date(e.getTime());else if("string"!=typeof e&&void 0!==e.toFixed)l=new Date(e);else if("string"==typeof e){var s=t||(n||a).dateFormat,u=String(e).trim();if("today"===u)l=new Date,i=!0;else if(/Z$/.test(u)||/GMT$/.test(u))l=new Date(e);else if(n&&n.parseDate)l=n.parseDate(e,s);else{l=n&&n.noCalendar?new Date((new Date).setHours(0,0,0,0)):new Date((new Date).getFullYear(),0,1,0,0,0,0);for(var f=void 0,m=[],g=0,p=0,h="";g=0?new Date:new Date(w.config.minDate.getTime()),n=x(w.config);t.setHours(n.hours,n.minutes,n.seconds,t.getMilliseconds()),w.selectedDates=[t],w.latestSelectedDateObj=t}void 0!==e&&"blur"!==e.type&&function(e){e.preventDefault();var t="keydown"===e.type,n=g(e),a=n;void 0!==w.amPM&&n===w.amPM&&(w.amPM.textContent=w.l10n.amPM[r(w.amPM.textContent===w.l10n.amPM[0])]);var i=parseFloat(a.getAttribute("min")),l=parseFloat(a.getAttribute("max")),c=parseFloat(a.getAttribute("step")),d=parseInt(a.value,10),s=e.delta||(t?38===e.which?1:-1:0),u=d+c*s;if(void 0!==a.value&&2===a.value.length){var f=a===w.hourElement,m=a===w.minuteElement;ul&&(u=a===w.hourElement?u-l-r(!w.amPM):i,m&&j(void 0,1,w.hourElement)),w.amPM&&f&&(1===c?u+d===23:Math.abs(u-d)>c)&&(w.amPM.textContent=w.l10n.amPM[r(w.amPM.textContent===w.l10n.amPM[0])]),a.value=o(u)}}(e);var a=w._input.value;S(),be(),w._input.value!==a&&w._debouncedChange()}function S(){if(void 0!==w.hourElement&&void 0!==w.minuteElement){var e,t,n=(parseInt(w.hourElement.value.slice(-2),10)||0)%24,a=(parseInt(w.minuteElement.value,10)||0)%60,i=void 0!==w.secondElement?(parseInt(w.secondElement.value,10)||0)%60:0;void 0!==w.amPM&&(e=n,t=w.amPM.textContent,n=e%12+12*r(t===w.l10n.amPM[1]));var o=void 0!==w.config.minTime||w.config.minDate&&w.minDateHasTime&&w.latestSelectedDateObj&&0===M(w.latestSelectedDateObj,w.config.minDate,!0);if(void 0!==w.config.maxTime||w.config.maxDate&&w.maxDateHasTime&&w.latestSelectedDateObj&&0===M(w.latestSelectedDateObj,w.config.maxDate,!0)){var l=void 0!==w.config.maxTime?w.config.maxTime:w.config.maxDate;(n=Math.min(n,l.getHours()))===l.getHours()&&(a=Math.min(a,l.getMinutes())),a===l.getMinutes()&&(i=Math.min(i,l.getSeconds()))}if(o){var c=void 0!==w.config.minTime?w.config.minTime:w.config.minDate;(n=Math.max(n,c.getHours()))===c.getHours()&&a=12)]),void 0!==w.secondElement&&(w.secondElement.value=o(n)))}function F(e){var t=g(e),n=parseInt(t.value)+(e.delta||0);(n/1e3>1||"Enter"===e.key&&!/[^\d]/.test(n.toString()))&&Q(n)}function A(e,t,n,a){return t instanceof Array?t.forEach((function(t){return A(e,t,n,a)})):e instanceof Array?e.forEach((function(e){return A(e,t,n,a)})):(e.addEventListener(t,n,a),void w._handlers.push({remove:function(){return e.removeEventListener(t,n)}}))}function N(){pe("onChange")}function P(e,t){var n=void 0!==e?w.parseDate(e):w.latestSelectedDateObj||(w.config.minDate&&w.config.minDate>w.now?w.config.minDate:w.config.maxDate&&w.config.maxDate=0&&M(e,w.selectedDates[1])<=0)}(t)&&!ve(t)&&o.classList.add("inRange"),w.weekNumbers&&1===w.config.showMonths&&"prevMonthDay"!==e&&n%7==1&&w.weekNumbers.insertAdjacentHTML("beforeend",""+w.config.getWeek(t)+""),pe("onDayCreate",o),o}function L(e){e.focus(),"range"===w.config.mode&&ae(e)}function W(e){for(var t=e>0?0:w.config.showMonths-1,n=e>0?w.config.showMonths:-1,a=t;a!=n;a+=e)for(var i=w.daysContainer.children[a],o=e>0?0:i.children.length-1,r=e>0?i.children.length:-1,l=o;l!=r;l+=e){var c=i.children[l];if(-1===c.className.indexOf("hidden")&&X(c.dateObj))return c}}function R(e,t){var n=ee(document.activeElement||document.body),a=void 0!==e?e:n?document.activeElement:void 0!==w.selectedDateElem&&ee(w.selectedDateElem)?w.selectedDateElem:void 0!==w.todayDateElem&&ee(w.todayDateElem)?w.todayDateElem:W(t>0?1:-1);void 0===a?w._input.focus():n?function(e,t){for(var n=-1===e.className.indexOf("Month")?e.dateObj.getMonth():w.currentMonth,a=t>0?w.config.showMonths:-1,i=t>0?1:-1,o=n-w.currentMonth;o!=a;o+=i)for(var r=w.daysContainer.children[o],l=n-w.currentMonth===o?e.$i+t:t<0?r.children.length-1:0,c=r.children.length,d=l;d>=0&&d0?c:-1);d+=i){var s=r.children[d];if(-1===s.className.indexOf("hidden")&&X(s.dateObj)&&Math.abs(e.$i-d)>=Math.abs(t))return L(s)}w.changeMonth(i),R(W(i),0)}(a,t):L(a)}function B(e,t){for(var n=(new Date(e,t,1).getDay()-w.l10n.firstDayOfWeek+7)%7,a=w.utils.getDaysInMonth((t-1+12)%12,e),i=w.utils.getDaysInMonth(t,e),o=window.document.createDocumentFragment(),r=w.config.showMonths>1,l=r?"prevMonthDay hidden":"prevMonthDay",c=r?"nextMonthDay hidden":"nextMonthDay",d=a+1-n,u=0;d<=a;d++,u++)o.appendChild(H(l,new Date(e,t-1,d),d,u));for(d=1;d<=i;d++,u++)o.appendChild(H("",new Date(e,t,d),d,u));for(var f=i+1;f<=42-n&&(1===w.config.showMonths||u%7!=0);f++,u++)o.appendChild(H(c,new Date(e,t+1,f%i),f,u));var m=s("div","dayContainer");return m.appendChild(o),m}function J(){if(void 0!==w.daysContainer){u(w.daysContainer),w.weekNumbers&&u(w.weekNumbers);for(var e=document.createDocumentFragment(),t=0;t1||"dropdown"!==w.config.monthSelectorType)){var e=function(e){return!(void 0!==w.config.minDate&&w.currentYear===w.config.minDate.getFullYear()&&ew.config.maxDate.getMonth())};w.monthsDropdownContainer.tabIndex=-1,w.monthsDropdownContainer.innerHTML="";for(var t=0;t<12;t++)if(e(t)){var n=s("option","flatpickr-monthDropdown-month");n.value=new Date(w.currentYear,t).getMonth().toString(),n.textContent=h(t,w.config.shorthandCurrentMonth,w.l10n),n.tabIndex=-1,w.currentMonth===t&&(n.selected=!0),w.monthsDropdownContainer.appendChild(n)}}}function U(){var e,t=s("div","flatpickr-month"),n=window.document.createDocumentFragment();w.config.showMonths>1||"static"===w.config.monthSelectorType?e=s("span","cur-month"):(w.monthsDropdownContainer=s("select","flatpickr-monthDropdown-months"),w.monthsDropdownContainer.setAttribute("aria-label",w.l10n.monthAriaLabel),A(w.monthsDropdownContainer,"change",(function(e){var t=g(e),n=parseInt(t.value,10);w.changeMonth(n-w.currentMonth),pe("onMonthChange")})),K(),e=w.monthsDropdownContainer);var a=m("cur-year",{tabindex:"-1"}),i=a.getElementsByTagName("input")[0];i.setAttribute("aria-label",w.l10n.yearAriaLabel),w.config.minDate&&i.setAttribute("min",w.config.minDate.getFullYear().toString()),w.config.maxDate&&(i.setAttribute("max",w.config.maxDate.getFullYear().toString()),i.disabled=!!w.config.minDate&&w.config.minDate.getFullYear()===w.config.maxDate.getFullYear());var o=s("div","flatpickr-current-month");return o.appendChild(e),o.appendChild(a),n.appendChild(o),t.appendChild(n),{container:t,yearElement:i,monthElement:e}}function q(){u(w.monthNav),w.monthNav.appendChild(w.prevMonthNav),w.config.showMonths&&(w.yearElements=[],w.monthElements=[]);for(var e=w.config.showMonths;e--;){var t=U();w.yearElements.push(t.yearElement),w.monthElements.push(t.monthElement),w.monthNav.appendChild(t.container)}w.monthNav.appendChild(w.nextMonthNav)}function $(){w.weekdayContainer?u(w.weekdayContainer):w.weekdayContainer=s("div","flatpickr-weekdays");for(var e=w.config.showMonths;e--;){var t=s("div","flatpickr-weekdaycontainer");w.weekdayContainer.appendChild(t)}return z(),w.weekdayContainer}function z(){if(w.weekdayContainer){var e=w.l10n.firstDayOfWeek,n=t(w.l10n.weekdays.shorthand);e>0&&e\n "+n.join("")+"\n \n "}}function G(e,t){void 0===t&&(t=!0);var n=t?e:e-w.currentMonth;n<0&&!0===w._hidePrevMonthArrow||n>0&&!0===w._hideNextMonthArrow||(w.currentMonth+=n,(w.currentMonth<0||w.currentMonth>11)&&(w.currentYear+=w.currentMonth>11?1:-1,w.currentMonth=(w.currentMonth+12)%12,pe("onYearChange"),K()),J(),pe("onMonthChange"),De())}function V(e){return!(!w.config.appendTo||!w.config.appendTo.contains(e))||w.calendarContainer.contains(e)}function Z(e){if(w.isOpen&&!w.config.inline){var t=g(e),n=V(t),a=t===w.input||t===w.altInput||w.element.contains(t)||e.path&&e.path.indexOf&&(~e.path.indexOf(w.input)||~e.path.indexOf(w.altInput)),i="blur"===e.type?a&&e.relatedTarget&&!V(e.relatedTarget):!a&&!n&&!V(e.relatedTarget),o=!w.config.ignoredFocusElements.some((function(e){return e.contains(t)}));i&&o&&(void 0!==w.timeContainer&&void 0!==w.minuteElement&&void 0!==w.hourElement&&""!==w.input.value&&void 0!==w.input.value&&I(),w.close(),w.config&&"range"===w.config.mode&&1===w.selectedDates.length&&(w.clear(!1),w.redraw()))}}function Q(e){if(!(!e||w.config.minDate&&ew.config.maxDate.getFullYear())){var t=e,n=w.currentYear!==t;w.currentYear=t||w.currentYear,w.config.maxDate&&w.currentYear===w.config.maxDate.getFullYear()?w.currentMonth=Math.min(w.config.maxDate.getMonth(),w.currentMonth):w.config.minDate&&w.currentYear===w.config.minDate.getFullYear()&&(w.currentMonth=Math.max(w.config.minDate.getMonth(),w.currentMonth)),n&&(w.redraw(),pe("onYearChange"),K())}}function X(e,t){var n;void 0===t&&(t=!0);var a=w.parseDate(e,void 0,t);if(w.config.minDate&&a&&M(a,w.config.minDate,void 0!==t?t:!w.minDateHasTime)<0||w.config.maxDate&&a&&M(a,w.config.maxDate,void 0!==t?t:!w.maxDateHasTime)>0)return!1;if(!w.config.enable&&0===w.config.disable.length)return!0;if(void 0===a)return!1;for(var i=!!w.config.enable,o=null!==(n=w.config.enable)&&void 0!==n?n:w.config.disable,r=0,l=void 0;r=l.from.getTime()&&a.getTime()<=l.to.getTime())return i}return!i}function ee(e){return void 0!==w.daysContainer&&(-1===e.className.indexOf("hidden")&&-1===e.className.indexOf("flatpickr-disabled")&&w.daysContainer.contains(e))}function te(e){!(e.target===w._input)||!(w.selectedDates.length>0||w._input.value.length>0)||e.relatedTarget&&V(e.relatedTarget)||w.setDate(w._input.value,!0,e.target===w.altInput?w.config.altFormat:w.config.dateFormat)}function ne(e){var t=g(e),n=w.config.wrap?p.contains(t):t===w._input,a=w.config.allowInput,i=w.isOpen&&(!a||!n),o=w.config.inline&&n&&!a;if(13===e.keyCode&&n){if(a)return w.setDate(w._input.value,!0,t===w.altInput?w.config.altFormat:w.config.dateFormat),t.blur();w.open()}else if(V(t)||i||o){var r=!!w.timeContainer&&w.timeContainer.contains(t);switch(e.keyCode){case 13:r?(e.preventDefault(),I(),se()):ue(e);break;case 27:e.preventDefault(),se();break;case 8:case 46:n&&!w.config.allowInput&&(e.preventDefault(),w.clear());break;case 37:case 39:if(r||n)w.hourElement&&w.hourElement.focus();else if(e.preventDefault(),void 0!==w.daysContainer&&(!1===a||document.activeElement&&ee(document.activeElement))){var l=39===e.keyCode?1:-1;e.ctrlKey?(e.stopPropagation(),G(l),R(W(1),0)):R(void 0,l)}break;case 38:case 40:e.preventDefault();var c=40===e.keyCode?1:-1;w.daysContainer&&void 0!==t.$i||t===w.input||t===w.altInput?e.ctrlKey?(e.stopPropagation(),Q(w.currentYear-c),R(W(1),0)):r||R(void 0,7*c):t===w.currentYearElement?Q(w.currentYear-c):w.config.enableTime&&(!r&&w.hourElement&&w.hourElement.focus(),I(e),w._debouncedChange());break;case 9:if(r){var d=[w.hourElement,w.minuteElement,w.secondElement,w.amPM].concat(w.pluginElements).filter((function(e){return e})),s=d.indexOf(t);if(-1!==s){var u=d[s+(e.shiftKey?-1:1)];e.preventDefault(),(u||w._input).focus()}}else!w.config.noCalendar&&w.daysContainer&&w.daysContainer.contains(t)&&e.shiftKey&&(e.preventDefault(),w._input.focus())}}if(void 0!==w.amPM&&t===w.amPM)switch(e.key){case w.l10n.amPM[0].charAt(0):case w.l10n.amPM[0].charAt(0).toLowerCase():w.amPM.textContent=w.l10n.amPM[0],S(),be();break;case w.l10n.amPM[1].charAt(0):case w.l10n.amPM[1].charAt(0).toLowerCase():w.amPM.textContent=w.l10n.amPM[1],S(),be()}(n||V(t))&&pe("onKeyDown",e)}function ae(e){if(1===w.selectedDates.length&&(!e||e.classList.contains("flatpickr-day")&&!e.classList.contains("flatpickr-disabled"))){for(var t=e?e.dateObj.getTime():w.days.firstElementChild.dateObj.getTime(),n=w.parseDate(w.selectedDates[0],void 0,!0).getTime(),a=Math.min(t,w.selectedDates[0].getTime()),i=Math.max(t,w.selectedDates[0].getTime()),o=!1,r=0,l=0,c=a;ca&&cr)?r=c:c>n&&(!l||c0&&m0&&m>l;return g?(f.classList.add("notAllowed"),["inRange","startRange","endRange"].forEach((function(e){f.classList.remove(e)})),"continue"):o&&!g?"continue":(["startRange","inRange","endRange","notAllowed"].forEach((function(e){f.classList.remove(e)})),void(void 0!==e&&(e.classList.add(t<=w.selectedDates[0].getTime()?"startRange":"endRange"),nt&&m===n&&f.classList.add("endRange"),m>=r&&(0===l||m<=l)&&(d=n,u=t,(c=m)>Math.min(d,u)&&c0||n.getMinutes()>0||n.getSeconds()>0),w.selectedDates&&(w.selectedDates=w.selectedDates.filter((function(e){return X(e)})),w.selectedDates.length||"min"!==e||_(n),be()),w.daysContainer&&(de(),void 0!==n?w.currentYearElement[e]=n.getFullYear().toString():w.currentYearElement.removeAttribute(e),w.currentYearElement.disabled=!!a&&void 0!==n&&a.getFullYear()===n.getFullYear())}}function re(){return w.config.wrap?p.querySelector("[data-input]"):p}function le(){"object"!=typeof w.config.locale&&void 0===T.l10ns[w.config.locale]&&w.config.errorHandler(new Error("flatpickr: invalid locale "+w.config.locale)),w.l10n=e(e({},T.l10ns.default),"object"==typeof w.config.locale?w.config.locale:"default"!==w.config.locale?T.l10ns[w.config.locale]:void 0),D.K="("+w.l10n.amPM[0]+"|"+w.l10n.amPM[1]+"|"+w.l10n.amPM[0].toLowerCase()+"|"+w.l10n.amPM[1].toLowerCase()+")",void 0===e(e({},v),JSON.parse(JSON.stringify(p.dataset||{}))).time_24hr&&void 0===T.defaultConfig.time_24hr&&(w.config.time_24hr=w.l10n.time_24hr),w.formatDate=b(w),w.parseDate=C({config:w.config,l10n:w.l10n})}function ce(e){if("function"!=typeof w.config.position){if(void 0!==w.calendarContainer){pe("onPreCalendarPosition");var t=e||w._positionElement,n=Array.prototype.reduce.call(w.calendarContainer.children,(function(e,t){return e+t.offsetHeight}),0),a=w.calendarContainer.offsetWidth,i=w.config.position.split(" "),o=i[0],r=i.length>1?i[1]:null,l=t.getBoundingClientRect(),c=window.innerHeight-l.bottom,s="above"===o||"below"!==o&&cn,u=window.pageYOffset+l.top+(s?-n-2:t.offsetHeight+2);if(d(w.calendarContainer,"arrowTop",!s),d(w.calendarContainer,"arrowBottom",s),!w.config.inline){var f=window.pageXOffset+l.left,m=!1,g=!1;"center"===r?(f-=(a-l.width)/2,m=!0):"right"===r&&(f-=a-l.width,g=!0),d(w.calendarContainer,"arrowLeft",!m&&!g),d(w.calendarContainer,"arrowCenter",m),d(w.calendarContainer,"arrowRight",g);var p=window.document.body.offsetWidth-(window.pageXOffset+l.right),h=f+a>window.document.body.offsetWidth,v=p+a>window.document.body.offsetWidth;if(d(w.calendarContainer,"rightMost",h),!w.config.static)if(w.calendarContainer.style.top=u+"px",h)if(v){var D=function(){for(var e=null,t=0;tw.currentMonth+w.config.showMonths-1)&&"range"!==w.config.mode;if(w.selectedDateElem=n,"single"===w.config.mode)w.selectedDates=[a];else if("multiple"===w.config.mode){var o=ve(a);o?w.selectedDates.splice(parseInt(o),1):w.selectedDates.push(a)}else"range"===w.config.mode&&(2===w.selectedDates.length&&w.clear(!1,!1),w.latestSelectedDateObj=a,w.selectedDates.push(a),0!==M(a,w.selectedDates[0],!0)&&w.selectedDates.sort((function(e,t){return e.getTime()-t.getTime()})));if(S(),i){var r=w.currentYear!==a.getFullYear();w.currentYear=a.getFullYear(),w.currentMonth=a.getMonth(),r&&(pe("onYearChange"),K()),pe("onMonthChange")}if(De(),J(),be(),i||"range"===w.config.mode||1!==w.config.showMonths?void 0!==w.selectedDateElem&&void 0===w.hourElement&&w.selectedDateElem&&w.selectedDateElem.focus():L(n),void 0!==w.hourElement&&void 0!==w.hourElement&&w.hourElement.focus(),w.config.closeOnSelect){var l="single"===w.config.mode&&!w.config.enableTime,c="range"===w.config.mode&&2===w.selectedDates.length&&!w.config.enableTime;(l||c)&&se()}N()}}w.parseDate=C({config:w.config,l10n:w.l10n}),w._handlers=[],w.pluginElements=[],w.loadedPlugins=[],w._bind=A,w._setHoursFromDate=_,w._positionCalendar=ce,w.changeMonth=G,w.changeYear=Q,w.clear=function(e,t){void 0===e&&(e=!0);void 0===t&&(t=!0);w.input.value="",void 0!==w.altInput&&(w.altInput.value="");void 0!==w.mobileInput&&(w.mobileInput.value="");w.selectedDates=[],w.latestSelectedDateObj=void 0,!0===t&&(w.currentYear=w._initialDate.getFullYear(),w.currentMonth=w._initialDate.getMonth());if(!0===w.config.enableTime){var n=x(w.config),a=n.hours,i=n.minutes,o=n.seconds;O(a,i,o)}w.redraw(),e&&pe("onChange")},w.close=function(){w.isOpen=!1,w.isMobile||(void 0!==w.calendarContainer&&w.calendarContainer.classList.remove("open"),void 0!==w._input&&w._input.classList.remove("active"));pe("onClose")},w._createElement=s,w.destroy=function(){void 0!==w.config&&pe("onDestroy");for(var e=w._handlers.length;e--;)w._handlers[e].remove();if(w._handlers=[],w.mobileInput)w.mobileInput.parentNode&&w.mobileInput.parentNode.removeChild(w.mobileInput),w.mobileInput=void 0;else if(w.calendarContainer&&w.calendarContainer.parentNode)if(w.config.static&&w.calendarContainer.parentNode){var t=w.calendarContainer.parentNode;if(t.lastChild&&t.removeChild(t.lastChild),t.parentNode){for(;t.firstChild;)t.parentNode.insertBefore(t.firstChild,t);t.parentNode.removeChild(t)}}else w.calendarContainer.parentNode.removeChild(w.calendarContainer);w.altInput&&(w.input.type="text",w.altInput.parentNode&&w.altInput.parentNode.removeChild(w.altInput),delete w.altInput);w.input&&(w.input.type=w.input._type,w.input.classList.remove("flatpickr-input"),w.input.removeAttribute("readonly"));["_showTimeInput","latestSelectedDateObj","_hideNextMonthArrow","_hidePrevMonthArrow","__hideNextMonthArrow","__hidePrevMonthArrow","isMobile","isOpen","selectedDateElem","minDateHasTime","maxDateHasTime","days","daysContainer","_input","_positionElement","innerContainer","rContainer","monthNav","todayDateElem","calendarContainer","weekdayContainer","prevMonthNav","nextMonthNav","monthsDropdownContainer","currentMonthElement","currentYearElement","navigationCurrentMonth","selectedDateElem","config"].forEach((function(e){try{delete w[e]}catch(e){}}))},w.isEnabled=X,w.jumpToDate=P,w.open=function(e,t){void 0===t&&(t=w._positionElement);if(!0===w.isMobile){if(e){e.preventDefault();var n=g(e);n&&n.blur()}return void 0!==w.mobileInput&&(w.mobileInput.focus(),w.mobileInput.click()),void pe("onOpen")}if(w._input.disabled||w.config.inline)return;var a=w.isOpen;w.isOpen=!0,a||(w.calendarContainer.classList.add("open"),w._input.classList.add("active"),pe("onOpen"),ce(t));!0===w.config.enableTime&&!0===w.config.noCalendar&&(!1!==w.config.allowInput||void 0!==e&&w.timeContainer.contains(e.relatedTarget)||setTimeout((function(){return w.hourElement.select()}),50))},w.redraw=de,w.set=function(e,t){if(null!==e&&"object"==typeof e)for(var a in Object.assign(w.config,e),e)void 0!==fe[a]&&fe[a].forEach((function(e){return e()}));else w.config[e]=t,void 0!==fe[e]?fe[e].forEach((function(e){return e()})):n.indexOf(e)>-1&&(w.config[e]=c(t));w.redraw(),be(!0)},w.setDate=function(e,t,n){void 0===t&&(t=!1);void 0===n&&(n=w.config.dateFormat);if(0!==e&&!e||e instanceof Array&&0===e.length)return w.clear(t);me(e,n),w.latestSelectedDateObj=w.selectedDates[w.selectedDates.length-1],w.redraw(),P(void 0,t),_(),0===w.selectedDates.length&&w.clear(!1);be(t),t&&pe("onChange")},w.toggle=function(e){if(!0===w.isOpen)return w.close();w.open(e)};var fe={locale:[le,z],showMonths:[q,k,$],minDate:[P],maxDate:[P],clickOpens:[function(){!0===w.config.clickOpens?(A(w._input,"focus",w.open),A(w._input,"click",w.open)):(w._input.removeEventListener("focus",w.open),w._input.removeEventListener("click",w.open))}]};function me(e,t){var n=[];if(e instanceof Array)n=e.map((function(e){return w.parseDate(e,t)}));else if(e instanceof Date||"number"==typeof e)n=[w.parseDate(e,t)];else if("string"==typeof e)switch(w.config.mode){case"single":case"time":n=[w.parseDate(e,t)];break;case"multiple":n=e.split(w.config.conjunction).map((function(e){return w.parseDate(e,t)}));break;case"range":n=e.split(w.l10n.rangeSeparator).map((function(e){return w.parseDate(e,t)}))}else w.config.errorHandler(new Error("Invalid date supplied: "+JSON.stringify(e)));w.selectedDates=w.config.allowInvalidPreload?n:n.filter((function(e){return e instanceof Date&&X(e,!1)})),"range"===w.config.mode&&w.selectedDates.sort((function(e,t){return e.getTime()-t.getTime()}))}function ge(e){return e.slice().map((function(e){return"string"==typeof e||"number"==typeof e||e instanceof Date?w.parseDate(e,void 0,!0):e&&"object"==typeof e&&e.from&&e.to?{from:w.parseDate(e.from,void 0),to:w.parseDate(e.to,void 0)}:e})).filter((function(e){return e}))}function pe(e,t){if(void 0!==w.config){var n=w.config[e];if(void 0!==n&&n.length>0)for(var a=0;n[a]&&a1||"static"===w.config.monthSelectorType?w.monthElements[t].textContent=h(n.getMonth(),w.config.shorthandCurrentMonth,w.l10n)+" ":w.monthsDropdownContainer.value=n.getMonth().toString(),e.value=n.getFullYear().toString()})),w._hidePrevMonthArrow=void 0!==w.config.minDate&&(w.currentYear===w.config.minDate.getFullYear()?w.currentMonth<=w.config.minDate.getMonth():w.currentYearw.config.maxDate.getMonth():w.currentYear>w.config.maxDate.getFullYear()))}function we(e){return w.selectedDates.map((function(t){return w.formatDate(t,e)})).filter((function(e,t,n){return"range"!==w.config.mode||w.config.enableTime||n.indexOf(e)===t})).join("range"!==w.config.mode?w.config.conjunction:w.l10n.rangeSeparator)}function be(e){void 0===e&&(e=!0),void 0!==w.mobileInput&&w.mobileFormatStr&&(w.mobileInput.value=void 0!==w.latestSelectedDateObj?w.formatDate(w.latestSelectedDateObj,w.mobileFormatStr):""),w.input.value=we(w.config.dateFormat),void 0!==w.altInput&&(w.altInput.value=we(w.config.altFormat)),!1!==e&&pe("onValueUpdate")}function Ce(e){var t=g(e),n=w.prevMonthNav.contains(t),a=w.nextMonthNav.contains(t);n||a?G(n?-1:1):w.yearElements.indexOf(t)>=0?t.select():t.classList.contains("arrowUp")?w.changeYear(w.currentYear+1):t.classList.contains("arrowDown")&&w.changeYear(w.currentYear-1)}return function(){w.element=w.input=p,w.isOpen=!1,function(){var t=["wrap","weekNumbers","allowInput","allowInvalidPreload","clickOpens","time_24hr","enableTime","noCalendar","altInput","shorthandCurrentMonth","inline","static","enableSeconds","disableMobile"],i=e(e({},JSON.parse(JSON.stringify(p.dataset||{}))),v),o={};w.config.parseDate=i.parseDate,w.config.formatDate=i.formatDate,Object.defineProperty(w.config,"enable",{get:function(){return w.config._enable},set:function(e){w.config._enable=ge(e)}}),Object.defineProperty(w.config,"disable",{get:function(){return w.config._disable},set:function(e){w.config._disable=ge(e)}});var r="time"===i.mode;if(!i.dateFormat&&(i.enableTime||r)){var l=T.defaultConfig.dateFormat||a.dateFormat;o.dateFormat=i.noCalendar||r?"H:i"+(i.enableSeconds?":S":""):l+" H:i"+(i.enableSeconds?":S":"")}if(i.altInput&&(i.enableTime||r)&&!i.altFormat){var d=T.defaultConfig.altFormat||a.altFormat;o.altFormat=i.noCalendar||r?"h:i"+(i.enableSeconds?":S K":" K"):d+" h:i"+(i.enableSeconds?":S":"")+" K"}Object.defineProperty(w.config,"minDate",{get:function(){return w.config._minDate},set:oe("min")}),Object.defineProperty(w.config,"maxDate",{get:function(){return w.config._maxDate},set:oe("max")});var s=function(e){return function(t){w.config["min"===e?"_minTime":"_maxTime"]=w.parseDate(t,"H:i:S")}};Object.defineProperty(w.config,"minTime",{get:function(){return w.config._minTime},set:s("min")}),Object.defineProperty(w.config,"maxTime",{get:function(){return w.config._maxTime},set:s("max")}),"time"===i.mode&&(w.config.noCalendar=!0,w.config.enableTime=!0);Object.assign(w.config,o,i);for(var u=0;u-1?w.config[m]=c(f[m]).map(E).concat(w.config[m]):void 0===i[m]&&(w.config[m]=f[m])}i.altInputClass||(w.config.altInputClass=re().className+" "+w.config.altInputClass);pe("onParseConfig")}(),le(),function(){if(w.input=re(),!w.input)return void w.config.errorHandler(new Error("Invalid input element specified"));w.input._type=w.input.type,w.input.type="text",w.input.classList.add("flatpickr-input"),w._input=w.input,w.config.altInput&&(w.altInput=s(w.input.nodeName,w.config.altInputClass),w._input=w.altInput,w.altInput.placeholder=w.input.placeholder,w.altInput.disabled=w.input.disabled,w.altInput.required=w.input.required,w.altInput.tabIndex=w.input.tabIndex,w.altInput.type="text",w.input.setAttribute("type","hidden"),!w.config.static&&w.input.parentNode&&w.input.parentNode.insertBefore(w.altInput,w.input.nextSibling));w.config.allowInput||w._input.setAttribute("readonly","readonly");w._positionElement=w.config.positionElement||w._input}(),function(){w.selectedDates=[],w.now=w.parseDate(w.config.now)||new Date;var e=w.config.defaultDate||("INPUT"!==w.input.nodeName&&"TEXTAREA"!==w.input.nodeName||!w.input.placeholder||w.input.value!==w.input.placeholder?w.input.value:null);e&&me(e,w.config.dateFormat);w._initialDate=w.selectedDates.length>0?w.selectedDates[0]:w.config.minDate&&w.config.minDate.getTime()>w.now.getTime()?w.config.minDate:w.config.maxDate&&w.config.maxDate.getTime()0&&(w.latestSelectedDateObj=w.selectedDates[0]);void 0!==w.config.minTime&&(w.config.minTime=w.parseDate(w.config.minTime,"H:i"));void 0!==w.config.maxTime&&(w.config.maxTime=w.parseDate(w.config.maxTime,"H:i"));w.minDateHasTime=!!w.config.minDate&&(w.config.minDate.getHours()>0||w.config.minDate.getMinutes()>0||w.config.minDate.getSeconds()>0),w.maxDateHasTime=!!w.config.maxDate&&(w.config.maxDate.getHours()>0||w.config.maxDate.getMinutes()>0||w.config.maxDate.getSeconds()>0)}(),w.utils={getDaysInMonth:function(e,t){return void 0===e&&(e=w.currentMonth),void 0===t&&(t=w.currentYear),1===e&&(t%4==0&&t%100!=0||t%400==0)?29:w.l10n.daysInMonth[e]}},w.isMobile||function(){var e=window.document.createDocumentFragment();if(w.calendarContainer=s("div","flatpickr-calendar"),w.calendarContainer.tabIndex=-1,!w.config.noCalendar){if(e.appendChild((w.monthNav=s("div","flatpickr-months"),w.yearElements=[],w.monthElements=[],w.prevMonthNav=s("span","flatpickr-prev-month"),w.prevMonthNav.innerHTML=w.config.prevArrow,w.nextMonthNav=s("span","flatpickr-next-month"),w.nextMonthNav.innerHTML=w.config.nextArrow,q(),Object.defineProperty(w,"_hidePrevMonthArrow",{get:function(){return w.__hidePrevMonthArrow},set:function(e){w.__hidePrevMonthArrow!==e&&(d(w.prevMonthNav,"flatpickr-disabled",e),w.__hidePrevMonthArrow=e)}}),Object.defineProperty(w,"_hideNextMonthArrow",{get:function(){return w.__hideNextMonthArrow},set:function(e){w.__hideNextMonthArrow!==e&&(d(w.nextMonthNav,"flatpickr-disabled",e),w.__hideNextMonthArrow=e)}}),w.currentYearElement=w.yearElements[0],De(),w.monthNav)),w.innerContainer=s("div","flatpickr-innerContainer"),w.config.weekNumbers){var t=function(){w.calendarContainer.classList.add("hasWeeks");var e=s("div","flatpickr-weekwrapper");e.appendChild(s("span","flatpickr-weekday",w.l10n.weekAbbreviation));var t=s("div","flatpickr-weeks");return e.appendChild(t),{weekWrapper:e,weekNumbers:t}}(),n=t.weekWrapper,a=t.weekNumbers;w.innerContainer.appendChild(n),w.weekNumbers=a,w.weekWrapper=n}w.rContainer=s("div","flatpickr-rContainer"),w.rContainer.appendChild($()),w.daysContainer||(w.daysContainer=s("div","flatpickr-days"),w.daysContainer.tabIndex=-1),J(),w.rContainer.appendChild(w.daysContainer),w.innerContainer.appendChild(w.rContainer),e.appendChild(w.innerContainer)}w.config.enableTime&&e.appendChild(function(){w.calendarContainer.classList.add("hasTime"),w.config.noCalendar&&w.calendarContainer.classList.add("noCalendar");var e=x(w.config);w.timeContainer=s("div","flatpickr-time"),w.timeContainer.tabIndex=-1;var t=s("span","flatpickr-time-separator",":"),n=m("flatpickr-hour",{"aria-label":w.l10n.hourAriaLabel});w.hourElement=n.getElementsByTagName("input")[0];var a=m("flatpickr-minute",{"aria-label":w.l10n.minuteAriaLabel});w.minuteElement=a.getElementsByTagName("input")[0],w.hourElement.tabIndex=w.minuteElement.tabIndex=-1,w.hourElement.value=o(w.latestSelectedDateObj?w.latestSelectedDateObj.getHours():w.config.time_24hr?e.hours:function(e){switch(e%24){case 0:case 12:return 12;default:return e%12}}(e.hours)),w.minuteElement.value=o(w.latestSelectedDateObj?w.latestSelectedDateObj.getMinutes():e.minutes),w.hourElement.setAttribute("step",w.config.hourIncrement.toString()),w.minuteElement.setAttribute("step",w.config.minuteIncrement.toString()),w.hourElement.setAttribute("min",w.config.time_24hr?"0":"1"),w.hourElement.setAttribute("max",w.config.time_24hr?"23":"12"),w.hourElement.setAttribute("maxlength","2"),w.minuteElement.setAttribute("min","0"),w.minuteElement.setAttribute("max","59"),w.minuteElement.setAttribute("maxlength","2"),w.timeContainer.appendChild(n),w.timeContainer.appendChild(t),w.timeContainer.appendChild(a),w.config.time_24hr&&w.timeContainer.classList.add("time24hr");if(w.config.enableSeconds){w.timeContainer.classList.add("hasSeconds");var i=m("flatpickr-second");w.secondElement=i.getElementsByTagName("input")[0],w.secondElement.value=o(w.latestSelectedDateObj?w.latestSelectedDateObj.getSeconds():e.seconds),w.secondElement.setAttribute("step",w.minuteElement.getAttribute("step")),w.secondElement.setAttribute("min","0"),w.secondElement.setAttribute("max","59"),w.secondElement.setAttribute("maxlength","2"),w.timeContainer.appendChild(s("span","flatpickr-time-separator",":")),w.timeContainer.appendChild(i)}w.config.time_24hr||(w.amPM=s("span","flatpickr-am-pm",w.l10n.amPM[r((w.latestSelectedDateObj?w.hourElement.value:w.config.defaultHour)>11)]),w.amPM.title=w.l10n.toggleTitle,w.amPM.tabIndex=-1,w.timeContainer.appendChild(w.amPM));return w.timeContainer}());d(w.calendarContainer,"rangeMode","range"===w.config.mode),d(w.calendarContainer,"animate",!0===w.config.animate),d(w.calendarContainer,"multiMonth",w.config.showMonths>1),w.calendarContainer.appendChild(e);var i=void 0!==w.config.appendTo&&void 0!==w.config.appendTo.nodeType;if((w.config.inline||w.config.static)&&(w.calendarContainer.classList.add(w.config.inline?"inline":"static"),w.config.inline&&(!i&&w.element.parentNode?w.element.parentNode.insertBefore(w.calendarContainer,w._input.nextSibling):void 0!==w.config.appendTo&&w.config.appendTo.appendChild(w.calendarContainer)),w.config.static)){var l=s("div","flatpickr-wrapper");w.element.parentNode&&w.element.parentNode.insertBefore(l,w.element),l.appendChild(w.element),w.altInput&&l.appendChild(w.altInput),l.appendChild(w.calendarContainer)}w.config.static||w.config.inline||(void 0!==w.config.appendTo?w.config.appendTo:window.document.body).appendChild(w.calendarContainer)}(),function(){w.config.wrap&&["open","close","toggle","clear"].forEach((function(e){Array.prototype.forEach.call(w.element.querySelectorAll("[data-"+e+"]"),(function(t){return A(t,"click",w[e])}))}));if(w.isMobile)return void function(){var e=w.config.enableTime?w.config.noCalendar?"time":"datetime-local":"date";w.mobileInput=s("input",w.input.className+" flatpickr-mobile"),w.mobileInput.tabIndex=1,w.mobileInput.type=e,w.mobileInput.disabled=w.input.disabled,w.mobileInput.required=w.input.required,w.mobileInput.placeholder=w.input.placeholder,w.mobileFormatStr="datetime-local"===e?"Y-m-d\\TH:i:S":"date"===e?"Y-m-d":"H:i:S",w.selectedDates.length>0&&(w.mobileInput.defaultValue=w.mobileInput.value=w.formatDate(w.selectedDates[0],w.mobileFormatStr));w.config.minDate&&(w.mobileInput.min=w.formatDate(w.config.minDate,"Y-m-d"));w.config.maxDate&&(w.mobileInput.max=w.formatDate(w.config.maxDate,"Y-m-d"));w.input.getAttribute("step")&&(w.mobileInput.step=String(w.input.getAttribute("step")));w.input.type="hidden",void 0!==w.altInput&&(w.altInput.type="hidden");try{w.input.parentNode&&w.input.parentNode.insertBefore(w.mobileInput,w.input.nextSibling)}catch(e){}A(w.mobileInput,"change",(function(e){w.setDate(g(e).value,!1,w.mobileFormatStr),pe("onChange"),pe("onClose")}))}();var e=l(ie,50);w._debouncedChange=l(N,300),w.daysContainer&&!/iPhone|iPad|iPod/i.test(navigator.userAgent)&&A(w.daysContainer,"mouseover",(function(e){"range"===w.config.mode&&ae(g(e))}));A(window.document.body,"keydown",ne),w.config.inline||w.config.static||A(window,"resize",e);void 0!==window.ontouchstart?A(window.document,"touchstart",Z):A(window.document,"mousedown",Z);A(window.document,"focus",Z,{capture:!0}),!0===w.config.clickOpens&&(A(w._input,"focus",w.open),A(w._input,"click",w.open));void 0!==w.daysContainer&&(A(w.monthNav,"click",Ce),A(w.monthNav,["keyup","increment"],F),A(w.daysContainer,"click",ue));if(void 0!==w.timeContainer&&void 0!==w.minuteElement&&void 0!==w.hourElement){var t=function(e){return g(e).select()};A(w.timeContainer,["increment"],I),A(w.timeContainer,"blur",I,{capture:!0}),A(w.timeContainer,"click",Y),A([w.hourElement,w.minuteElement],["focus","click"],t),void 0!==w.secondElement&&A(w.secondElement,"focus",(function(){return w.secondElement&&w.secondElement.select()})),void 0!==w.amPM&&A(w.amPM,"click",(function(e){I(e),N()}))}w.config.allowInput&&A(w._input,"blur",te)}(),(w.selectedDates.length||w.config.noCalendar)&&(w.config.enableTime&&_(w.config.noCalendar?w.latestSelectedDateObj:void 0),be(!1)),k();var t=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);!w.isMobile&&t&&ce(),pe("onReady")}(),w}function k(e,t){for(var n=Array.prototype.slice.call(e).filter((function(e){return e instanceof HTMLElement})),a=[],i=0;i Date: Wed, 10 Mar 2021 13:02:36 +0100 Subject: [PATCH 002/172] Preview instance broadcast before sending --- www/admin/app-admin.less | 3 +++ www/admin/inner.js | 39 +++++++++++++++++++++++------ www/common/notifications.js | 3 ++- www/common/sframe-common-mailbox.js | 7 +++++- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index 64f977eca..2875c0a27 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -223,6 +223,9 @@ margin-right: 20px; } } + .cp-broadcast-preview { + vertical-align: bottom !important; + } } } diff --git a/www/admin/inner.js b/www/admin/inner.js index 7d260e681..b6d1988e5 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -973,10 +973,36 @@ define([ UI.log(Messages.saved); }); }; + $button.click(function () { send(); }); + var onPreview = function (l) { + var data = getData(); + if (data === false) { return void UI.warn(Messages.error); } + var msg = { + uid: Util.uid(), + type: 'BROADCAST_'+key.toUpperCase(), + content: data + }; + common.mailbox.onMessage({ + lang: l, + type: 'broadcast', + content: { + msg: msg, + hash: 'LOCAL|' + JSON.stringify(msg).slice(0,58) + } + }, function () { + UI.log(Messages.saved); + }); + }; + var preview = h('button.cp-broadcast-preview.btn.btn-secondary', Messages.share_linkOpen); + $(preview).click(function () { + onPreview(); + }); + + if (key === 'custom') { (function () { var container = h('div.cp-broadcast-container'); @@ -984,10 +1010,6 @@ define([ var languages = Messages._languages; var keys = Object.keys(languages).sort(); - var onPreview = function (l) { -// XXX - }; - var reorder = function () { $container.find('.cp-broadcast-lang').each(function (i, el) { var $el = $(el); @@ -1117,7 +1139,8 @@ define([ h('label', Messages.broadcast_end), end, h('br'), - button + button, + preview ]); })(); return; @@ -1139,7 +1162,8 @@ define([ $form.append([ $cbox[0], h('br'), - button + button, + preview ]); })(); return; @@ -1166,7 +1190,8 @@ define([ label, input, h('br'), - button + button, + preview ]); })(); return; diff --git a/www/common/notifications.js b/www/common/notifications.js index 93da2d9b9..bc7e353da 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -454,8 +454,9 @@ define([ var msg = content.msg.content; var text = msg.content; var defaultL = msg.defaultLanguage; + var myLang = data.lang || Messages._languageUsed; // Check if our language is available - var toShow = text[Messages._languageUsed]; + var toShow = text[myLang]; // Otherwise, fallback to the default language if it exists if (!toShow && defaultL) { toShow = text[defaultL]; } // No translation available, dismiss diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 5f35b5a2c..174777449 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -142,7 +142,7 @@ define([ removeFromHistory(data.type, data.hash); }; - var onMessage = function (data, cb) { + var onMessage = mailbox.onMessage = function (data, cb) { // data = { type: 'type', content: {msg: 'msg', hash: 'hash'} } pushMessage(data); if (data.content && typeof (data.content.getFormatText) === "function") { @@ -160,6 +160,11 @@ define([ hash: data.content.hash, type: data.type }; + if (/^LOCAL|/.test(dataObj.hash)) { + onViewed(dataObj); + cb(); + return; + } execCommand('DISMISS', dataObj, function (obj) { if (obj && obj.error) { return void cb(obj.error); } onViewed(dataObj); From 171d00a943003281aaa266e1f26f60fe29974a72 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 11 Mar 2021 12:27:12 +0100 Subject: [PATCH 003/172] Broadcast deletion (manual and automatic) --- .../src/less2/include/notifications.less | 13 +++ www/admin/app-admin.less | 24 ++++++ www/admin/inner.js | 82 +++++++++++++++++-- www/common/outer/mailbox-handlers.js | 65 +++++++++++++++ www/common/outer/mailbox.js | 22 ++++- www/common/sframe-common-mailbox.js | 35 +++++++- www/common/sframe-common.js | 8 ++ 7 files changed, 237 insertions(+), 12 deletions(-) diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less index 46209eb6a..133ee2d2a 100644 --- a/customize.dist/src/less2/include/notifications.less +++ b/customize.dist/src/less2/include/notifications.less @@ -17,6 +17,19 @@ .cp-notification { min-height: @notif-height; display: flex; + .cp-broadcast { + display: flex; + img { + width: 30px; + } + padding: 0 5px; + &.admin { + cursor: pointer; + &:hover { + background-color: @cp_dropdown-bg-hover; + } + } + } .cp-avatar { .avatar_main(30px); padding: 0 5px; diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index 2875c0a27..bf63e53c3 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -226,6 +226,30 @@ .cp-broadcast-preview { vertical-align: bottom !important; } + .cp-broadcast-delete { + width: 100%; + min-width: 600px; + .cp-notification { + display: flex; + align-items: center; + .cp-avatar, .cp-broadcast, .cp-notification-dismiss { + display: none; + } + p { + margin: 0 !important; + } + .cp-notification-content { + width: 100%; + padding: 10px; + } + .cp-clickable { + cursor: pointer; + &:hover { + background-color: @cp_dropdown-bg-hover; + } + } + } + } } } diff --git a/www/admin/inner.js b/www/admin/inner.js index b6d1988e5..42e3e4123 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -944,12 +944,17 @@ define([ Messages.broadcast_survey = 'survey'; // XXX Messages.broadcast_version = 'version'; // XXX Messages.broadcast_custom = 'custom'; // XXX + Messages.broadcast_delete = 'delete'; // XXX Messages.broadcast_newVersionReload = 'Force a worker reload on all clients'; // XXX Messages.broadcast_surveyURL = 'Survey URL'; Messages.broadcast_translations = 'Translations'; - Messages.broadcast_defaultLanguage = 'Default language'; + Messages.broadcast_defaultLanguage = 'Fallback to this language (optional)'; Messages.broadcast_start = 'Start time'; Messages.broadcast_end = 'End time'; + Messages.broadcast_preview = "Preview in a fake notification"; + Messages.broadcast_setLKH = "Mark as latest"; + Messages.broadcast_deleteBtn = "Delete for all"; + Messages.broadcast_reset = "Reset my visible messages"; var getBroadcastForm = function ($form, key) { $form.empty(); @@ -966,6 +971,7 @@ define([ var data = getData(); if (data === false) { return void UI.warn(Messages.error); } $button.prop('disabled', 'disabled'); + data.time = +new Date(); common.mailbox.sendTo('BROADCAST_'+key.toUpperCase(), data, {}, function (err) { $button.prop('disabled', ''); if (err) { return UI.warn(Messages.error); } @@ -997,7 +1003,7 @@ define([ UI.log(Messages.saved); }); }; - var preview = h('button.cp-broadcast-preview.btn.btn-secondary', Messages.share_linkOpen); + var preview = h('button.cp-broadcast-preview.btn.btn-secondary', Messages.broadcast_preview); $(preview).click(function () { onPreview(); }); @@ -1022,7 +1028,7 @@ define([ }; var addLang = function (l) { if ($container.find('.cp-broadcast-lang[data-lang="'+l+'"]').length) { return; } - var preview = h('button.btn.btn-secondary', Messages.share_linkOpen); + var preview = h('button.btn.btn-secondary', Messages.broadcast_preview); $(preview).click(function () { onPreview(l); }); @@ -1197,12 +1203,74 @@ define([ return; } + if (key === 'delete') { + (function () { + var table = h('table.cp-broadcast-delete'); + var $table = $(table); + common.mailbox.subscribe(["broadcast"], { + onMessage: function (data, el) { + if (Util.find(data, ['content', 'msg', 'type']) === 'BROADCAST_DELETE') { + var _uid = Util.find(data, ['content', 'msg', 'content', 'uid']); + var $button = $table.find('[data-uid="'+_uid+'"] td.delete button'); + $button.prop('disabled', 'disabled').text(Messages.deleted); + return; + } + var uid = Util.find(data, ['content', 'msg', 'uid']); + var time = Util.find(data, ['content', 'msg', 'content', 'time']); + var setLKHBtn = h('button.btn.btn-secondary', Messages.broadcast_setLKH); + var deleteBtn = h('button.btn.btn-danger', Messages.broadcast_deleteBtn); + $(setLKHBtn).click(function () { + // XXX + }); + var tr = h('tr', { 'data-uid': uid }, [ + h('td', 'ID: '+uid), + h('td', new Date(time || 0).toLocaleString()), + h('td', el), + h('td', setLKHBtn), + h('td.delete', deleteBtn), + ]); + + UI.confirmButton(deleteBtn, { + classes: 'btn-danger', + multiple: true + }, function () { + getData = function () { + if (!uid) { return false; } + return { uid: uid }; + }; + reset = function () { + $(deleteBtn).prop('disabled', 'disabled').text(Messages.deleted); + }; + send(); + }); + + $table.append(tr); + }, + history: true // won't receive new messages: not a "subscription" + }); + + var resetMine = h('button.btn.btn-primary', Messages.broadcast_reset); + UI.confirmButton(resetMine, {}, function () { + common.mailbox.reset('broadcast', function () { + // XXX + console.error(arguments); + }); + }); + + $form.append([ + resetMine, + table + ]); + })(); + return; + } + }; create['broadcast'] = function () { var key = 'broadcast'; var $div = makeBlock(key); - var form = h('div.cp-admin-broadcast-form') + var form = h('div.cp-admin-broadcast-form'); var $select = $(h('div.cp-dropdown-container')).appendTo($div); var $form = $(form).appendTo($div); @@ -1210,7 +1278,8 @@ define([ 'maintenance', 'survey', 'version', - 'custom' + 'custom', + 'delete' ]; categories = categories.map(function (key) { return { @@ -1400,8 +1469,7 @@ define([ var privateData = metadataMgr.getPrivateData(); common.setTabTitle(Messages.adminPage || 'Administration'); - if (!privateData.edPublic || !ApiConfig.adminKeys || !Array.isArray(ApiConfig.adminKeys) - || ApiConfig.adminKeys.indexOf(privateData.edPublic) === -1) { + if (!common.isAdmin()) { return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden'); } diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 3a39e4ce7..f200ed387 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -686,6 +686,71 @@ define([ }; + // Broadcast + var broadcasts = {}; + handlers['BROADCAST_MAINTENANCE'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + if (content.end < (+new Date())) { + // Expired maintenance: dismiss + return void cb(true); + } + var uid = msg.uid; + broadcasts[uid] = { + type: box.type, + hash: data.hash + }; + cb(false); + }; + handlers['BROADCAST_VERSION'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + if (!box.ready) { + // This is an old version message: dismiss + return void cb(true); + } + if (content.reload) { + // We're going to force a disconnect, dismiss + // XXX + return void cb(true); + } + var uid = msg.uid; + broadcasts[uid] = { + type: box.type, + hash: data.hash + }; + cb(false); + }; + handlers['BROADCAST_SURVEY'] = function (ctx, box, data, cb) { + var msg = data.msg; + var uid = msg.uid; + broadcasts[uid] = { + type: box.type, + hash: data.hash + }; + cb(false); + }; + handlers['BROADCAST_CUSTOM'] = function (ctx, box, data, cb) { + var msg = data.msg; + var uid = msg.uid; + broadcasts[uid] = { + type: box.type, + hash: data.hash + }; + cb(false); + }; + handlers['BROADCAST_DELETE'] = function (ctx, box, data, cb) { + var msg = data.msg; + var content = msg.content; + var uid = content.uid; // uid of the message to delete + if (!broadcasts[uid]) { + // We don't have this message in memory, nothing to delete + return void cb(true); + } + cb(false, broadcasts[uid]); + delete broadcasts[uid]; + }; + return { add: function (ctx, box, data, cb) { /** diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 680eddd73..7b91938c9 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -464,6 +464,21 @@ proxy.mailboxes = { }); }; + var resetBox = function (ctx, cId, type, cb) { + var box = ctx.mailboxes && ctx.mailboxes[type]; + if (!box) { return void cb({error: 'ENOENT'}); } + + console.log(box); + if (type === 'broadcast') { + box.viewed = []; + box.lastKnownHash = ''; // XXX Use api/broadcast + return void cb(); + } + + box.lastKnownHash = ''; + box.viewed = []; + }; + var subscribe = function (ctx, data, cId, cb) { // Get existing notifications Object.keys(ctx.boxes).forEach(function (type) { @@ -491,19 +506,21 @@ proxy.mailboxes = { Mailbox.init = function (cfg, waitFor, emit) { var mailbox = {}; var store = cfg.store; + var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {}; + var ctx = { Store: cfg.Store, store: store, pinPads: cfg.pinPads, updateMetadata: cfg.updateMetadata, updateDrive: cfg.updateDrive, + mailboxes: mailboxes, emit: emit, clients: [], boxes: {}, req: {} }; - var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {}; initializeMailboxes(ctx, mailboxes); initializeHistory(ctx); @@ -580,6 +597,9 @@ proxy.mailboxes = { if (cmd === 'LOAD_HISTORY') { return void loadHistory(ctx, clientId, data, cb); } + if (cmd === 'RESET') { + return void resetBox(ctx, clientId, data, cb); + } }; return mailbox; diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 174777449..3e711265c 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -1,13 +1,16 @@ define([ 'jquery', + '/api/config', '/common/common-util.js', '/common/common-hash.js', '/common/common-interface.js', '/common/common-ui-elements.js', '/common/notifications.js', '/common/hyperscript.js', + '/common/clipboard.js', '/customize/messages.js', -], function ($, Util, Hash, UI, UIElements, Notifications, h, Messages) { +], function ($, ApiConfig, Util, Hash, UI, UIElements, Notifications, h, + Clipboard, Messages) { var Mailbox = {}; Mailbox.create = function (Common) { @@ -56,7 +59,24 @@ define([ var notif; var avatar; var userData = Util.find(data, ['content', 'msg', 'content', 'user']); - if (userData && typeof(userData) === "object" && userData.profile) { + + if (Util.find(data, ['content', 'msg', 'type']) === 'BROADCAST_DELETE') { + return; + } + if (data.type === 'broadcast') { + var urlArgs = Util.find(ApiConfig, ['requireConf', 'urlArgs']) || ''; + var adminCls = Common.isAdmin() ? '.admin' : ''; + avatar = h('span.cp-broadcast'+adminCls, h('img', { + src: '/customize/CryptPad_logo.svg?' + urlArgs, + title: adminCls ? 'Copy UID' : '' // XXX + })); + if (adminCls) { + $(avatar).click(function () { + var success = Clipboard.copy(Util.find(data, ['content', 'msg', 'uid'])); + if (success) { UI.log(Messages.shareSuccess); } + }); + } + } else if (userData && typeof(userData) === "object" && userData.profile) { avatar = h('span.cp-avatar'); Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name); $(avatar).click(function (e) { @@ -160,7 +180,7 @@ define([ hash: data.content.hash, type: data.type }; - if (/^LOCAL|/.test(dataObj.hash)) { + if (/^LOCAL\|/.test(dataObj.hash)) { onViewed(dataObj); cb(); return; @@ -188,7 +208,7 @@ define([ cfg.onViewed(data); }); } - if (typeof(cfg.onMessage) === "function") { + if (typeof(cfg.onMessage) === "function" && !cfg.history) { onMessageHandlers.push(function (data, el) { var type = data.type; if (types.indexOf(type) === -1 && !(teams && /^team-/.test(type))) { return; } @@ -206,6 +226,13 @@ define([ }); }; + mailbox.reset = function (type, cb) { + if (!type) { return; } + execCommand('RESET', type, function (obj) { + cb(obj); + }); + }; + var historyState = false; var onHistory = function () {}; mailbox.getMoreHistory = function (type, count, lastKnownHash, cb) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index c46a0641d..1036f84d3 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -1,5 +1,6 @@ define([ 'jquery', + '/api/config', '/bower_components/nthen/index.js', '/customize/messages.js', '/common/sframe-chainpad-netflux-inner.js', @@ -26,6 +27,7 @@ define([ '/bower_components/localforage/dist/localforage.min.js' ], function ( $, + ApiConfig, nThen, Messages, CpNfInner, @@ -703,6 +705,12 @@ define([ }); }; + funcs.isAdmin = function () { + var privateData = ctx.metadataMgr.getPrivateData(); + return privateData.edPublic && Array.isArray(ApiConfig.adminKeys) && + ApiConfig.adminKeys.indexOf(privateData.edPublic) !== -1; + }; + funcs.mailbox = {}; Object.freeze(funcs); From 3858d68a0a95873865c012bb43d7884020ce5474 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 11 Mar 2021 13:27:06 +0100 Subject: [PATCH 004/172] Reload worker on new version --- www/common/cryptpad-common.js | 4 ++++ www/common/outer/async-store.js | 14 ++++++++++++++ www/common/outer/mailbox-handlers.js | 4 ++-- www/common/outer/sharedworker.js | 14 ++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 68e341562..0cc25b684 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2129,6 +2129,10 @@ define([ common.onNetworkReconnect.fire(data); }); }, + FORCE_RELOAD: function () { + // XXX not used + common.onNewVersionReconnect.fire(); + }, // OnlyOffice OO_EVENT: common.onlyoffice.onEvent.fire, // Mailbox diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index bc857be7f..27d9e1997 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2990,6 +2990,20 @@ define([ }); }; + Store.newVersionReload = function () { + broadcast([], "NETWORK_RECONNECT"); + + /* // XXX NETWORK_RECONNECT only works when a manual /api/config is used + // XXX The following code disconnect all tabs and asks for a page reload BUT + // if the urlArgs has not changed, new tabs will stay on the same DISCONNECTED worker + // XXX One solution is to change the FRESH mode token before sending the newVersion message + Store.disconnect(); + broadcast([], "FORCE_RELOAD"); + if (self.CP_closeWorker) { + setTimeout(self.CP_closeWorker, 200); + } + */ + }; Store.disconnect = function () { if (self.accountDeletion) { return; } if (!store.network) { return; } diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index f200ed387..6190e00e6 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -711,8 +711,8 @@ define([ } if (content.reload) { // We're going to force a disconnect, dismiss - // XXX - return void cb(true); + ctx.Store.newVersionReload(); + return; // This message will be removed when reloading the worker } var uid = msg.uid; broadcasts[uid] = { diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js index 825deb383..21b80e1d5 100644 --- a/www/common/outer/sharedworker.js +++ b/www/common/outer/sharedworker.js @@ -9,6 +9,20 @@ localStorage = { self.tabs = {}; +// XXX Not used for now +self.CP_closeWorker = function () { + Object.keys(self.tabs).forEach(function (id) { + var obj = self.tabs[id]; + if (obj.port) { return; } + console.error(id); + try { + obj.port.close(); + } catch (e) { + console.error(e); + } + }); +}; + var postMsg = function (client, data) { client.port.postMessage(data); }; From d15c0461cc47fe42f35d0827d208a7b0288bfff9 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 12:46:11 +0100 Subject: [PATCH 005/172] Add /api/broadcast and improve message deletion --- lib/decrees.js | 20 +++ lib/env.js | 4 + server.js | 47 +++++++ www/admin/app-admin.less | 3 + www/admin/inner.js | 202 +++++++++++++++++++-------- www/common/notifications.js | 3 +- www/common/outer/async-store.js | 3 +- www/common/outer/mailbox-handlers.js | 11 +- www/common/outer/mailbox.js | 70 ++++++---- www/common/sframe-common-mailbox.js | 15 +- 10 files changed, 277 insertions(+), 101 deletions(-) diff --git a/lib/decrees.js b/lib/decrees.js index 2672efdd3..bd539e271 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -24,6 +24,9 @@ SET_PREMIUM_UPLOAD_SIZE DISABLE_INTEGRATED_TASKS DISABLE_INTEGRATED_EVICTION +// BROADCAST +SET_LAST_BROADCAST_HASH + NOT IMPLEMENTED: // RESTRICTED REGISTRATION @@ -121,6 +124,23 @@ commands.SET_ARCHIVE_RETENTION_TIME = makeIntegerSetter('archiveRetentionTime'); // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_ACCOUNT_RETENTION_TIME', [365]]], console.log) commands.SET_ACCOUNT_RETENTION_TIME = makeIntegerSetter('accountRetentionTime'); +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_LAST_BROADCAST_HASH', [hash]]], console.log) +commands.SET_LAST_BROADCAST_HASH = function (Env, args) { + if (!Array.isArray(args) || typeof(args[0]) !== "string") { + throw new Error('INVALID_ARGS'); + } + if (args[0] && args[0].length !== 64) { + throw new Error('INVALID_ARGS'); + } + var hash = args[0]; + if (hash === Env.lastBroadcastHash) { return false; } + + // Hash is valid and has changed: update it and clear the broadcast cache + Env.lastBroadcastHash = hash; + Env.broadcastCache = {}; + return true; +}; + var Quota = require("./commands/quota"); var Keys = require("./keys"); var Util = require("./common-util"); diff --git a/lib/env.js b/lib/env.js index 97d3893f9..285f2906a 100644 --- a/lib/env.js +++ b/lib/env.js @@ -19,8 +19,10 @@ module.exports.create = function (config) { FRESH_MODE: true, DEV_MODE: false, configCache: {}, + broadcastCache: {}, flushCache: function () { Env.configCache = {}; + Env.broadcastCache = {}; Env.FRESH_KEY = +new Date(); if (!(Env.DEV_MODE || Env.FRESH_MODE)) { Env.FRESH_MODE = true; } if (!Env.Log) { return; } @@ -65,6 +67,8 @@ module.exports.create = function (config) { paths: {}, //msgStore: config.store, + lastBroadcastHash: '', + netfluxUsers: {}, pinStore: undefined, diff --git a/server.js b/server.js index f3ee71d0b..5bf30f05e 100644 --- a/server.js +++ b/server.js @@ -109,6 +109,7 @@ var setHeaders = (function () { // Don't set CSP headers on /api/config because they aren't necessary and they cause problems // when duplicated by NGINX in production environments + // XXX /api/broadcast too? if (/^\/api\/config/.test(req.url)) { return; } // targeted CSP, generic policies, maybe custom headers const h = [ @@ -268,7 +269,53 @@ var serveConfig = (function () { }; }()); +var serveBroadcast = (function () { + var cacheString = function () { + return (Env.FRESH_KEY? '-' + Env.FRESH_KEY: '') + (Env.DEV_MODE? '-' + (+new Date()): ''); + }; + + var template = function (host) { + return [ + 'define(function(){', + 'return ' + JSON.stringify({ + lastBroadcastHash: Env.lastBroadcastHash + }, null, '\t'), + '});' + ].join(';\n') + }; + + var cleanUp = {}; + + return function (req, res) { + var host = req.headers.host.replace(/\:[0-9]+/, ''); + res.setHeader('Content-Type', 'text/javascript'); + // don't cache anything if you're in dev mode + if (Env.DEV_MODE) { + return void res.send(template(host)); + } + // generate a lookup key for the cache + var cacheKey = host + ':' + cacheString(); + + // XXX do we need a cache for /api/broadcast? + if (!Env.broadcastCache[cacheKey]) { + // generate the response and cache it in memory + Env.broadcastCache[cacheKey] = template(host); + // and create a function to conditionally evict cache entries + // which have not been accessed in the last 20 seconds + cleanUp[cacheKey] = Util.throttle(function () { + delete cleanUp[cacheKey]; + delete Env.broadcastCache[cacheKey]; + }, 20000); + } + + // successive calls to this function + cleanUp[cacheKey](); + return void res.send(Env.broadcastCache[cacheKey]); + }; +}()); + app.get('/api/config', serveConfig); +app.get('/api/broadcast', serveBroadcast); var four04_path = Path.resolve(__dirname + '/customize.dist/404.html'); var custom_four04_path = Path.resolve(__dirname + '/customize/404.html'); diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index bf63e53c3..c85994796 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -229,6 +229,9 @@ .cp-broadcast-delete { width: 100%; min-width: 600px; + .empty { + font-style: italic; + } .cp-notification { display: flex; align-items: center; diff --git a/www/admin/inner.js b/www/admin/inner.js index 42e3e4123..217c5eba6 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -1,6 +1,7 @@ define([ 'jquery', '/api/config', + '/customize/application_config.js', '/bower_components/chainpad-crypto/crypto.js', '/common/toolbar.js', '/bower_components/nthen/index.js', @@ -23,6 +24,7 @@ define([ ], function ( $, ApiConfig, + AppConfig, Crypto, Toolbar, nThen, @@ -937,7 +939,7 @@ define([ return; }; - // Messages.admin_cat_broadcast // XXX + Messages.admin_cat_broadcast = "Broadcast" // XXX // Messages.admin_broadcastHint // XXX // Messages.admin_broadcastTitle // XXX Messages.broadcast_maintenance = 'maintenance';// XXX @@ -952,9 +954,10 @@ define([ Messages.broadcast_start = 'Start time'; Messages.broadcast_end = 'End time'; Messages.broadcast_preview = "Preview in a fake notification"; - Messages.broadcast_setLKH = "Mark as latest"; Messages.broadcast_deleteBtn = "Delete for all"; - Messages.broadcast_reset = "Reset my visible messages"; + Messages.broadcast_clear = "Clear all for everybody"; + Messages.expired = "Expired"; + Messages.broadcast_empty = "No active message"; var getBroadcastForm = function ($form, key) { $form.empty(); @@ -967,16 +970,28 @@ define([ var button = h('button.btn.btn-primary', Messages.support_formButton); var $button = $(button); - var send = function () { + var send = function (_cb) { + var cb = Util.once(_cb || function () {}); var data = getData(); - if (data === false) { return void UI.warn(Messages.error); } + if (data === false) { + cb('NODATA'); + return void UI.warn(Messages.error); + } $button.prop('disabled', 'disabled'); data.time = +new Date(); - common.mailbox.sendTo('BROADCAST_'+key.toUpperCase(), data, {}, function (err) { + common.mailbox.sendTo('BROADCAST_'+key.toUpperCase(), data, {}, function (err, data) { $button.prop('disabled', ''); - if (err) { return UI.warn(Messages.error); } + cb(err, data); + if (err) { + console.error(err); + return UI.warn(Messages.error); + } + + // Clear the UI reset(); - UI.log(Messages.saved); + + // Only print success if there is no callback + if (!_cb) { UI.log(Messages.saved); } }); }; @@ -1011,11 +1026,13 @@ define([ if (key === 'custom') { (function () { + // Custom message var container = h('div.cp-broadcast-container'); var $container = $(container); var languages = Messages._languages; var keys = Object.keys(languages).sort(); + // Always keep the textarea ordered by language code var reorder = function () { $container.find('.cp-broadcast-lang').each(function (i, el) { var $el = $(el); @@ -1023,9 +1040,11 @@ define([ $el.css('order', keys.indexOf(l)); }); }; + // Remove a textarea var removeLang = function (l) { $container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove(); }; + // Add a textarea var addLang = function (l) { if ($container.find('.cp-broadcast-lang[data-lang="'+l+'"]').length) { return; } var preview = h('button.btn.btn-secondary', Messages.broadcast_preview); @@ -1033,6 +1052,8 @@ define([ onPreview(l); }); var bcastDefault = Messages.broadcast_defaultLanguage; + // XXX + //var first = !$container.find('.cp-broadcast-lang').length; $container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [ h('h4', languages[l]), h('label', Messages.kanban_body), @@ -1046,7 +1067,7 @@ define([ reorder(); }; - + // Checkboxes to select translations var boxes = keys.map(function (l) { var $cbox = $(UI.createCheckbox('cp-broadcast-custom-lang-'+l, languages[l], false, { label: { class: 'noTitle' } })); @@ -1063,6 +1084,7 @@ define([ return $cbox[0]; }); + // Extract form data getData = function () { var map = {}; var defaultLanguage; @@ -1091,11 +1113,13 @@ define([ content: map }; }; + // Clear all the textarea when sent reset = function () { $container.find('.cp-broadcast-lang textarea').each(function (i, el) { $(el).val(''); }); }; + // Make the form $form.append([ h('label', Messages.broadcast_translations), h('div.cp-broadcast-languages', boxes), @@ -1107,11 +1131,13 @@ define([ } if (key === 'maintenance') { (function () { + // Maintenance message + + // Start and end date pickers var start = h('input'); var end = h('input'); var $start = $(start); var $end = $(end); - var endPickr = Flatpickr(end, { enableTime: true, minDate: new Date() @@ -1123,6 +1149,8 @@ define([ endPickr.set('minDate', new Date($start.val())); } }); + + // Extract form data getData = function () { var start = +new Date($start.val()); var end = +new Date($start.val()); @@ -1135,6 +1163,8 @@ define([ end: end }; }; + + // Clear when sent reset = function () { $start.val(''); $end.val(''); @@ -1153,10 +1183,16 @@ define([ } if (key === 'version') { (function () { + // New version available message + + // This checkbox can be used to trigger a fake "reconnect" event on the clients + // so that they can check api/config and reload the worker in case of a new version var $cbox = $(UI.createCheckbox('cp-admin-version-reload', Messages.broadcast_newVersionReload, false, { label: { class: 'noTitle' } })); var $checkbox = $cbox.find('input'); + + // Extract the data and make the form getData = function () { return { reload: $checkbox.is(':checked') @@ -1176,6 +1212,8 @@ define([ } if (key === 'survey') { (function () { + // New survey message + // TODO send different URLs for other languages? var label = h('label', Messages.broadcast_surveyURL); var input = h('input'); var $input = $(input); @@ -1204,64 +1242,110 @@ define([ } if (key === 'delete') { - (function () { + // Delete form + require(['/api/broadcast?'+ (+new Date())], function (BCast) { + + // Always display the messages from the instance "lastBroadcastHash" + var hash = BCast.lastBroadcastHash || '1'; // Truthy value if no lastKnownHash + common.mailbox.getNotificationsHistory('broadcast', null, hash, function (e, msgs) { var table = h('table.cp-broadcast-delete'); var $table = $(table); - common.mailbox.subscribe(["broadcast"], { - onMessage: function (data, el) { - if (Util.find(data, ['content', 'msg', 'type']) === 'BROADCAST_DELETE') { - var _uid = Util.find(data, ['content', 'msg', 'content', 'uid']); - var $button = $table.find('[data-uid="'+_uid+'"] td.delete button'); - $button.prop('disabled', 'disabled').text(Messages.deleted); - return; + + // Empty history + if (!msgs.length) { + $table.append(h('tr', h('td.empty', Messages.broadcast_empty))); + } + + // Build the table + msgs.forEach(function (data) { + var el = common.mailbox.createElement(data); + var t = Util.find(data, ['content', 'msg', 'type']); + + // A "DELETE" message is here to disable a previous line + if (t === 'BROADCAST_DELETE') { + var _uid = Util.find(data, ['content', 'msg', 'content', 'uid']); + var $button = $table.find('[data-uid="'+_uid+'"] td.delete button'); + $button.prop('disabled', 'disabled').text(Messages.deleted); + return; + } + + // Make the line + var uid = Util.find(data, ['content', 'msg', 'uid']); + var hash = Util.find(data, ['content', 'hash']); + var time = Util.find(data, ['content', 'msg', 'content', 'time']); + var deleteBtn = h('button.btn.btn-danger', Messages.broadcast_deleteBtn); + var tr = h('tr', { 'data-uid': uid }, [ + h('td', 'ID: '+uid), + h('td', new Date(time || 0).toLocaleString()), + h('td', el), + h('td.delete', deleteBtn), + ]); + + // Auto-expire maintenance and survey messages + if (t === 'BROADCAST_MAINTENANCE') { + var end = Util.find(data, ['content', 'msg', 'content', 'end']); + if (end < +new Date()) { + $(deleteBtn).prop('disabled', 'disabled').text(Messages.expired); } - var uid = Util.find(data, ['content', 'msg', 'uid']); - var time = Util.find(data, ['content', 'msg', 'content', 'time']); - var setLKHBtn = h('button.btn.btn-secondary', Messages.broadcast_setLKH); - var deleteBtn = h('button.btn.btn-danger', Messages.broadcast_deleteBtn); - $(setLKHBtn).click(function () { - // XXX - }); - var tr = h('tr', { 'data-uid': uid }, [ - h('td', 'ID: '+uid), - h('td', new Date(time || 0).toLocaleString()), - h('td', el), - h('td', setLKHBtn), - h('td.delete', deleteBtn), - ]); + } + if (t === 'BROADCAST_VERSION') { + $(deleteBtn).prop('disabled', 'disabled').text(Messages.expired); + } - UI.confirmButton(deleteBtn, { - classes: 'btn-danger', - multiple: true - }, function () { - getData = function () { - if (!uid) { return false; } - return { uid: uid }; - }; - reset = function () { - $(deleteBtn).prop('disabled', 'disabled').text(Messages.deleted); - }; - send(); - }); + // "Delete this message" button + UI.confirmButton(deleteBtn, { + classes: 'btn-danger', + multiple: true + }, function () { + getData = function () { + if (!uid) { return false; } + return { uid: uid }; + }; + reset = function () { + $(deleteBtn).prop('disabled', 'disabled').text(Messages.deleted); + }; + send(); + }); - $table.append(tr); - }, - history: true // won't receive new messages: not a "subscription" + $table.append(tr); }); - var resetMine = h('button.btn.btn-primary', Messages.broadcast_reset); - UI.confirmButton(resetMine, {}, function () { - common.mailbox.reset('broadcast', function () { - // XXX - console.error(arguments); + // Clear all button: remove all the messages and bump lastBroadcastHash + var clearAll = h('button.btn.btn-danger', Messages.broadcast_clear); + UI.confirmButton(clearAll, { + classes: 'btn-danger', + multiple: true + }, function () { + getData = function () { + return { all: true }; + }; + reset = function () {}; + + // Send a message to all users telling them to wipe the broadcast mailbox + // and on success, send an admin decree to update /api/broadcast + send(function (err, obj) { + if (err) { return; } + if (!obj || !obj.hash) { return; } + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'ADMIN_DECREE', + data: ['SET_LAST_BROADCAST_HASH', [obj.hash]] + }, function (e) { + if (e) { + UI.warn(Messages.error); console.error(e); + return; + } + // On success, reload the "delete" tab + getBroadcastForm($form, key); + }); }); }); $form.append([ - resetMine, - table + table, + msgs.length ? clearAll : undefined ]); - })(); + }); + }); return; } @@ -1281,6 +1365,12 @@ define([ 'custom', 'delete' ]; + + // The "version" message only works if the instance is using a manual /api/config + // This is a custom setup for which our team won't provide support and is NOT + // recommended unless you know exactly what you're doing. + if (!AppConfig.customApiConfig) { categories.splice(2,1); } + categories = categories.map(function (key) { return { tag: 'a', diff --git a/www/common/notifications.js b/www/common/notifications.js index bc7e353da..5b850b1c4 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -460,7 +460,7 @@ define([ // Otherwise, fallback to the default language if it exists if (!toShow && defaultL) { toShow = text[defaultL]; } // No translation available, dismiss - if (!toShow) { defaultDismiss(common, data)(); } + if (!toShow) { return defaultDismiss(common, data)(); } var slice = toShow.length > 500; toShow = Util.fixHTML(toShow); @@ -492,6 +492,7 @@ define([ return { add: function(common, data) { + console.log(data); var type = data.content.msg.type; if (handlers[type]) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 27d9e1997..f19b11ad3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2996,7 +2996,8 @@ define([ /* // XXX NETWORK_RECONNECT only works when a manual /api/config is used // XXX The following code disconnect all tabs and asks for a page reload BUT // if the urlArgs has not changed, new tabs will stay on the same DISCONNECTED worker - // XXX One solution is to change the FRESH mode token before sending the newVersion message + // XXX ==> we should probably keep NETWORK_RECONNECT but keep this "version reload" only for us + // because other instances have to reload the server when a new version is deployed Store.disconnect(); broadcast([], "FORCE_RELOAD"); if (self.CP_closeWorker) { diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 6190e00e6..33c87617b 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -742,12 +742,21 @@ define([ handlers['BROADCAST_DELETE'] = function (ctx, box, data, cb) { var msg = data.msg; var content = msg.content; + + // If this is a "clear all" message, empty the box and update lkh + if (content.all) { + // 3rd argument of callback: clear the mailbox + return void cb(null, null, true); + } + var uid = content.uid; // uid of the message to delete if (!broadcasts[uid]) { // We don't have this message in memory, nothing to delete return void cb(true); } - cb(false, broadcasts[uid]); + + // We have the message in memory, remove it and don't keep the DELETE msg + cb(true, broadcasts[uid]); delete broadcasts[uid]; }; diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 7b91938c9..d365bbadb 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -1,5 +1,6 @@ define([ '/api/config', + '/api/broadcast', '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', @@ -8,7 +9,7 @@ define([ '/common/outer/mailbox-handlers.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', '/bower_components/chainpad-crypto/crypto.js', -], function (Config, Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) { +], function (Config, BCast, Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) { var Mailbox = {}; var TYPES = [ @@ -46,12 +47,10 @@ define([ if (!mailboxes['broadcast']) { mailboxes.broadcast = { channel: BROADCAST_CHAN, - lastKnownHash: '', // XXX load /api/brooadcast to set this hash + lastKnownHash: BCast.lastBroadcastHash, decrypted: true, viewed: [] }; - } else { - // XXX update lastKnownHash from /api/broadcast } }; @@ -159,7 +158,9 @@ proxy.mailboxes = { error: err, }); } - return void cb(); + return void cb({ + hash: ciphertext.slice(0,64) + }); }); }; @@ -331,7 +332,22 @@ proxy.mailboxes = { hash: hash }; var notify = box.ready; - Handlers.add(ctx, box, message, function (dismissed, toDismiss) { + Handlers.add(ctx, box, message, function (dismissed, toDismiss, setAsLKH) { + if (setAsLKH) { + // Update LKH + box.data.lastKnownHash = hash; + box.data.viewed = []; + + // Make sure we remove data about dismissed messages + Realtime.whenRealtimeSyncs(ctx.store.realtime, function () { + Object.keys(box.content).forEach(function (h) { + Handlers.remove(ctx, box, box.content[h], h); + delete box.content[h]; + hideMessage(ctx, type, h, ctx.clients); + }); + }); + return; + } if (toDismiss) { // List of other messages to remove dismiss(ctx, toDismiss, '', function () { console.log('Notification handled automatically'); @@ -422,12 +438,16 @@ proxy.mailboxes = { if (type === 'HISTORY_RANGE') { if (!Array.isArray(_msg)) { return; } var message; - try { - var decrypted = box.encryptor.decrypt(_msg[4]); - message = JSON.parse(decrypted.content); - message.author = decrypted.author; - } catch (e) { - console.log(e); + if (req.box.type === 'broadcast') { + message = Util.tryParse(_msg[4]); + } else { + try { + var decrypted = box.encryptor.decrypt(_msg[4]); + message = JSON.parse(decrypted.content); + message.author = decrypted.author; + } catch (e) { + console.log(e); + } } ctx.emit('HISTORY', { txid: txid, @@ -453,6 +473,13 @@ proxy.mailboxes = { txid: data.txid } ]; + if (data.type === 'broadcast') { + msg = [ 'GET_HISTORY_RANGE', box.channel, { + to: data.lastKnownHash, + txid: data.txid + } + ]; + } ctx.req[data.txid] = { cId: clientId, box: box @@ -464,21 +491,6 @@ proxy.mailboxes = { }); }; - var resetBox = function (ctx, cId, type, cb) { - var box = ctx.mailboxes && ctx.mailboxes[type]; - if (!box) { return void cb({error: 'ENOENT'}); } - - console.log(box); - if (type === 'broadcast') { - box.viewed = []; - box.lastKnownHash = ''; // XXX Use api/broadcast - return void cb(); - } - - box.lastKnownHash = ''; - box.viewed = []; - }; - var subscribe = function (ctx, data, cId, cb) { // Get existing notifications Object.keys(ctx.boxes).forEach(function (type) { @@ -521,7 +533,6 @@ proxy.mailboxes = { req: {} }; - initializeMailboxes(ctx, mailboxes); initializeHistory(ctx); @@ -597,9 +608,6 @@ proxy.mailboxes = { if (cmd === 'LOAD_HISTORY') { return void loadHistory(ctx, clientId, data, cb); } - if (cmd === 'RESET') { - return void resetBox(ctx, clientId, data, cb); - } }; return mailbox; diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index 3e711265c..c0ba2b3ff 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -42,10 +42,10 @@ define([ type: type, msg: content, user: user - }, function (err, obj) { - cb(err || (obj && obj.error), obj); - if (err || (obj && obj.error)) { - return void console.error(err || obj.error); + }, function (obj) { + cb(obj && obj.error, obj); + if (obj && obj.error) { + return void console.error(obj.error); } }); }; @@ -226,13 +226,6 @@ define([ }); }; - mailbox.reset = function (type, cb) { - if (!type) { return; } - execCommand('RESET', type, function (obj) { - cb(obj); - }); - }; - var historyState = false; var onHistory = function () {}; mailbox.getMoreHistory = function (type, count, lastKnownHash, cb) { From 43c07943436dc426c590b45fd0968375a0fb4f2a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 12:54:20 +0100 Subject: [PATCH 006/172] Improve Custom broadcast UI with default language --- www/admin/inner.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index 217c5eba6..7deed589a 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -958,6 +958,7 @@ define([ Messages.broadcast_clear = "Clear all for everybody"; Messages.expired = "Expired"; Messages.broadcast_empty = "No active message"; + Messages.broadcast_noFallback = "Don't fallback to a default language"; var getBroadcastForm = function ($form, key) { $form.empty(); @@ -1044,6 +1045,11 @@ define([ var removeLang = function (l) { $container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove(); }; + + var noFallbackBtn = h('button.btn.btn-secondary.cp-broadcast-preview', + Messages.broadcast_noFallback); + var $noFallbackBtn = $(noFallbackBtn); + // Add a textarea var addLang = function (l) { if ($container.find('.cp-broadcast-lang[data-lang="'+l+'"]').length) { return; } @@ -1052,16 +1058,19 @@ define([ onPreview(l); }); var bcastDefault = Messages.broadcast_defaultLanguage; - // XXX - //var first = !$container.find('.cp-broadcast-lang').length; + var first = !$container.find('.cp-broadcast-lang').length; + var radio = UI.createRadio('broadcastDefault', null, bcastDefault, first, { + 'data-lang': l, + label: {class: 'noTitle'} + }) + $(radio).find('input').on('change', function () { + if ($(this).is(':checked')) { $noFallbackBtn.css('visibility', ''); } + }); $container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [ h('h4', languages[l]), h('label', Messages.kanban_body), h('textarea'), - UI.createRadio('broadcastDefault', null, bcastDefault, false, { - 'data-lang': l, - label: {class: 'noTitle'} - }), + radio, preview ])); reorder(); @@ -1119,12 +1128,20 @@ define([ $(el).val(''); }); }; + + // "Don't fallback to a default language" button + $noFallbackBtn.click(function () { + $container.find('.cp-checkmark input').prop('checked', false); + $noFallbackBtn.css('visibility', 'hidden'); + }); + // Make the form $form.append([ h('label', Messages.broadcast_translations), h('div.cp-broadcast-languages', boxes), container, - button + button, + noFallbackBtn ]); })(); return; From 3ace3eaa91e0d5bbba08e1d7cf53a9327d75fd43 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 12:55:45 +0100 Subject: [PATCH 007/172] lint compliance --- www/admin/inner.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index 7deed589a..6b4f340a3 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -939,7 +939,7 @@ define([ return; }; - Messages.admin_cat_broadcast = "Broadcast" // XXX + Messages.admin_cat_broadcast = "Broadcast"; // XXX // Messages.admin_broadcastHint // XXX // Messages.admin_broadcastTitle // XXX Messages.broadcast_maintenance = 'maintenance';// XXX @@ -1062,7 +1062,7 @@ define([ var radio = UI.createRadio('broadcastDefault', null, bcastDefault, first, { 'data-lang': l, label: {class: 'noTitle'} - }) + }); $(radio).find('input').on('change', function () { if ($(this).is(':checked')) { $noFallbackBtn.css('visibility', ''); } }); @@ -1288,7 +1288,6 @@ define([ // Make the line var uid = Util.find(data, ['content', 'msg', 'uid']); - var hash = Util.find(data, ['content', 'hash']); var time = Util.find(data, ['content', 'msg', 'content', 'time']); var deleteBtn = h('button.btn.btn-danger', Messages.broadcast_deleteBtn); var tr = h('tr', { 'data-uid': uid }, [ From a41d70ab6bf0a04a03f4da30b307a1514133d640 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 15:05:19 +0100 Subject: [PATCH 008/172] Fix XXX --- .../src/less2/include/notifications.less | 6 ------ www/common/sframe-common-mailbox.js | 14 ++------------ 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/customize.dist/src/less2/include/notifications.less b/customize.dist/src/less2/include/notifications.less index 133ee2d2a..2883c7483 100644 --- a/customize.dist/src/less2/include/notifications.less +++ b/customize.dist/src/less2/include/notifications.less @@ -23,12 +23,6 @@ width: 30px; } padding: 0 5px; - &.admin { - cursor: pointer; - &:hover { - background-color: @cp_dropdown-bg-hover; - } - } } .cp-avatar { .avatar_main(30px); diff --git a/www/common/sframe-common-mailbox.js b/www/common/sframe-common-mailbox.js index c0ba2b3ff..3c5627902 100644 --- a/www/common/sframe-common-mailbox.js +++ b/www/common/sframe-common-mailbox.js @@ -7,10 +7,8 @@ define([ '/common/common-ui-elements.js', '/common/notifications.js', '/common/hyperscript.js', - '/common/clipboard.js', '/customize/messages.js', -], function ($, ApiConfig, Util, Hash, UI, UIElements, Notifications, h, - Clipboard, Messages) { +], function ($, ApiConfig, Util, Hash, UI, UIElements, Notifications, h, Messages) { var Mailbox = {}; Mailbox.create = function (Common) { @@ -65,17 +63,9 @@ define([ } if (data.type === 'broadcast') { var urlArgs = Util.find(ApiConfig, ['requireConf', 'urlArgs']) || ''; - var adminCls = Common.isAdmin() ? '.admin' : ''; - avatar = h('span.cp-broadcast'+adminCls, h('img', { + avatar = h('span.cp-broadcast', h('img', { src: '/customize/CryptPad_logo.svg?' + urlArgs, - title: adminCls ? 'Copy UID' : '' // XXX })); - if (adminCls) { - $(avatar).click(function () { - var success = Clipboard.copy(Util.find(data, ['content', 'msg', 'uid'])); - if (success) { UI.log(Messages.shareSuccess); } - }); - } } else if (userData && typeof(userData) === "object" && userData.profile) { avatar = h('span.cp-avatar'); Common.displayAvatar($(avatar), userData.avatar, userData.displayName || userData.name); From 34964ba5988f03baa1851ebee94e36a37ee25800 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 16:12:36 +0100 Subject: [PATCH 009/172] Fix maintenance end time --- www/admin/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index 6b4f340a3..30a10b942 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -1170,7 +1170,7 @@ define([ // Extract form data getData = function () { var start = +new Date($start.val()); - var end = +new Date($start.val()); + var end = +new Date($end.val()); if (isNaN(start) || isNaN(end)) { console.error('Invalid dates'); return false; From f938e3e60afcf7ad4892024674681f5d78cfecde Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 16:14:15 +0100 Subject: [PATCH 010/172] Protocol required in isValidURL --- www/common/common-util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index a6fe7afdf..36ae5cf64 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -555,7 +555,7 @@ }; Util.isValidURL = function (str) { - var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol + var pattern = new RegExp('^(https?:\\/\\/)'+ // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path From 39ce9dd02b4cb0142895924411d0e35c11fd5717 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 12 Mar 2021 17:29:17 +0100 Subject: [PATCH 011/172] Minor fixes and improvements to broadcast panel --- www/admin/inner.js | 28 +++++++++++++++++++++------- www/common/common-ui-elements.js | 2 +- www/common/notifications.js | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/www/admin/inner.js b/www/admin/inner.js index 30a10b942..bde7fae52 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -942,6 +942,7 @@ define([ Messages.admin_cat_broadcast = "Broadcast"; // XXX // Messages.admin_broadcastHint // XXX // Messages.admin_broadcastTitle // XXX + Messages.broadcast_new = "New message"; Messages.broadcast_maintenance = 'maintenance';// XXX Messages.broadcast_survey = 'survey'; // XXX Messages.broadcast_version = 'version'; // XXX @@ -1041,14 +1042,25 @@ define([ $el.css('order', keys.indexOf(l)); }); }; - // Remove a textarea - var removeLang = function (l) { - $container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove(); - }; var noFallbackBtn = h('button.btn.btn-secondary.cp-broadcast-preview', Messages.broadcast_noFallback); var $noFallbackBtn = $(noFallbackBtn); + var checkFallbackBtn = function () { + var hasDefault = $container.find('.cp-broadcast-lang .cp-checkmark input:checked').length; + console.error(hasDefault); + if (hasDefault) { + $noFallbackBtn.css('visibility', ''); + } else { + $noFallbackBtn.css('visibility', 'hidden'); + } + }; + + // Remove a textarea + var removeLang = function (l) { + $container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove(); + checkFallbackBtn(); + }; // Add a textarea var addLang = function (l) { @@ -1064,7 +1076,7 @@ define([ label: {class: 'noTitle'} }); $(radio).find('input').on('change', function () { - if ($(this).is(':checked')) { $noFallbackBtn.css('visibility', ''); } + checkFallbackBtn(); }); $container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [ h('h4', languages[l]), @@ -1073,6 +1085,7 @@ define([ radio, preview ])); + checkFallbackBtn(); reorder(); }; @@ -1397,11 +1410,12 @@ define([ }; }); var dropdownCfg = { - text: Messages.support_category, + text: Messages.broadcast_new, angleDown: 1, options: categories, container: $select, - isSelect: true + isSelect: true, + buttonCls: 'btn btn-default' }; UIElements.createDropdown(dropdownCfg); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4d8adc6b4..5eaf65523 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1328,7 +1328,7 @@ define([ // Button var $button = $('