mirror of https://github.com/snachodog/mybuddy.git
Minify scripts after concat
This commit is contained in:
parent
8436726e0c
commit
0a09fce38d
14
gulpfile.js
14
gulpfile.js
|
@ -180,34 +180,40 @@ function lint(cb) {
|
||||||
* @param cb
|
* @param cb
|
||||||
*/
|
*/
|
||||||
function scripts(cb) {
|
function scripts(cb) {
|
||||||
|
const minifyOptions = {
|
||||||
|
ext: { min:'.js' },
|
||||||
|
noSource: true,
|
||||||
|
};
|
||||||
|
|
||||||
pump([
|
pump([
|
||||||
gulp.src(config.scriptsConfig.vendor),
|
gulp.src(config.scriptsConfig.vendor),
|
||||||
removeSourcemaps(),
|
removeSourcemaps(),
|
||||||
minify(),
|
|
||||||
concat('vendor.js'),
|
concat('vendor.js'),
|
||||||
|
minify(minifyOptions),
|
||||||
gulp.dest(config.scriptsConfig.dest)
|
gulp.dest(config.scriptsConfig.dest)
|
||||||
], cb);
|
], cb);
|
||||||
|
|
||||||
pump([
|
pump([
|
||||||
gulp.src(config.scriptsConfig.graph),
|
gulp.src(config.scriptsConfig.graph),
|
||||||
removeSourcemaps(),
|
removeSourcemaps(),
|
||||||
minify(),
|
|
||||||
concat('graph.js'),
|
concat('graph.js'),
|
||||||
|
minify(minifyOptions),
|
||||||
gulp.dest(config.scriptsConfig.dest)
|
gulp.dest(config.scriptsConfig.dest)
|
||||||
], cb);
|
], cb);
|
||||||
|
|
||||||
pump([
|
pump([
|
||||||
gulp.src(config.scriptsConfig.app),
|
gulp.src(config.scriptsConfig.app),
|
||||||
removeSourcemaps(),
|
removeSourcemaps(),
|
||||||
minify(),
|
|
||||||
concat('app.js'),
|
concat('app.js'),
|
||||||
|
minify(minifyOptions),
|
||||||
gulp.dest(config.scriptsConfig.dest)
|
gulp.dest(config.scriptsConfig.dest)
|
||||||
], cb);
|
], cb);
|
||||||
|
|
||||||
pump([
|
pump([
|
||||||
gulp.src(config.scriptsConfig.tags_editor),
|
gulp.src(config.scriptsConfig.tags_editor),
|
||||||
minify(),
|
removeSourcemaps(),
|
||||||
concat('tags_editor.js'),
|
concat('tags_editor.js'),
|
||||||
|
minify(minifyOptions),
|
||||||
gulp.dest(config.scriptsConfig.dest)
|
gulp.dest(config.scriptsConfig.dest)
|
||||||
], cb);
|
], cb);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
if("undefined"==typeof jQuery)throw new Error("Baby Buddy requires jQuery.");if("undefined"==typeof moment)throw new Error("Baby Buddy requires moment.js.");var BabyBuddy={};function preventDoubleSubmit(){return!1}BabyBuddy.DatetimePicker=function(e,n){return{init:function(t,i){var o={buttons:{showToday:!0,showClose:!0},defaultDate:"now",focusOnShow:!1,format:"L LT",ignoreReadonly:!0,locale:n.locale(),useCurrent:!1,icons:{time:"icon-clock",date:"icon-calendar",up:"icon-arrow-up",down:"icon-arrow-down",previous:"icon-angle-circled-left",next:"icon-angle-circled-right",today:"icon-today",clear:"icon-delete",close:"icon-cancel"},viewMode:"times"};t.datetimepicker(e.extend(o,i))}}}(jQuery,moment),BabyBuddy.PullToRefresh=function(e){return{init:function(){e.init({mainElement:"body",onRefresh:this.onRefresh})},onRefresh:function(){window.location.reload()}}}(PullToRefresh),$("form").off("submit",preventDoubleSubmit),$("form").on("submit",function(){$(this).on("submit",preventDoubleSubmit)}),BabyBuddy.Timer=function(e){var n=null,t=null,i=null,o=moment(),d=null,r={run:function(o,u){return t=o,0==(i=e("#"+u)).length?(console.error("BBTimer: Timer element not found."),!1):0==i.find(".timer-seconds").length||0==i.find(".timer-minutes").length||0==i.find(".timer-hours").length?(console.error("BBTimer: Element does not contain expected children."),!1):(n=setInterval(this.tick,1e3),void 0!==document.hidden?d="hidden":void 0!==document.msHidden?d="msHidden":void 0!==document.webkitHidden&&(d="webkitHidden"),void window.addEventListener("focus",r.handleVisibilityChange,!1))},handleVisibilityChange:function(){!document[d]&&moment().diff(o)>1e4&&r.update()},tick:function(){var e=i.find(".timer-seconds"),n=Number(e.text());if(n<59)e.text(n+1);else{e.text(0);var t=i.find(".timer-minutes"),o=Number(t.text());if(o<59)t.text(o+1);else{t.text(0);var d=i.find(".timer-hours"),r=Number(d.text());d.text(r+1)}}},update:function(){e.get("/api/timers/"+t+"/",function(e){if(e&&"duration"in e){clearInterval(n);var t=moment.duration(e.duration);i.find(".timer-hours").text(t.hours()),i.find(".timer-minutes").text(t.minutes()),i.find(".timer-seconds").text(t.seconds()),o=moment(),e.active?n=setInterval(r.tick,1e3):i.addClass("timer-stopped")}})}};return r}(jQuery),BabyBuddy.Dashboard=function(e){var n=null,t={watch:function(i,o){if(0==e("#"+i).length)return console.error("Baby Buddy: Dashboard element not found."),!1;void 0!==document.hidden?n="hidden":void 0!==document.msHidden?n="msHidden":void 0!==document.webkitHidden&&(n="webkitHidden"),void 0===window.addEventListener||void 0===document.hidden?o&&setInterval(this.update,o):(window.addEventListener("focus",t.handleVisibilityChange,!1),o&&setInterval(t.handleVisibilityChange,o))},handleVisibilityChange:function(){document[n]||t.update()},update:function(){location.reload()}};return t}(jQuery);
|
Binary file not shown.
|
@ -1,246 +0,0 @@
|
||||||
if (typeof jQuery === 'undefined') {
|
|
||||||
throw new Error('Baby Buddy requires jQuery.')
|
|
||||||
}
|
|
||||||
if (typeof moment === 'undefined') {
|
|
||||||
throw new Error('Baby Buddy requires moment.js.')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Baby Buddy Namespace
|
|
||||||
*
|
|
||||||
* Default namespace for the Baby Buddy app.
|
|
||||||
*
|
|
||||||
* @type {{}}
|
|
||||||
*/
|
|
||||||
var BabyBuddy = function () {
|
|
||||||
return {};
|
|
||||||
}();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Datetime Picker.
|
|
||||||
*
|
|
||||||
* Provides modifications and defaults for the base datetime picker widget.
|
|
||||||
*
|
|
||||||
* @type {{init: BabyBuddy.DatetimePicker.init}}
|
|
||||||
*/
|
|
||||||
BabyBuddy.DatetimePicker = function ($, moment) {
|
|
||||||
return {
|
|
||||||
init: function (element, options) {
|
|
||||||
var defaultOptions = {
|
|
||||||
buttons: { showToday: true, showClose: true },
|
|
||||||
defaultDate: 'now',
|
|
||||||
focusOnShow: false,
|
|
||||||
format: 'L LT',
|
|
||||||
ignoreReadonly: true,
|
|
||||||
locale: moment.locale(),
|
|
||||||
useCurrent: false,
|
|
||||||
icons: {
|
|
||||||
time: 'icon-clock',
|
|
||||||
date: 'icon-calendar',
|
|
||||||
up: 'icon-arrow-up',
|
|
||||||
down: 'icon-arrow-down',
|
|
||||||
previous: 'icon-angle-circled-left',
|
|
||||||
next: 'icon-angle-circled-right',
|
|
||||||
today: 'icon-today',
|
|
||||||
clear: 'icon-delete',
|
|
||||||
close: 'icon-cancel'
|
|
||||||
},
|
|
||||||
viewMode: 'times',
|
|
||||||
};
|
|
||||||
element.datetimepicker($.extend(defaultOptions, options));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}(jQuery, moment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pull to refresh.
|
|
||||||
*
|
|
||||||
* @type {{init: BabyBuddy.PullToRefresh.init, onRefresh: BabyBuddy.PullToRefresh.onRefresh}}
|
|
||||||
*/
|
|
||||||
BabyBuddy.PullToRefresh = function(ptr) {
|
|
||||||
return {
|
|
||||||
init: function () {
|
|
||||||
ptr.init({
|
|
||||||
mainElement: 'body',
|
|
||||||
onRefresh: this.onRefresh
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onRefresh: function() {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}(PullToRefresh);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix for duplicate form submission from double pressing submit
|
|
||||||
*/
|
|
||||||
function preventDoubleSubmit() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$('form').off("submit", preventDoubleSubmit);
|
|
||||||
$("form").on("submit", function() {
|
|
||||||
$(this).on("submit", preventDoubleSubmit);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Baby Buddy Timer
|
|
||||||
*
|
|
||||||
* Uses a supplied ID to run a timer. The element using the ID must have
|
|
||||||
* three children with the following classes:
|
|
||||||
* * timer-seconds
|
|
||||||
* * timer-minutes
|
|
||||||
* * timer-hours
|
|
||||||
*/
|
|
||||||
BabyBuddy.Timer = function ($) {
|
|
||||||
var runIntervalId = null;
|
|
||||||
var timerId = null;
|
|
||||||
var timerElement = null;
|
|
||||||
var lastUpdate = moment();
|
|
||||||
var hidden = null;
|
|
||||||
|
|
||||||
var Timer = {
|
|
||||||
run: function(timer_id, element_id) {
|
|
||||||
timerId = timer_id;
|
|
||||||
timerElement = $('#' + element_id);
|
|
||||||
|
|
||||||
if (timerElement.length == 0) {
|
|
||||||
console.error('BBTimer: Timer element not found.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timerElement.find('.timer-seconds').length == 0
|
|
||||||
|| timerElement.find('.timer-minutes').length == 0
|
|
||||||
|| timerElement.find('.timer-hours').length == 0) {
|
|
||||||
console.error('BBTimer: Element does not contain expected children.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
runIntervalId = setInterval(this.tick, 1000);
|
|
||||||
|
|
||||||
// If the page just came in to view, update the timer data with the
|
|
||||||
// current actual duration. This will (potentially) help mobile
|
|
||||||
// phones that lock with the timer page open.
|
|
||||||
if (typeof document.hidden !== "undefined") {
|
|
||||||
hidden = "hidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.msHidden !== "undefined") {
|
|
||||||
hidden = "msHidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.webkitHidden !== "undefined") {
|
|
||||||
hidden = "webkitHidden";
|
|
||||||
}
|
|
||||||
window.addEventListener('focus', Timer.handleVisibilityChange, false);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleVisibilityChange: function() {
|
|
||||||
if (!document[hidden] && moment().diff(lastUpdate) > 10000) {
|
|
||||||
Timer.update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tick: function() {
|
|
||||||
var s = timerElement.find('.timer-seconds');
|
|
||||||
var seconds = Number(s.text());
|
|
||||||
if (seconds < 59) {
|
|
||||||
s.text(seconds + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s.text(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var m = timerElement.find('.timer-minutes');
|
|
||||||
var minutes = Number(m.text());
|
|
||||||
if (minutes < 59) {
|
|
||||||
m.text(minutes + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m.text(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var h = timerElement.find('.timer-hours');
|
|
||||||
var hours = Number(h.text());
|
|
||||||
h.text(hours + 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function() {
|
|
||||||
$.get('/api/timers/' + timerId + '/', function(data) {
|
|
||||||
if (data && 'duration' in data) {
|
|
||||||
clearInterval(runIntervalId);
|
|
||||||
var duration = moment.duration(data.duration);
|
|
||||||
timerElement.find('.timer-hours').text(duration.hours());
|
|
||||||
timerElement.find('.timer-minutes').text(duration.minutes());
|
|
||||||
timerElement.find('.timer-seconds').text(duration.seconds());
|
|
||||||
lastUpdate = moment();
|
|
||||||
|
|
||||||
if (data['active']) {
|
|
||||||
runIntervalId = setInterval(Timer.tick, 1000);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
timerElement.addClass('timer-stopped');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Timer;
|
|
||||||
}(jQuery);
|
|
||||||
|
|
||||||
/* Baby Buddy Dashboard
|
|
||||||
*
|
|
||||||
* Provides a "watch" function to update the dashboard at one minute intervals
|
|
||||||
* and/or on visibility state changes.
|
|
||||||
*/
|
|
||||||
BabyBuddy.Dashboard = function ($) {
|
|
||||||
var runIntervalId = null;
|
|
||||||
var dashboardElement = null;
|
|
||||||
var hidden = null;
|
|
||||||
|
|
||||||
var Dashboard = {
|
|
||||||
watch: function(element_id, refresh_rate) {
|
|
||||||
dashboardElement = $('#' + element_id);
|
|
||||||
|
|
||||||
if (dashboardElement.length == 0) {
|
|
||||||
console.error('Baby Buddy: Dashboard element not found.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof document.hidden !== "undefined") {
|
|
||||||
hidden = "hidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.msHidden !== "undefined") {
|
|
||||||
hidden = "msHidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.webkitHidden !== "undefined") {
|
|
||||||
hidden = "webkitHidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window.addEventListener === "undefined" || typeof document.hidden === "undefined") {
|
|
||||||
if (refresh_rate) {
|
|
||||||
runIntervalId = setInterval(this.update, refresh_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
window.addEventListener('focus', Dashboard.handleVisibilityChange, false);
|
|
||||||
if (refresh_rate) {
|
|
||||||
runIntervalId = setInterval(Dashboard.handleVisibilityChange, refresh_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleVisibilityChange: function() {
|
|
||||||
if (!document[hidden]) {
|
|
||||||
Dashboard.update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function() {
|
|
||||||
// TODO: Someday maybe update in place?
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Dashboard;
|
|
||||||
}(jQuery);
|
|
Binary file not shown.
|
@ -1,246 +1 @@
|
||||||
if (typeof jQuery === 'undefined') {
|
if("undefined"==typeof jQuery)throw new Error("Baby Buddy requires jQuery.");if("undefined"==typeof moment)throw new Error("Baby Buddy requires moment.js.");var BabyBuddy={};function preventDoubleSubmit(){return!1}BabyBuddy.DatetimePicker=function(e,n){return{init:function(t,i){var o={buttons:{showToday:!0,showClose:!0},defaultDate:"now",focusOnShow:!1,format:"L LT",ignoreReadonly:!0,locale:n.locale(),useCurrent:!1,icons:{time:"icon-clock",date:"icon-calendar",up:"icon-arrow-up",down:"icon-arrow-down",previous:"icon-angle-circled-left",next:"icon-angle-circled-right",today:"icon-today",clear:"icon-delete",close:"icon-cancel"},viewMode:"times"};t.datetimepicker(e.extend(o,i))}}}(jQuery,moment),BabyBuddy.PullToRefresh=function(e){return{init:function(){e.init({mainElement:"body",onRefresh:this.onRefresh})},onRefresh:function(){window.location.reload()}}}(PullToRefresh),$("form").off("submit",preventDoubleSubmit),$("form").on("submit",function(){$(this).on("submit",preventDoubleSubmit)}),BabyBuddy.Timer=function(e){var n=null,t=null,i=null,o=moment(),d=null,r={run:function(o,u){return t=o,0==(i=e("#"+u)).length?(console.error("BBTimer: Timer element not found."),!1):0==i.find(".timer-seconds").length||0==i.find(".timer-minutes").length||0==i.find(".timer-hours").length?(console.error("BBTimer: Element does not contain expected children."),!1):(n=setInterval(this.tick,1e3),void 0!==document.hidden?d="hidden":void 0!==document.msHidden?d="msHidden":void 0!==document.webkitHidden&&(d="webkitHidden"),void window.addEventListener("focus",r.handleVisibilityChange,!1))},handleVisibilityChange:function(){!document[d]&&moment().diff(o)>1e4&&r.update()},tick:function(){var e=i.find(".timer-seconds"),n=Number(e.text());if(n<59)e.text(n+1);else{e.text(0);var t=i.find(".timer-minutes"),o=Number(t.text());if(o<59)t.text(o+1);else{t.text(0);var d=i.find(".timer-hours"),r=Number(d.text());d.text(r+1)}}},update:function(){e.get("/api/timers/"+t+"/",function(e){if(e&&"duration"in e){clearInterval(n);var t=moment.duration(e.duration);i.find(".timer-hours").text(t.hours()),i.find(".timer-minutes").text(t.minutes()),i.find(".timer-seconds").text(t.seconds()),o=moment(),e.active?n=setInterval(r.tick,1e3):i.addClass("timer-stopped")}})}};return r}(jQuery),BabyBuddy.Dashboard=function(e){var n=null,t={watch:function(i,o){if(0==e("#"+i).length)return console.error("Baby Buddy: Dashboard element not found."),!1;void 0!==document.hidden?n="hidden":void 0!==document.msHidden?n="msHidden":void 0!==document.webkitHidden&&(n="webkitHidden"),void 0===window.addEventListener||void 0===document.hidden?o&&setInterval(this.update,o):(window.addEventListener("focus",t.handleVisibilityChange,!1),o&&setInterval(t.handleVisibilityChange,o))},handleVisibilityChange:function(){document[n]||t.update()},update:function(){location.reload()}};return t}(jQuery);
|
||||||
throw new Error('Baby Buddy requires jQuery.')
|
|
||||||
}
|
|
||||||
if (typeof moment === 'undefined') {
|
|
||||||
throw new Error('Baby Buddy requires moment.js.')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Baby Buddy Namespace
|
|
||||||
*
|
|
||||||
* Default namespace for the Baby Buddy app.
|
|
||||||
*
|
|
||||||
* @type {{}}
|
|
||||||
*/
|
|
||||||
var BabyBuddy = function () {
|
|
||||||
return {};
|
|
||||||
}();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Datetime Picker.
|
|
||||||
*
|
|
||||||
* Provides modifications and defaults for the base datetime picker widget.
|
|
||||||
*
|
|
||||||
* @type {{init: BabyBuddy.DatetimePicker.init}}
|
|
||||||
*/
|
|
||||||
BabyBuddy.DatetimePicker = function ($, moment) {
|
|
||||||
return {
|
|
||||||
init: function (element, options) {
|
|
||||||
var defaultOptions = {
|
|
||||||
buttons: { showToday: true, showClose: true },
|
|
||||||
defaultDate: 'now',
|
|
||||||
focusOnShow: false,
|
|
||||||
format: 'L LT',
|
|
||||||
ignoreReadonly: true,
|
|
||||||
locale: moment.locale(),
|
|
||||||
useCurrent: false,
|
|
||||||
icons: {
|
|
||||||
time: 'icon-clock',
|
|
||||||
date: 'icon-calendar',
|
|
||||||
up: 'icon-arrow-up',
|
|
||||||
down: 'icon-arrow-down',
|
|
||||||
previous: 'icon-angle-circled-left',
|
|
||||||
next: 'icon-angle-circled-right',
|
|
||||||
today: 'icon-today',
|
|
||||||
clear: 'icon-delete',
|
|
||||||
close: 'icon-cancel'
|
|
||||||
},
|
|
||||||
viewMode: 'times',
|
|
||||||
};
|
|
||||||
element.datetimepicker($.extend(defaultOptions, options));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}(jQuery, moment);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pull to refresh.
|
|
||||||
*
|
|
||||||
* @type {{init: BabyBuddy.PullToRefresh.init, onRefresh: BabyBuddy.PullToRefresh.onRefresh}}
|
|
||||||
*/
|
|
||||||
BabyBuddy.PullToRefresh = function(ptr) {
|
|
||||||
return {
|
|
||||||
init: function () {
|
|
||||||
ptr.init({
|
|
||||||
mainElement: 'body',
|
|
||||||
onRefresh: this.onRefresh
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onRefresh: function() {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}(PullToRefresh);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix for duplicate form submission from double pressing submit
|
|
||||||
*/
|
|
||||||
function preventDoubleSubmit() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$('form').off("submit", preventDoubleSubmit);
|
|
||||||
$("form").on("submit", function() {
|
|
||||||
$(this).on("submit", preventDoubleSubmit);
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Baby Buddy Timer
|
|
||||||
*
|
|
||||||
* Uses a supplied ID to run a timer. The element using the ID must have
|
|
||||||
* three children with the following classes:
|
|
||||||
* * timer-seconds
|
|
||||||
* * timer-minutes
|
|
||||||
* * timer-hours
|
|
||||||
*/
|
|
||||||
BabyBuddy.Timer = function ($) {
|
|
||||||
var runIntervalId = null;
|
|
||||||
var timerId = null;
|
|
||||||
var timerElement = null;
|
|
||||||
var lastUpdate = moment();
|
|
||||||
var hidden = null;
|
|
||||||
|
|
||||||
var Timer = {
|
|
||||||
run: function(timer_id, element_id) {
|
|
||||||
timerId = timer_id;
|
|
||||||
timerElement = $('#' + element_id);
|
|
||||||
|
|
||||||
if (timerElement.length == 0) {
|
|
||||||
console.error('BBTimer: Timer element not found.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timerElement.find('.timer-seconds').length == 0
|
|
||||||
|| timerElement.find('.timer-minutes').length == 0
|
|
||||||
|| timerElement.find('.timer-hours').length == 0) {
|
|
||||||
console.error('BBTimer: Element does not contain expected children.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
runIntervalId = setInterval(this.tick, 1000);
|
|
||||||
|
|
||||||
// If the page just came in to view, update the timer data with the
|
|
||||||
// current actual duration. This will (potentially) help mobile
|
|
||||||
// phones that lock with the timer page open.
|
|
||||||
if (typeof document.hidden !== "undefined") {
|
|
||||||
hidden = "hidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.msHidden !== "undefined") {
|
|
||||||
hidden = "msHidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.webkitHidden !== "undefined") {
|
|
||||||
hidden = "webkitHidden";
|
|
||||||
}
|
|
||||||
window.addEventListener('focus', Timer.handleVisibilityChange, false);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleVisibilityChange: function() {
|
|
||||||
if (!document[hidden] && moment().diff(lastUpdate) > 10000) {
|
|
||||||
Timer.update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
tick: function() {
|
|
||||||
var s = timerElement.find('.timer-seconds');
|
|
||||||
var seconds = Number(s.text());
|
|
||||||
if (seconds < 59) {
|
|
||||||
s.text(seconds + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
s.text(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var m = timerElement.find('.timer-minutes');
|
|
||||||
var minutes = Number(m.text());
|
|
||||||
if (minutes < 59) {
|
|
||||||
m.text(minutes + 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m.text(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var h = timerElement.find('.timer-hours');
|
|
||||||
var hours = Number(h.text());
|
|
||||||
h.text(hours + 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function() {
|
|
||||||
$.get('/api/timers/' + timerId + '/', function(data) {
|
|
||||||
if (data && 'duration' in data) {
|
|
||||||
clearInterval(runIntervalId);
|
|
||||||
var duration = moment.duration(data.duration);
|
|
||||||
timerElement.find('.timer-hours').text(duration.hours());
|
|
||||||
timerElement.find('.timer-minutes').text(duration.minutes());
|
|
||||||
timerElement.find('.timer-seconds').text(duration.seconds());
|
|
||||||
lastUpdate = moment();
|
|
||||||
|
|
||||||
if (data['active']) {
|
|
||||||
runIntervalId = setInterval(Timer.tick, 1000);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
timerElement.addClass('timer-stopped');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Timer;
|
|
||||||
}(jQuery);
|
|
||||||
|
|
||||||
/* Baby Buddy Dashboard
|
|
||||||
*
|
|
||||||
* Provides a "watch" function to update the dashboard at one minute intervals
|
|
||||||
* and/or on visibility state changes.
|
|
||||||
*/
|
|
||||||
BabyBuddy.Dashboard = function ($) {
|
|
||||||
var runIntervalId = null;
|
|
||||||
var dashboardElement = null;
|
|
||||||
var hidden = null;
|
|
||||||
|
|
||||||
var Dashboard = {
|
|
||||||
watch: function(element_id, refresh_rate) {
|
|
||||||
dashboardElement = $('#' + element_id);
|
|
||||||
|
|
||||||
if (dashboardElement.length == 0) {
|
|
||||||
console.error('Baby Buddy: Dashboard element not found.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof document.hidden !== "undefined") {
|
|
||||||
hidden = "hidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.msHidden !== "undefined") {
|
|
||||||
hidden = "msHidden";
|
|
||||||
}
|
|
||||||
else if (typeof document.webkitHidden !== "undefined") {
|
|
||||||
hidden = "webkitHidden";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window.addEventListener === "undefined" || typeof document.hidden === "undefined") {
|
|
||||||
if (refresh_rate) {
|
|
||||||
runIntervalId = setInterval(this.update, refresh_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
window.addEventListener('focus', Dashboard.handleVisibilityChange, false);
|
|
||||||
if (refresh_rate) {
|
|
||||||
runIntervalId = setInterval(Dashboard.handleVisibilityChange, refresh_rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleVisibilityChange: function() {
|
|
||||||
if (!document[hidden]) {
|
|
||||||
Dashboard.update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
update: function() {
|
|
||||||
// TODO: Someday maybe update in place?
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Dashboard;
|
|
||||||
}(jQuery);
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
@ -1,354 +0,0 @@
|
||||||
(function() {
|
|
||||||
/**
|
|
||||||
* Parse a string as hexadecimal number
|
|
||||||
*/
|
|
||||||
function hexParse(x) {
|
|
||||||
return parseInt(x, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the contrasting color for any hex color
|
|
||||||
*
|
|
||||||
* Sourced from: https://vanillajstoolkit.com/helpers/getcontrast/
|
|
||||||
* - Modified with slightly softer colors
|
|
||||||
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
|
|
||||||
* Derived from work by Brian Suda, https://24ways.org/2010/calculating-color-contrast/
|
|
||||||
* @param {String} A hexcolor value
|
|
||||||
* @return {String} The contrasting color (black or white)
|
|
||||||
*/
|
|
||||||
function computeComplementaryColor(hexcolor) {
|
|
||||||
|
|
||||||
// If a leading # is provided, remove it
|
|
||||||
if (hexcolor.slice(0, 1) === '#') {
|
|
||||||
hexcolor = hexcolor.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a three-character hexcode, make six-character
|
|
||||||
if (hexcolor.length === 3) {
|
|
||||||
hexcolor = hexcolor.split('').map(function (hex) {
|
|
||||||
return hex + hex;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to RGB value
|
|
||||||
let r = parseInt(hexcolor.substr(0,2),16);
|
|
||||||
let g = parseInt(hexcolor.substr(2,2),16);
|
|
||||||
let b = parseInt(hexcolor.substr(4,2),16);
|
|
||||||
|
|
||||||
// Get YIQ ratio
|
|
||||||
let yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
|
||||||
|
|
||||||
// Check contrast
|
|
||||||
return (yiq >= 128) ? '#101010' : '#EFEFEF';
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSRF token should always be present because it is auto-included with
|
|
||||||
// every tag-editor widget
|
|
||||||
const CSRF_TOKEN = document.querySelector('input[name="csrfmiddlewaretoken"]').value;
|
|
||||||
|
|
||||||
function doReq(method, uri, data, success, fail) {
|
|
||||||
// TODO: prefer jQuery based requests for now
|
|
||||||
|
|
||||||
const req = new XMLHttpRequest();
|
|
||||||
req.addEventListener('load', () => {
|
|
||||||
if ((req.status >= 200) && (req.status < 300)) {
|
|
||||||
success(req.responseText, req);
|
|
||||||
} else {
|
|
||||||
fail(req.responseText, req);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (const name of ["error", "timeout", "abort"]) {
|
|
||||||
req.addEventListener(name, () => {
|
|
||||||
fail(req.responseText, req);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
req.timeout = 20000;
|
|
||||||
|
|
||||||
req.open(method, uri);
|
|
||||||
req.setRequestHeader("Content-Type", "application/json");
|
|
||||||
req.setRequestHeader("Accept", "application/json");
|
|
||||||
req.setRequestHeader("X-CSRFTOKEN", CSRF_TOKEN);
|
|
||||||
req.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class allowing generic operations on the tag lists, like:
|
|
||||||
*
|
|
||||||
* - Adding tags to a tag list
|
|
||||||
* - Updating or creating new tags with a set name and color
|
|
||||||
* - Controlling the error modal
|
|
||||||
*/
|
|
||||||
class TaggingBase {
|
|
||||||
constructor(widget) {
|
|
||||||
this.prototype = widget.querySelector('.prototype-tag');
|
|
||||||
this.listeners = [];
|
|
||||||
|
|
||||||
this.modalElement = widget.querySelector('.tag-editor-error-modal');
|
|
||||||
this.modalBodyNode = this.modalElement.querySelector('.modal-body');
|
|
||||||
|
|
||||||
// Clean whitespace text nodes between spans
|
|
||||||
for (const n of this.modalBodyNode.childNodes) {
|
|
||||||
if (n.nodeType === Node.TEXT_NODE) {
|
|
||||||
this.modalBodyNode.removeChild(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal(msg) {
|
|
||||||
const selectedMessage = this.modalBodyNode.querySelector(`span[data-message='${msg}']`);
|
|
||||||
if (!selectedMessage) {
|
|
||||||
selectedMessage = this.modalBodyNode.childNodes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const n of this.modalBodyNode.childNodes) {
|
|
||||||
n.classList.add('d-none');
|
|
||||||
}
|
|
||||||
selectedMessage.classList.remove('d-none');
|
|
||||||
|
|
||||||
jQuery(this.modalElement).modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
addTagListUpdatedListener(c) {
|
|
||||||
this.listeners.push(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
callTagListUpdatedListeners() {
|
|
||||||
for (const l of this.listeners) {
|
|
||||||
l();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTag(tag, name, color, actionSymbol) {
|
|
||||||
const actionTextNode = tag.querySelector('.add-remove-icon').childNodes[0];
|
|
||||||
|
|
||||||
name = name || tag.getAttribute("data-value");
|
|
||||||
color = color || tag.getAttribute("data-color");
|
|
||||||
actionSymbol = actionSymbol || actionTextNode.textContent;
|
|
||||||
|
|
||||||
tag.childNodes[0].textContent = name;
|
|
||||||
tag.setAttribute("data-value", name);
|
|
||||||
tag.setAttribute("data-color", color);
|
|
||||||
|
|
||||||
const textColor = computeComplementaryColor(color);
|
|
||||||
tag.setAttribute('style', `background-color: ${color}; color: ${textColor};`);
|
|
||||||
actionTextNode.textContent = actionSymbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
createNewTag(name, color, actionSymbol) {
|
|
||||||
const tag = this.prototype.cloneNode(true);
|
|
||||||
tag.classList.remove("prototype-tag");
|
|
||||||
tag.classList.add("tag");
|
|
||||||
this.updateTag(tag, name, color, actionSymbol);
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertTag(list, tag) {
|
|
||||||
list.appendChild(tag);
|
|
||||||
this.callTagListUpdatedListeners();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for the edit field allowing to dynamically create new tags.
|
|
||||||
*
|
|
||||||
* Handles user inputs for the editor. Calls the 'onInsertNewTag' callback
|
|
||||||
* when the creation of a new tag has been requested. All backend handling
|
|
||||||
* like guareteening that the requested tag exists is handled by this class,
|
|
||||||
* the only task left is to add the new tag to the tags-list when
|
|
||||||
* 'onInsertNewTag' is called.
|
|
||||||
*/
|
|
||||||
class AddNewTagControl {
|
|
||||||
/**
|
|
||||||
* @param widget
|
|
||||||
* The root DOM element of the widget
|
|
||||||
* @param taggingBase
|
|
||||||
* Reference to a common TaggingBase class to be used by this widget
|
|
||||||
* @param onInsertNewTag
|
|
||||||
* Callback that is called when a new tag should be added to the
|
|
||||||
* tags widget.
|
|
||||||
*/
|
|
||||||
constructor(widget, taggingBase, onInsertNewTag) {
|
|
||||||
this.widget = widget;
|
|
||||||
this.taggingBase = taggingBase;
|
|
||||||
|
|
||||||
this.apiTagsUrl = widget.getAttribute('data-tags-url');
|
|
||||||
this.createTagInputs = widget.querySelector('.create-tag-inputs');
|
|
||||||
this.addTagInput = this.createTagInputs.querySelector('input[type="text"]');
|
|
||||||
this.addTagButton = this.createTagInputs.querySelector('.btn-add-new-tag');
|
|
||||||
|
|
||||||
this.addTagInput.value = "";
|
|
||||||
|
|
||||||
this.onInsertNewTag = onInsertNewTag;
|
|
||||||
|
|
||||||
this.addTagButton.addEventListener('click', () => this.onCreateTagClicked());
|
|
||||||
this.addTagInput.addEventListener('keydown', (e) => {
|
|
||||||
const key = e.key.toLowerCase();
|
|
||||||
if (key === "enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
this.onCreateTagClicked();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback called when the the "Add" button of the add-tag input is
|
|
||||||
* clicked or enter is pressed in the editor.
|
|
||||||
*/
|
|
||||||
onCreateTagClicked() {
|
|
||||||
// TODO: Make promise based
|
|
||||||
|
|
||||||
const tagName = this.addTagInput.value.trim();
|
|
||||||
const uriTagName = encodeURIComponent(tagName);
|
|
||||||
|
|
||||||
const fail = (msg) => {
|
|
||||||
this.addTagInput.select();
|
|
||||||
this.taggingBase.showModal(msg || "generic");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!tagName) {
|
|
||||||
fail('invalid-tag-name');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addTag = (name, color) => {
|
|
||||||
const tag = this.taggingBase.createNewTag(name, color, "-");
|
|
||||||
this.addTagInput.value = "";
|
|
||||||
this.onInsertNewTag(tag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = JSON.stringify({
|
|
||||||
'name': this.addTagInput.value
|
|
||||||
});
|
|
||||||
|
|
||||||
doReq("GET", `${this.apiTagsUrl}?name=${uriTagName}`, null,
|
|
||||||
(text) => {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
if (json.count) {
|
|
||||||
const tagJson = json.results[0];
|
|
||||||
addTag(tagJson.name, tagJson.color);
|
|
||||||
} else {
|
|
||||||
doReq("POST", this.apiTagsUrl, data,
|
|
||||||
(text) => {
|
|
||||||
const tagJson = JSON.parse(text);
|
|
||||||
addTag(tagJson.name, tagJson.color);
|
|
||||||
}, () => fail("tag-creation-failed")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, () => fail("tag-checking-failed")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JavaScript implementation for the tags editor.
|
|
||||||
*
|
|
||||||
* This class uses TaggingBase and AddNewTagControl to provide the custom
|
|
||||||
* tag editor controls. This mainly consists of updating the hidden
|
|
||||||
* input values with the current list of tags and adding/removing
|
|
||||||
* tags from the current-tags- or recently-used-lists.
|
|
||||||
*/
|
|
||||||
class TagsEditor {
|
|
||||||
/**
|
|
||||||
* @param tagEditorRoot
|
|
||||||
* The root DOM element of the widget.
|
|
||||||
*/
|
|
||||||
constructor(tagEditorRoot) {
|
|
||||||
this.widget = tagEditorRoot;
|
|
||||||
this.taggingBase = new TaggingBase(this.widget);
|
|
||||||
this.addTagControl = new AddNewTagControl(
|
|
||||||
this.widget, this.taggingBase, (t) => this.insertNewTag(t)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.currentTags = this.widget.querySelector('.current_tags');
|
|
||||||
this.newTags = this.widget.querySelector('.new-tags');
|
|
||||||
this.inputElement = this.widget.querySelector('input[type="hidden"]');
|
|
||||||
|
|
||||||
for (const tag of this.newTags.querySelectorAll(".tag")) {
|
|
||||||
this.configureAddTag(tag);
|
|
||||||
}
|
|
||||||
for (const tag of this.currentTags.querySelectorAll(".tag")) {
|
|
||||||
this.configureRemoveTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateInputList();
|
|
||||||
this.taggingBase.addTagListUpdatedListener(
|
|
||||||
() => this.updateInputList()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a new tag into the "current tag" list.
|
|
||||||
*
|
|
||||||
* Makes sure that no duplicates are present in the widget before adding
|
|
||||||
* the new tag. If a duplicate is found, the old tag is removed before
|
|
||||||
* the new one is added.
|
|
||||||
*/
|
|
||||||
insertNewTag(tag) {
|
|
||||||
const name = tag.getAttribute("data-value");
|
|
||||||
|
|
||||||
const oldTag = this.widget.querySelector(`span[data-value="${name}"]`);
|
|
||||||
if (oldTag) {
|
|
||||||
oldTag.parentNode.removeChild(oldTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taggingBase.insertTag(this.currentTags, tag);
|
|
||||||
this.configureRemoveTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registeres a click-callback for a given node.
|
|
||||||
*
|
|
||||||
* The callback chain-calls another callback "onClicked" after
|
|
||||||
* moving the clicked tag from the old tag-list to a new tag list.
|
|
||||||
*/
|
|
||||||
registerNewCallback(tag, newParent, onClicked) {
|
|
||||||
function callback(event) {
|
|
||||||
tag.parentNode.removeChild(tag);
|
|
||||||
this.taggingBase.insertTag(newParent, tag);
|
|
||||||
|
|
||||||
tag.removeEventListener('click', callback);
|
|
||||||
onClicked(tag);
|
|
||||||
}
|
|
||||||
tag.addEventListener('click', callback.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the value of the hidden input element.
|
|
||||||
*
|
|
||||||
* Sets the value from the list of tags added to the currentTags
|
|
||||||
* DOM element.
|
|
||||||
*/
|
|
||||||
updateInputList() {
|
|
||||||
const names = [];
|
|
||||||
for (const tag of this.currentTags.querySelectorAll(".tag")) {
|
|
||||||
const name = tag.getAttribute("data-value");
|
|
||||||
names.push(`"${name}"`);
|
|
||||||
}
|
|
||||||
this.inputElement.value = names.join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a tag-DOM element as a "add tag" button.
|
|
||||||
*/
|
|
||||||
configureAddTag(tag) {
|
|
||||||
this.taggingBase.updateTag(tag, null, null, "+");
|
|
||||||
this.registerNewCallback(tag, this.currentTags, () => this.configureRemoveTag(tag));
|
|
||||||
this.updateInputList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a tag-DOM element as a "remove tag" button.
|
|
||||||
*/
|
|
||||||
configureRemoveTag(tag) {
|
|
||||||
this.taggingBase.updateTag(tag, null, null, "-");
|
|
||||||
this.registerNewCallback(tag, this.newTags, () => this.configureAddTag(tag));
|
|
||||||
this.updateInputList();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
for (const el of document.querySelectorAll('.babybuddy-tags-editor')) {
|
|
||||||
new TagsEditor(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
!function(){const t=document.querySelector('input[name="csrfmiddlewaretoken"]').value;function e(e,s,a,i,n){const o=new XMLHttpRequest;o.addEventListener("load",()=>{o.status>=200&&o.status<300?i(o.responseText,o):n(o.responseText,o)});for(const t of["error","timeout","abort"])o.addEventListener(t,()=>{n(o.responseText,o)});o.timeout=2e4,o.open(e,s),o.setRequestHeader("Content-Type","application/json"),o.setRequestHeader("Accept","application/json"),o.setRequestHeader("X-CSRFTOKEN",t),o.send(a)}class s{constructor(t){this.prototype=t.querySelector(".prototype-tag"),this.listeners=[],this.modalElement=t.querySelector(".tag-editor-error-modal"),this.modalBodyNode=this.modalElement.querySelector(".modal-body");for(const t of this.modalBodyNode.childNodes)t.nodeType===Node.TEXT_NODE&&this.modalBodyNode.removeChild(t)}showModal(t){const e=this.modalBodyNode.querySelector(`span[data-message='${t}']`);e||(e=this.modalBodyNode.childNodes[0]);for(const t of this.modalBodyNode.childNodes)t.classList.add("d-none");e.classList.remove("d-none"),jQuery(this.modalElement).modal("show")}addTagListUpdatedListener(t){this.listeners.push(t)}callTagListUpdatedListeners(){for(const t of this.listeners)t()}updateTag(t,e,s,a){const i=t.querySelector(".add-remove-icon").childNodes[0];e=e||t.getAttribute("data-value"),s=s||t.getAttribute("data-color"),a=a||i.textContent,t.childNodes[0].textContent=e,t.setAttribute("data-value",e),t.setAttribute("data-color",s);const n=("#"===(o=s).slice(0,1)&&(o=o.slice(1)),3===o.length&&(o=o.split("").map(function(t){return t+t}).join("")),(299*parseInt(o.substr(0,2),16)+587*parseInt(o.substr(2,2),16)+114*parseInt(o.substr(4,2),16))/1e3>=128?"#101010":"#EFEFEF");var o;t.setAttribute("style",`background-color: ${s}; color: ${n};`),i.textContent=a}createNewTag(t,e,s){const a=this.prototype.cloneNode(!0);return a.classList.remove("prototype-tag"),a.classList.add("tag"),this.updateTag(a,t,e,s),a}insertTag(t,e){t.appendChild(e),this.callTagListUpdatedListeners()}}class a{constructor(t,e,s){this.widget=t,this.taggingBase=e,this.apiTagsUrl=t.getAttribute("data-tags-url"),this.createTagInputs=t.querySelector(".create-tag-inputs"),this.addTagInput=this.createTagInputs.querySelector('input[type="text"]'),this.addTagButton=this.createTagInputs.querySelector(".btn-add-new-tag"),this.addTagInput.value="",this.onInsertNewTag=s,this.addTagButton.addEventListener("click",()=>this.onCreateTagClicked()),this.addTagInput.addEventListener("keydown",t=>{"enter"===t.key.toLowerCase()&&(t.preventDefault(),this.onCreateTagClicked())})}onCreateTagClicked(){const t=this.addTagInput.value.trim(),s=encodeURIComponent(t),a=t=>{this.addTagInput.select(),this.taggingBase.showModal(t||"generic")};if(!t)return void a("invalid-tag-name");const i=(t,e)=>{const s=this.taggingBase.createNewTag(t,e,"-");this.addTagInput.value="",this.onInsertNewTag(s)},n=JSON.stringify({name:this.addTagInput.value});e("GET",`${this.apiTagsUrl}?name=${s}`,null,t=>{const s=JSON.parse(t);if(s.count){const t=s.results[0];i(t.name,t.color)}else e("POST",this.apiTagsUrl,n,t=>{const e=JSON.parse(t);i(e.name,e.color)},()=>a("tag-creation-failed"))},()=>a("tag-checking-failed"))}}class i{constructor(t){this.widget=t,this.taggingBase=new s(this.widget),this.addTagControl=new a(this.widget,this.taggingBase,t=>this.insertNewTag(t)),this.currentTags=this.widget.querySelector(".current_tags"),this.newTags=this.widget.querySelector(".new-tags"),this.inputElement=this.widget.querySelector('input[type="hidden"]');for(const t of this.newTags.querySelectorAll(".tag"))this.configureAddTag(t);for(const t of this.currentTags.querySelectorAll(".tag"))this.configureRemoveTag(t);this.updateInputList(),this.taggingBase.addTagListUpdatedListener(()=>this.updateInputList())}insertNewTag(t){const e=t.getAttribute("data-value"),s=this.widget.querySelector(`span[data-value="${e}"]`);s&&s.parentNode.removeChild(s),this.taggingBase.insertTag(this.currentTags,t),this.configureRemoveTag(t)}registerNewCallback(t,e,s){t.addEventListener("click",function a(i){t.parentNode.removeChild(t),this.taggingBase.insertTag(e,t),t.removeEventListener("click",a),s(t)}.bind(this))}updateInputList(){const t=[];for(const e of this.currentTags.querySelectorAll(".tag")){const s=e.getAttribute("data-value");t.push(`"${s}"`)}this.inputElement.value=t.join(",")}configureAddTag(t){this.taggingBase.updateTag(t,null,null,"+"),this.registerNewCallback(t,this.currentTags,()=>this.configureRemoveTag(t)),this.updateInputList()}configureRemoveTag(t){this.taggingBase.updateTag(t,null,null,"-"),this.registerNewCallback(t,this.newTags,()=>this.configureAddTag(t)),this.updateInputList()}}window.addEventListener("load",()=>{for(const t of document.querySelectorAll(".babybuddy-tags-editor"))new i(t)})}();
|
Binary file not shown.
|
@ -1,354 +1 @@
|
||||||
(function() {
|
!function(){const t=document.querySelector('input[name="csrfmiddlewaretoken"]').value;function e(e,s,a,i,n){const o=new XMLHttpRequest;o.addEventListener("load",()=>{o.status>=200&&o.status<300?i(o.responseText,o):n(o.responseText,o)});for(const t of["error","timeout","abort"])o.addEventListener(t,()=>{n(o.responseText,o)});o.timeout=2e4,o.open(e,s),o.setRequestHeader("Content-Type","application/json"),o.setRequestHeader("Accept","application/json"),o.setRequestHeader("X-CSRFTOKEN",t),o.send(a)}class s{constructor(t){this.prototype=t.querySelector(".prototype-tag"),this.listeners=[],this.modalElement=t.querySelector(".tag-editor-error-modal"),this.modalBodyNode=this.modalElement.querySelector(".modal-body");for(const t of this.modalBodyNode.childNodes)t.nodeType===Node.TEXT_NODE&&this.modalBodyNode.removeChild(t)}showModal(t){const e=this.modalBodyNode.querySelector(`span[data-message='${t}']`);e||(e=this.modalBodyNode.childNodes[0]);for(const t of this.modalBodyNode.childNodes)t.classList.add("d-none");e.classList.remove("d-none"),jQuery(this.modalElement).modal("show")}addTagListUpdatedListener(t){this.listeners.push(t)}callTagListUpdatedListeners(){for(const t of this.listeners)t()}updateTag(t,e,s,a){const i=t.querySelector(".add-remove-icon").childNodes[0];e=e||t.getAttribute("data-value"),s=s||t.getAttribute("data-color"),a=a||i.textContent,t.childNodes[0].textContent=e,t.setAttribute("data-value",e),t.setAttribute("data-color",s);const n=("#"===(o=s).slice(0,1)&&(o=o.slice(1)),3===o.length&&(o=o.split("").map(function(t){return t+t}).join("")),(299*parseInt(o.substr(0,2),16)+587*parseInt(o.substr(2,2),16)+114*parseInt(o.substr(4,2),16))/1e3>=128?"#101010":"#EFEFEF");var o;t.setAttribute("style",`background-color: ${s}; color: ${n};`),i.textContent=a}createNewTag(t,e,s){const a=this.prototype.cloneNode(!0);return a.classList.remove("prototype-tag"),a.classList.add("tag"),this.updateTag(a,t,e,s),a}insertTag(t,e){t.appendChild(e),this.callTagListUpdatedListeners()}}class a{constructor(t,e,s){this.widget=t,this.taggingBase=e,this.apiTagsUrl=t.getAttribute("data-tags-url"),this.createTagInputs=t.querySelector(".create-tag-inputs"),this.addTagInput=this.createTagInputs.querySelector('input[type="text"]'),this.addTagButton=this.createTagInputs.querySelector(".btn-add-new-tag"),this.addTagInput.value="",this.onInsertNewTag=s,this.addTagButton.addEventListener("click",()=>this.onCreateTagClicked()),this.addTagInput.addEventListener("keydown",t=>{"enter"===t.key.toLowerCase()&&(t.preventDefault(),this.onCreateTagClicked())})}onCreateTagClicked(){const t=this.addTagInput.value.trim(),s=encodeURIComponent(t),a=t=>{this.addTagInput.select(),this.taggingBase.showModal(t||"generic")};if(!t)return void a("invalid-tag-name");const i=(t,e)=>{const s=this.taggingBase.createNewTag(t,e,"-");this.addTagInput.value="",this.onInsertNewTag(s)},n=JSON.stringify({name:this.addTagInput.value});e("GET",`${this.apiTagsUrl}?name=${s}`,null,t=>{const s=JSON.parse(t);if(s.count){const t=s.results[0];i(t.name,t.color)}else e("POST",this.apiTagsUrl,n,t=>{const e=JSON.parse(t);i(e.name,e.color)},()=>a("tag-creation-failed"))},()=>a("tag-checking-failed"))}}class i{constructor(t){this.widget=t,this.taggingBase=new s(this.widget),this.addTagControl=new a(this.widget,this.taggingBase,t=>this.insertNewTag(t)),this.currentTags=this.widget.querySelector(".current_tags"),this.newTags=this.widget.querySelector(".new-tags"),this.inputElement=this.widget.querySelector('input[type="hidden"]');for(const t of this.newTags.querySelectorAll(".tag"))this.configureAddTag(t);for(const t of this.currentTags.querySelectorAll(".tag"))this.configureRemoveTag(t);this.updateInputList(),this.taggingBase.addTagListUpdatedListener(()=>this.updateInputList())}insertNewTag(t){const e=t.getAttribute("data-value"),s=this.widget.querySelector(`span[data-value="${e}"]`);s&&s.parentNode.removeChild(s),this.taggingBase.insertTag(this.currentTags,t),this.configureRemoveTag(t)}registerNewCallback(t,e,s){t.addEventListener("click",function a(i){t.parentNode.removeChild(t),this.taggingBase.insertTag(e,t),t.removeEventListener("click",a),s(t)}.bind(this))}updateInputList(){const t=[];for(const e of this.currentTags.querySelectorAll(".tag")){const s=e.getAttribute("data-value");t.push(`"${s}"`)}this.inputElement.value=t.join(",")}configureAddTag(t){this.taggingBase.updateTag(t,null,null,"+"),this.registerNewCallback(t,this.currentTags,()=>this.configureRemoveTag(t)),this.updateInputList()}configureRemoveTag(t){this.taggingBase.updateTag(t,null,null,"-"),this.registerNewCallback(t,this.newTags,()=>this.configureAddTag(t)),this.updateInputList()}}window.addEventListener("load",()=>{for(const t of document.querySelectorAll(".babybuddy-tags-editor"))new i(t)})}();
|
||||||
/**
|
|
||||||
* Parse a string as hexadecimal number
|
|
||||||
*/
|
|
||||||
function hexParse(x) {
|
|
||||||
return parseInt(x, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the contrasting color for any hex color
|
|
||||||
*
|
|
||||||
* Sourced from: https://vanillajstoolkit.com/helpers/getcontrast/
|
|
||||||
* - Modified with slightly softer colors
|
|
||||||
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
|
|
||||||
* Derived from work by Brian Suda, https://24ways.org/2010/calculating-color-contrast/
|
|
||||||
* @param {String} A hexcolor value
|
|
||||||
* @return {String} The contrasting color (black or white)
|
|
||||||
*/
|
|
||||||
function computeComplementaryColor(hexcolor) {
|
|
||||||
|
|
||||||
// If a leading # is provided, remove it
|
|
||||||
if (hexcolor.slice(0, 1) === '#') {
|
|
||||||
hexcolor = hexcolor.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a three-character hexcode, make six-character
|
|
||||||
if (hexcolor.length === 3) {
|
|
||||||
hexcolor = hexcolor.split('').map(function (hex) {
|
|
||||||
return hex + hex;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to RGB value
|
|
||||||
let r = parseInt(hexcolor.substr(0,2),16);
|
|
||||||
let g = parseInt(hexcolor.substr(2,2),16);
|
|
||||||
let b = parseInt(hexcolor.substr(4,2),16);
|
|
||||||
|
|
||||||
// Get YIQ ratio
|
|
||||||
let yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
|
|
||||||
|
|
||||||
// Check contrast
|
|
||||||
return (yiq >= 128) ? '#101010' : '#EFEFEF';
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSRF token should always be present because it is auto-included with
|
|
||||||
// every tag-editor widget
|
|
||||||
const CSRF_TOKEN = document.querySelector('input[name="csrfmiddlewaretoken"]').value;
|
|
||||||
|
|
||||||
function doReq(method, uri, data, success, fail) {
|
|
||||||
// TODO: prefer jQuery based requests for now
|
|
||||||
|
|
||||||
const req = new XMLHttpRequest();
|
|
||||||
req.addEventListener('load', () => {
|
|
||||||
if ((req.status >= 200) && (req.status < 300)) {
|
|
||||||
success(req.responseText, req);
|
|
||||||
} else {
|
|
||||||
fail(req.responseText, req);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (const name of ["error", "timeout", "abort"]) {
|
|
||||||
req.addEventListener(name, () => {
|
|
||||||
fail(req.responseText, req);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
req.timeout = 20000;
|
|
||||||
|
|
||||||
req.open(method, uri);
|
|
||||||
req.setRequestHeader("Content-Type", "application/json");
|
|
||||||
req.setRequestHeader("Accept", "application/json");
|
|
||||||
req.setRequestHeader("X-CSRFTOKEN", CSRF_TOKEN);
|
|
||||||
req.send(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class allowing generic operations on the tag lists, like:
|
|
||||||
*
|
|
||||||
* - Adding tags to a tag list
|
|
||||||
* - Updating or creating new tags with a set name and color
|
|
||||||
* - Controlling the error modal
|
|
||||||
*/
|
|
||||||
class TaggingBase {
|
|
||||||
constructor(widget) {
|
|
||||||
this.prototype = widget.querySelector('.prototype-tag');
|
|
||||||
this.listeners = [];
|
|
||||||
|
|
||||||
this.modalElement = widget.querySelector('.tag-editor-error-modal');
|
|
||||||
this.modalBodyNode = this.modalElement.querySelector('.modal-body');
|
|
||||||
|
|
||||||
// Clean whitespace text nodes between spans
|
|
||||||
for (const n of this.modalBodyNode.childNodes) {
|
|
||||||
if (n.nodeType === Node.TEXT_NODE) {
|
|
||||||
this.modalBodyNode.removeChild(n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showModal(msg) {
|
|
||||||
const selectedMessage = this.modalBodyNode.querySelector(`span[data-message='${msg}']`);
|
|
||||||
if (!selectedMessage) {
|
|
||||||
selectedMessage = this.modalBodyNode.childNodes[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const n of this.modalBodyNode.childNodes) {
|
|
||||||
n.classList.add('d-none');
|
|
||||||
}
|
|
||||||
selectedMessage.classList.remove('d-none');
|
|
||||||
|
|
||||||
jQuery(this.modalElement).modal('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
addTagListUpdatedListener(c) {
|
|
||||||
this.listeners.push(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
callTagListUpdatedListeners() {
|
|
||||||
for (const l of this.listeners) {
|
|
||||||
l();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTag(tag, name, color, actionSymbol) {
|
|
||||||
const actionTextNode = tag.querySelector('.add-remove-icon').childNodes[0];
|
|
||||||
|
|
||||||
name = name || tag.getAttribute("data-value");
|
|
||||||
color = color || tag.getAttribute("data-color");
|
|
||||||
actionSymbol = actionSymbol || actionTextNode.textContent;
|
|
||||||
|
|
||||||
tag.childNodes[0].textContent = name;
|
|
||||||
tag.setAttribute("data-value", name);
|
|
||||||
tag.setAttribute("data-color", color);
|
|
||||||
|
|
||||||
const textColor = computeComplementaryColor(color);
|
|
||||||
tag.setAttribute('style', `background-color: ${color}; color: ${textColor};`);
|
|
||||||
actionTextNode.textContent = actionSymbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
createNewTag(name, color, actionSymbol) {
|
|
||||||
const tag = this.prototype.cloneNode(true);
|
|
||||||
tag.classList.remove("prototype-tag");
|
|
||||||
tag.classList.add("tag");
|
|
||||||
this.updateTag(tag, name, color, actionSymbol);
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertTag(list, tag) {
|
|
||||||
list.appendChild(tag);
|
|
||||||
this.callTagListUpdatedListeners();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for the edit field allowing to dynamically create new tags.
|
|
||||||
*
|
|
||||||
* Handles user inputs for the editor. Calls the 'onInsertNewTag' callback
|
|
||||||
* when the creation of a new tag has been requested. All backend handling
|
|
||||||
* like guareteening that the requested tag exists is handled by this class,
|
|
||||||
* the only task left is to add the new tag to the tags-list when
|
|
||||||
* 'onInsertNewTag' is called.
|
|
||||||
*/
|
|
||||||
class AddNewTagControl {
|
|
||||||
/**
|
|
||||||
* @param widget
|
|
||||||
* The root DOM element of the widget
|
|
||||||
* @param taggingBase
|
|
||||||
* Reference to a common TaggingBase class to be used by this widget
|
|
||||||
* @param onInsertNewTag
|
|
||||||
* Callback that is called when a new tag should be added to the
|
|
||||||
* tags widget.
|
|
||||||
*/
|
|
||||||
constructor(widget, taggingBase, onInsertNewTag) {
|
|
||||||
this.widget = widget;
|
|
||||||
this.taggingBase = taggingBase;
|
|
||||||
|
|
||||||
this.apiTagsUrl = widget.getAttribute('data-tags-url');
|
|
||||||
this.createTagInputs = widget.querySelector('.create-tag-inputs');
|
|
||||||
this.addTagInput = this.createTagInputs.querySelector('input[type="text"]');
|
|
||||||
this.addTagButton = this.createTagInputs.querySelector('.btn-add-new-tag');
|
|
||||||
|
|
||||||
this.addTagInput.value = "";
|
|
||||||
|
|
||||||
this.onInsertNewTag = onInsertNewTag;
|
|
||||||
|
|
||||||
this.addTagButton.addEventListener('click', () => this.onCreateTagClicked());
|
|
||||||
this.addTagInput.addEventListener('keydown', (e) => {
|
|
||||||
const key = e.key.toLowerCase();
|
|
||||||
if (key === "enter") {
|
|
||||||
e.preventDefault();
|
|
||||||
this.onCreateTagClicked();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback called when the the "Add" button of the add-tag input is
|
|
||||||
* clicked or enter is pressed in the editor.
|
|
||||||
*/
|
|
||||||
onCreateTagClicked() {
|
|
||||||
// TODO: Make promise based
|
|
||||||
|
|
||||||
const tagName = this.addTagInput.value.trim();
|
|
||||||
const uriTagName = encodeURIComponent(tagName);
|
|
||||||
|
|
||||||
const fail = (msg) => {
|
|
||||||
this.addTagInput.select();
|
|
||||||
this.taggingBase.showModal(msg || "generic");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!tagName) {
|
|
||||||
fail('invalid-tag-name');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addTag = (name, color) => {
|
|
||||||
const tag = this.taggingBase.createNewTag(name, color, "-");
|
|
||||||
this.addTagInput.value = "";
|
|
||||||
this.onInsertNewTag(tag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = JSON.stringify({
|
|
||||||
'name': this.addTagInput.value
|
|
||||||
});
|
|
||||||
|
|
||||||
doReq("GET", `${this.apiTagsUrl}?name=${uriTagName}`, null,
|
|
||||||
(text) => {
|
|
||||||
const json = JSON.parse(text);
|
|
||||||
if (json.count) {
|
|
||||||
const tagJson = json.results[0];
|
|
||||||
addTag(tagJson.name, tagJson.color);
|
|
||||||
} else {
|
|
||||||
doReq("POST", this.apiTagsUrl, data,
|
|
||||||
(text) => {
|
|
||||||
const tagJson = JSON.parse(text);
|
|
||||||
addTag(tagJson.name, tagJson.color);
|
|
||||||
}, () => fail("tag-creation-failed")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, () => fail("tag-checking-failed")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JavaScript implementation for the tags editor.
|
|
||||||
*
|
|
||||||
* This class uses TaggingBase and AddNewTagControl to provide the custom
|
|
||||||
* tag editor controls. This mainly consists of updating the hidden
|
|
||||||
* input values with the current list of tags and adding/removing
|
|
||||||
* tags from the current-tags- or recently-used-lists.
|
|
||||||
*/
|
|
||||||
class TagsEditor {
|
|
||||||
/**
|
|
||||||
* @param tagEditorRoot
|
|
||||||
* The root DOM element of the widget.
|
|
||||||
*/
|
|
||||||
constructor(tagEditorRoot) {
|
|
||||||
this.widget = tagEditorRoot;
|
|
||||||
this.taggingBase = new TaggingBase(this.widget);
|
|
||||||
this.addTagControl = new AddNewTagControl(
|
|
||||||
this.widget, this.taggingBase, (t) => this.insertNewTag(t)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.currentTags = this.widget.querySelector('.current_tags');
|
|
||||||
this.newTags = this.widget.querySelector('.new-tags');
|
|
||||||
this.inputElement = this.widget.querySelector('input[type="hidden"]');
|
|
||||||
|
|
||||||
for (const tag of this.newTags.querySelectorAll(".tag")) {
|
|
||||||
this.configureAddTag(tag);
|
|
||||||
}
|
|
||||||
for (const tag of this.currentTags.querySelectorAll(".tag")) {
|
|
||||||
this.configureRemoveTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateInputList();
|
|
||||||
this.taggingBase.addTagListUpdatedListener(
|
|
||||||
() => this.updateInputList()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a new tag into the "current tag" list.
|
|
||||||
*
|
|
||||||
* Makes sure that no duplicates are present in the widget before adding
|
|
||||||
* the new tag. If a duplicate is found, the old tag is removed before
|
|
||||||
* the new one is added.
|
|
||||||
*/
|
|
||||||
insertNewTag(tag) {
|
|
||||||
const name = tag.getAttribute("data-value");
|
|
||||||
|
|
||||||
const oldTag = this.widget.querySelector(`span[data-value="${name}"]`);
|
|
||||||
if (oldTag) {
|
|
||||||
oldTag.parentNode.removeChild(oldTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taggingBase.insertTag(this.currentTags, tag);
|
|
||||||
this.configureRemoveTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registeres a click-callback for a given node.
|
|
||||||
*
|
|
||||||
* The callback chain-calls another callback "onClicked" after
|
|
||||||
* moving the clicked tag from the old tag-list to a new tag list.
|
|
||||||
*/
|
|
||||||
registerNewCallback(tag, newParent, onClicked) {
|
|
||||||
function callback(event) {
|
|
||||||
tag.parentNode.removeChild(tag);
|
|
||||||
this.taggingBase.insertTag(newParent, tag);
|
|
||||||
|
|
||||||
tag.removeEventListener('click', callback);
|
|
||||||
onClicked(tag);
|
|
||||||
}
|
|
||||||
tag.addEventListener('click', callback.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the value of the hidden input element.
|
|
||||||
*
|
|
||||||
* Sets the value from the list of tags added to the currentTags
|
|
||||||
* DOM element.
|
|
||||||
*/
|
|
||||||
updateInputList() {
|
|
||||||
const names = [];
|
|
||||||
for (const tag of this.currentTags.querySelectorAll(".tag")) {
|
|
||||||
const name = tag.getAttribute("data-value");
|
|
||||||
names.push(`"${name}"`);
|
|
||||||
}
|
|
||||||
this.inputElement.value = names.join(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a tag-DOM element as a "add tag" button.
|
|
||||||
*/
|
|
||||||
configureAddTag(tag) {
|
|
||||||
this.taggingBase.updateTag(tag, null, null, "+");
|
|
||||||
this.registerNewCallback(tag, this.currentTags, () => this.configureRemoveTag(tag));
|
|
||||||
this.updateInputList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure a tag-DOM element as a "remove tag" button.
|
|
||||||
*/
|
|
||||||
configureRemoveTag(tag) {
|
|
||||||
this.taggingBase.updateTag(tag, null, null, "-");
|
|
||||||
this.registerNewCallback(tag, this.newTags, () => this.configureAddTag(tag));
|
|
||||||
this.updateInputList();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
for (const el of document.querySelectorAll('.babybuddy-tags-editor')) {
|
|
||||||
new TagsEditor(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue