diff --git a/appinfo/info.xml b/appinfo/info.xml index 48ba83d..476c1ec 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -43,4 +43,8 @@ _[View changelog](https://github.com/te-online/timemanager/blob/main/CHANGELOG.m 4 - \ No newline at end of file + + OCA\TimeManager\Settings\TimeManagerAdmin + OCA\TimeManager\Sections\TimeManagerAdmin + + diff --git a/appinfo/routes.php b/appinfo/routes.php index a24eaca..5755014 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -24,12 +24,12 @@ ["name" => "page#deleteTime", "url" => "/times/delete", "verb" => "POST"], ["name" => "page#payTime", "url" => "/times/paid", "verb" => "POST"], ["name" => "page#unpayTime", "url" => "/times/unpaid", "verb" => "POST"], - ["name" => "page#updateSettings", "url" => "/settings", "verb" => "POST"], ["name" => "page#tools", "url" => "/tools", "verb" => "GET"], ["name" => "t_api#get", "url" => "/api/items", "verb" => "GET"], ["name" => "t_api#post", "url" => "/api/items", "verb" => "POST"], ["name" => "t_api#updateObjects", "url" => "/api/updateObjects", "verb" => "POST"], ["name" => "t_api#updateObjectsFromWeb", "url" => "/api/sync-web", "verb" => "POST"], ["name" => "t_api#getHoursInPeriodStats", "url" => "/api/hoursInPeriod", "verb" => "GET"], + ["name" => "settings#saveSettings", "url" => "/api/settings", "verb" => "POST"], ], ]; diff --git a/img/timemanager-dark.svg b/img/timemanager-dark.svg new file mode 100644 index 0000000..142e259 --- /dev/null +++ b/img/timemanager-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/js/bundle.js b/js/bundle.js index 9f57a8a..b9f0ef3 100644 --- a/js/bundle.js +++ b/js/bundle.js @@ -907,13 +907,13 @@ var id = 0; var postfix = Math.random(); var toString$2 = functionUncurryThis(1.0.toString); - var uid = function (key) { + var uid$1 = function (key) { return 'Symbol(' + (key === undefined ? '' : key) + ')_' + toString$2(++id + postfix, 36); }; var Symbol$2 = global_1.Symbol; var WellKnownSymbolsStore = shared('wks'); - var createWellKnownSymbol = useSymbolAsUid ? Symbol$2['for'] || Symbol$2 : Symbol$2 && Symbol$2.withoutSetter || uid; + var createWellKnownSymbol = useSymbolAsUid ? Symbol$2['for'] || Symbol$2 : Symbol$2 && Symbol$2.withoutSetter || uid$1; var wellKnownSymbol = function (name) { if (!hasOwnProperty_1(WellKnownSymbolsStore, name)) { WellKnownSymbolsStore[name] = symbolConstructorDetection && hasOwnProperty_1(Symbol$2, name) ? Symbol$2[name] : createWellKnownSymbol('Symbol.' + name); @@ -1079,7 +1079,7 @@ var keys$1 = shared('keys'); var sharedKey = function (key) { - return keys$1[key] || (keys$1[key] = uid(key)); + return keys$1[key] || (keys$1[key] = uid$1(key)); }; var hiddenKeys$1 = {}; @@ -14555,12 +14555,12 @@ function get_each_context$8(ctx, list, i) { var child_ctx = ctx.slice(); - child_ctx[25] = list[i]; - child_ctx[27] = i; + child_ctx[26] = list[i]; + child_ctx[28] = i; return child_ctx; } - // (227:0) {#if controls} + // (228:0) {#if controls} function create_if_block_7$2(ctx) { var h2; return { @@ -14578,7 +14578,7 @@ }; } - // (231:1) {#if controls} + // (232:1) {#if controls} function create_if_block_6$3(ctx) { var div; var figure0; @@ -14646,7 +14646,7 @@ }; } - // (247:3) {#if !loading && weekTotal > 0} + // (248:3) {#if !loading && weekTotal > 0} function create_if_block_3$3(ctx) { var each_1_anchor; var each_value = /*points*/ctx[3]; @@ -14696,18 +14696,18 @@ }; } - // (250:6) {#if point && point.stats} + // (251:6) {#if point && point.stats} function create_if_block_4$3(ctx) { var t0; var div; var span0; - var t1_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[25].date, "primary") + ""; + var t1_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[26].date, "primary") + ""; var t1; var t2; var span1; - var t3_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[25].date, "secondary") + ""; + var t3_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[26].date, "secondary") + ""; var t3; - var if_block = /*point*/ctx[25].stats.total > 0 && create_if_block_5$3(ctx); + var if_block = /*point*/ctx[26].stats.total > 0 && create_if_block_5$3(ctx); return { c: function c() { if (if_block) if_block.c(); @@ -14733,7 +14733,7 @@ append(span1, t3); }, p: function p(ctx, dirty) { - if ( /*point*/ctx[25].stats.total > 0) { + if ( /*point*/ctx[26].stats.total > 0) { if (if_block) { if_block.p(ctx, dirty); } else { @@ -14745,8 +14745,8 @@ if_block.d(1); if_block = null; } - if (dirty & /*points*/8 && t1_value !== (t1_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[25].date, "primary") + "")) set_data(t1, t1_value); - if (dirty & /*points*/8 && t3_value !== (t3_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[25].date, "secondary") + "")) set_data(t3, t3_value); + if (dirty & /*points*/8 && t1_value !== (t1_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[26].date, "primary") + "")) set_data(t1, t1_value); + if (dirty & /*points*/8 && t3_value !== (t3_value = /*formatDateForScale*/ctx[11]( /*point*/ctx[26].date, "secondary") + "")) set_data(t3, t3_value); }, d: function d(detaching) { if (if_block) if_block.d(detaching); @@ -14756,10 +14756,10 @@ }; } - // (251:7) {#if point.stats.total > 0} + // (252:7) {#if point.stats.total > 0} function create_if_block_5$3(ctx) { var span; - var t0_value = /*point*/ctx[25].stats.total + ""; + var t0_value = /*point*/ctx[26].stats.total + ""; var t0; var t1; var t2_value = translate_1("timemanager", "hrs.") + ""; @@ -14777,7 +14777,7 @@ div = element("div"); attr(span, "class", "hours-label"); attr(div, "class", "column-inner"); - attr(div, "style", div_style_value = "height: ".concat( /*point*/ctx[25].stats.total / /*highest*/ctx[6] * 100, "%")); + attr(div, "style", div_style_value = "height: ".concat( /*point*/ctx[26].stats.total / /*highest*/ctx[6] * 100, "%")); }, m: function m(target, anchor) { insert(target, span, anchor); @@ -14788,8 +14788,8 @@ insert(target, div, anchor); }, p: function p(ctx, dirty) { - if (dirty & /*points*/8 && t0_value !== (t0_value = /*point*/ctx[25].stats.total + "")) set_data(t0, t0_value); - if (dirty & /*points, highest*/72 && div_style_value !== (div_style_value = "height: ".concat( /*point*/ctx[25].stats.total / /*highest*/ctx[6] * 100, "%"))) { + if (dirty & /*points*/8 && t0_value !== (t0_value = /*point*/ctx[26].stats.total + "")) set_data(t0, t0_value); + if (dirty & /*points, highest*/72 && div_style_value !== (div_style_value = "height: ".concat( /*point*/ctx[26].stats.total / /*highest*/ctx[6] * 100, "%"))) { attr(div, "style", div_style_value); } }, @@ -14801,11 +14801,11 @@ }; } - // (248:4) {#each points as point, index} + // (249:4) {#each points as point, index} function create_each_block$8(ctx) { var div; var t; - var if_block = /*point*/ctx[25] && /*point*/ctx[25].stats && create_if_block_4$3(ctx); + var if_block = /*point*/ctx[26] && /*point*/ctx[26].stats && create_if_block_4$3(ctx); return { c: function c() { div = element("div"); @@ -14819,7 +14819,7 @@ append(div, t); }, p: function p(ctx, dirty) { - if ( /*point*/ctx[25] && /*point*/ctx[25].stats) { + if ( /*point*/ctx[26] && /*point*/ctx[26].stats) { if (if_block) { if_block.p(ctx, dirty); } else { @@ -14839,7 +14839,7 @@ }; } - // (263:3) {#if controls && !loading && weekTotal === 0} + // (264:3) {#if controls && !loading && weekTotal === 0} function create_if_block_2$6(ctx) { var p; return { @@ -14858,7 +14858,7 @@ }; } - // (267:2) {#if controls} + // (268:2) {#if controls} function create_if_block$j(ctx) { var nav; var button0; @@ -14934,7 +14934,7 @@ append(span2, t12); append(span2, button1); if (!mounted) { - dispose = [listen(button0, "click", prevent_default( /*click_handler*/ctx[17])), listen(button1, "click", prevent_default( /*click_handler_2*/ctx[19]))]; + dispose = [listen(button0, "click", prevent_default( /*click_handler*/ctx[18])), listen(button1, "click", prevent_default( /*click_handler_2*/ctx[20]))]; mounted = true; } }, @@ -14965,7 +14965,7 @@ }; } - // (284:5) {#if !isSameDay(startOfWeek(startOfToday(), localeOptions), startCursor)} + // (285:5) {#if !isSameDay(startOfWeek(startOfToday(), localeOptions), startCursor)} function create_if_block_1$7(ctx) { var button; var mounted; @@ -14979,7 +14979,7 @@ m: function m(target, anchor) { insert(target, button, anchor); if (!mounted) { - dispose = listen(button, "click", prevent_default( /*click_handler_1*/ctx[18])); + dispose = listen(button, "click", prevent_default( /*click_handler_1*/ctx[19])); mounted = true; } }, @@ -15139,6 +15139,8 @@ controls = _$$props$controls === void 0 ? true : _$$props$controls; var _$$props$includeShare = $$props.includeShared, includeShared = _$$props$includeShare === void 0 ? false : _$$props$includeShare; + var _$$props$includeRepor = $$props.includeReporter, + includeReporter = _$$props$includeRepor === void 0 ? false : _$$props$includeRepor; var simpleRounding = Helpers.simpleRounding; var localeOptions = Helpers.getDateLocaleOptions(); var _$$props$start = $$props.start, @@ -15260,7 +15262,7 @@ case 0: start = format$2(Helpers.toUTC(startOfDay(startCursor)), apiDateFormat); end = format$2(Helpers.toUTC(endOfDay(endCursor)), apiDateFormat); - statUrl = "".concat(statsApiUrl, "?start=").concat(start, "&end=").concat(end, "&group_by=").concat(scale, "&shared=").concat(includeShared ? 1 : 0); // Parse current URL for filters + statUrl = "".concat(statsApiUrl, "?start=").concat(start, "&end=").concat(end, "&group_by=").concat(scale, "&shared=").concat(includeShared ? 1 : 0, "&reporter=").concat(includeReporter ? 1 : 0); // Parse current URL for filters urlParts = document.location.href.split("?"); if (urlParts.length > 1) { queryString = urlParts[1]; @@ -15368,14 +15370,15 @@ if ('requestToken' in $$props) $$invalidate(13, requestToken = $$props.requestToken); if ('controls' in $$props) $$invalidate(0, controls = $$props.controls); if ('includeShared' in $$props) $$invalidate(14, includeShared = $$props.includeShared); - if ('start' in $$props) $$invalidate(15, start = $$props.start); - if ('end' in $$props) $$invalidate(16, end = $$props.end); + if ('includeReporter' in $$props) $$invalidate(15, includeReporter = $$props.includeReporter); + if ('start' in $$props) $$invalidate(16, start = $$props.start); + if ('end' in $$props) $$invalidate(17, end = $$props.end); }; $$self.$$.update = function () { - if ($$self.$$.dirty & /*start*/32768) { + if ($$self.$$.dirty & /*start*/65536) { $$invalidate(1, startCursor = isDate$1(parse$3(start, dateFormat$4, new Date())) ? parse$3(start, dateFormat$4, new Date()) : startOfWeek(new Date(), localeOptions)); } - if ($$self.$$.dirty & /*end*/65536) { + if ($$self.$$.dirty & /*end*/131072) { endCursor = isDate$1(parse$3(end, dateFormat$4, new Date())) ? parse$3(end, dateFormat$4, new Date()) : endOfWeek(new Date(), localeOptions); } }; @@ -15386,7 +15389,7 @@ $$invalidate(4, todayTotal = 0); $$invalidate(6, highest = 0); $$invalidate(7, currentWeek = null); - return [controls, startCursor, loading, points, todayTotal, weekTotal, highest, currentWeek, simpleRounding, localeOptions, weekNavigation, formatDateForScale, statsApiUrl, requestToken, includeShared, start, end, click_handler, click_handler_1, click_handler_2]; + return [controls, startCursor, loading, points, todayTotal, weekTotal, highest, currentWeek, simpleRounding, localeOptions, weekNavigation, formatDateForScale, statsApiUrl, requestToken, includeShared, includeReporter, start, end, click_handler, click_handler_1, click_handler_2]; } var Statistics = /*#__PURE__*/function (_SvelteComponent) { _inherits$1(Statistics, _SvelteComponent); @@ -15400,8 +15403,9 @@ requestToken: 13, controls: 0, includeShared: 14, - start: 15, - end: 16 + includeReporter: 15, + start: 16, + end: 17 }); return _this; } @@ -18310,6 +18314,155 @@ } }); + var $TypeError = TypeError; + var deletePropertyOrThrow = function (O, P) { + if (!delete O[P]) throw $TypeError('Cannot delete property ' + tryToString(P) + ' of ' + tryToString(O)); + }; + + var $Array = Array; + var max$1 = Math.max; + var arraySliceSimple = function (O, start, end) { + var length = lengthOfArrayLike(O); + var k = toAbsoluteIndex(start, length); + var fin = toAbsoluteIndex(end === undefined ? length : end, length); + var result = $Array(max$1(fin - k, 0)); + for (var n = 0; k < fin; k++, n++) createProperty(result, n, O[k]); + result.length = n; + return result; + }; + + var floor = Math.floor; + var mergeSort = function (array, comparefn) { + var length = array.length; + var middle = floor(length / 2); + return length < 8 ? insertionSort(array, comparefn) : merge(array, mergeSort(arraySliceSimple(array, 0, middle), comparefn), mergeSort(arraySliceSimple(array, middle), comparefn), comparefn); + }; + var insertionSort = function (array, comparefn) { + var length = array.length; + var i = 1; + var element, j; + while (i < length) { + j = i; + element = array[i]; + while (j && comparefn(array[j - 1], element) > 0) { + array[j] = array[--j]; + } + if (j !== i++) array[j] = element; + } + return array; + }; + var merge = function (array, left, right, comparefn) { + var llength = left.length; + var rlength = right.length; + var lindex = 0; + var rindex = 0; + while (lindex < llength || rindex < rlength) { + array[lindex + rindex] = lindex < llength && rindex < rlength ? comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++] : lindex < llength ? left[lindex++] : right[rindex++]; + } + return array; + }; + var arraySort = mergeSort; + + var firefox = engineUserAgent.match(/firefox\/(\d+)/i); + var engineFfVersion = !!firefox && +firefox[1]; + + var engineIsIeOrEdge = /MSIE|Trident/.test(engineUserAgent); + + var webkit = engineUserAgent.match(/AppleWebKit\/(\d+)\./); + var engineWebkitVersion = !!webkit && +webkit[1]; + + var test = []; + var nativeSort = functionUncurryThis(test.sort); + var push$1 = functionUncurryThis(test.push); + + // IE8- + var FAILS_ON_UNDEFINED = fails(function () { + test.sort(undefined); + }); + // V8 bug + var FAILS_ON_NULL = fails(function () { + test.sort(null); + }); + // Old WebKit + var STRICT_METHOD = arrayMethodIsStrict('sort'); + var STABLE_SORT = !fails(function () { + // feature detection can be too slow, so check engines versions + if (engineV8Version) return engineV8Version < 70; + if (engineFfVersion && engineFfVersion > 3) return; + if (engineIsIeOrEdge) return true; + if (engineWebkitVersion) return engineWebkitVersion < 603; + var result = ''; + var code, chr, value, index; + + // generate an array with more 512 elements (Chakra and old V8 fails only in this case) + for (code = 65; code < 76; code++) { + chr = String.fromCharCode(code); + switch (code) { + case 66: + case 69: + case 70: + case 72: + value = 3; + break; + case 68: + case 71: + value = 4; + break; + default: + value = 2; + } + for (index = 0; index < 47; index++) { + test.push({ + k: chr + index, + v: value + }); + } + } + test.sort(function (a, b) { + return b.v - a.v; + }); + for (index = 0; index < test.length; index++) { + chr = test[index].k.charAt(0); + if (result.charAt(result.length - 1) !== chr) result += chr; + } + return result !== 'DGBEFHACIJK'; + }); + var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; + var getSortCompare = function (comparefn) { + return function (x, y) { + if (y === undefined) return -1; + if (x === undefined) return 1; + if (comparefn !== undefined) return +comparefn(x, y) || 0; + return toString_1(x) > toString_1(y) ? 1 : -1; + }; + }; + + // `Array.prototype.sort` method + // https://tc39.es/ecma262/#sec-array.prototype.sort + _export({ + target: 'Array', + proto: true, + forced: FORCED + }, { + sort: function sort(comparefn) { + if (comparefn !== undefined) aCallable(comparefn); + var array = toObject(this); + if (STABLE_SORT) return comparefn === undefined ? nativeSort(array) : nativeSort(array, comparefn); + var items = []; + var arrayLength = lengthOfArrayLike(array); + var itemsLength, index; + for (index = 0; index < arrayLength; index++) { + if (index in array) push$1(items, array[index]); + } + arraySort(items, getSortCompare(comparefn)); + itemsLength = lengthOfArrayLike(items); + index = 0; + while (index < itemsLength) array[index] = items[index++]; + while (index < arrayLength) deletePropertyOrThrow(array, index++); + return array; + } + }); + function isOutOfViewport (parent, container) { const parentBounding = parent.getBoundingClientRect(); const boundingContainer = container.getBoundingClientRect(); @@ -21799,7 +21952,7 @@ return child_ctx; } - // (81:0) {#if dialogVisible} + // (83:0) {#if dialogVisible} function create_if_block$6(ctx) { var overlay; var current; @@ -21846,7 +21999,7 @@ }; } - // (97:4) {#if !sharees || !sharees.length} + // (99:4) {#if !sharees || !sharees.length} function create_if_block_2$3(ctx) { var p; var em; @@ -21867,7 +22020,7 @@ }; } - // (108:8) {:else} + // (110:8) {:else} function create_else_block$2(ctx) { var img; var img_src_value; @@ -21896,7 +22049,7 @@ }; } - // (106:8) {#if sharee.recipient_type == "group"} + // (108:8) {#if sharee.recipient_type == "group"} function create_if_block_1$3(ctx) { var img; var img_src_value; @@ -21917,7 +22070,7 @@ }; } - // (103:5) {#each sharees as sharee} + // (105:5) {#each sharees as sharee} function create_each_block$3(ctx) { var li; var figure; @@ -22027,7 +22180,7 @@ }; } - // (82:1) + // (84:1) function create_default_slot$2(ctx) { var div2; var label; @@ -22394,12 +22547,12 @@ exact = _yield$response$json$.exact; groups = _yield$response$json$.groups; existing_users = sharees.filter(function (s) { - return s.recipient_type == 'user'; + return s.recipient_type === 'user'; }).map(function (share) { return share.recipient_id; }); existing_groups = sharees.filter(function (s) { - return s.recipient_type == 'group'; + return s.recipient_type === 'group'; }).map(function (share) { return share.recipient_id; }); @@ -22407,6 +22560,8 @@ return !existing_users.includes(user.value.shareWith) && user.value.shareWith !== userId; }).filter(function (group) { return !existing_groups.includes(group.value.shareWith); + }).sort(function (a, b) { + return a.label.localeCompare(b.label); })); case 17: case "end": @@ -23049,7 +23204,7 @@ var $propertyIsEnumerable = objectPropertyIsEnumerable.f; var propertyIsEnumerable = functionUncurryThis($propertyIsEnumerable); - var push$1 = functionUncurryThis([].push); + var push = functionUncurryThis([].push); // in some IE versions, `propertyIsEnumerable` returns incorrect result on integer keys // of `null` prototype objects @@ -23073,7 +23228,7 @@ while (length > i) { key = keys[i++]; if (!descriptors || (IE_WORKAROUND ? key in O : propertyIsEnumerable(O, key))) { - push$1(result, TO_ENTRIES ? [key, O[key]] : O[key]); + push(result, TO_ENTRIES ? [key, O[key]] : O[key]); } } return result; @@ -23134,155 +23289,6 @@ }]; }); - var $TypeError = TypeError; - var deletePropertyOrThrow = function (O, P) { - if (!delete O[P]) throw $TypeError('Cannot delete property ' + tryToString(P) + ' of ' + tryToString(O)); - }; - - var $Array = Array; - var max$1 = Math.max; - var arraySliceSimple = function (O, start, end) { - var length = lengthOfArrayLike(O); - var k = toAbsoluteIndex(start, length); - var fin = toAbsoluteIndex(end === undefined ? length : end, length); - var result = $Array(max$1(fin - k, 0)); - for (var n = 0; k < fin; k++, n++) createProperty(result, n, O[k]); - result.length = n; - return result; - }; - - var floor = Math.floor; - var mergeSort = function (array, comparefn) { - var length = array.length; - var middle = floor(length / 2); - return length < 8 ? insertionSort(array, comparefn) : merge(array, mergeSort(arraySliceSimple(array, 0, middle), comparefn), mergeSort(arraySliceSimple(array, middle), comparefn), comparefn); - }; - var insertionSort = function (array, comparefn) { - var length = array.length; - var i = 1; - var element, j; - while (i < length) { - j = i; - element = array[i]; - while (j && comparefn(array[j - 1], element) > 0) { - array[j] = array[--j]; - } - if (j !== i++) array[j] = element; - } - return array; - }; - var merge = function (array, left, right, comparefn) { - var llength = left.length; - var rlength = right.length; - var lindex = 0; - var rindex = 0; - while (lindex < llength || rindex < rlength) { - array[lindex + rindex] = lindex < llength && rindex < rlength ? comparefn(left[lindex], right[rindex]) <= 0 ? left[lindex++] : right[rindex++] : lindex < llength ? left[lindex++] : right[rindex++]; - } - return array; - }; - var arraySort = mergeSort; - - var firefox = engineUserAgent.match(/firefox\/(\d+)/i); - var engineFfVersion = !!firefox && +firefox[1]; - - var engineIsIeOrEdge = /MSIE|Trident/.test(engineUserAgent); - - var webkit = engineUserAgent.match(/AppleWebKit\/(\d+)\./); - var engineWebkitVersion = !!webkit && +webkit[1]; - - var test = []; - var nativeSort = functionUncurryThis(test.sort); - var push = functionUncurryThis(test.push); - - // IE8- - var FAILS_ON_UNDEFINED = fails(function () { - test.sort(undefined); - }); - // V8 bug - var FAILS_ON_NULL = fails(function () { - test.sort(null); - }); - // Old WebKit - var STRICT_METHOD = arrayMethodIsStrict('sort'); - var STABLE_SORT = !fails(function () { - // feature detection can be too slow, so check engines versions - if (engineV8Version) return engineV8Version < 70; - if (engineFfVersion && engineFfVersion > 3) return; - if (engineIsIeOrEdge) return true; - if (engineWebkitVersion) return engineWebkitVersion < 603; - var result = ''; - var code, chr, value, index; - - // generate an array with more 512 elements (Chakra and old V8 fails only in this case) - for (code = 65; code < 76; code++) { - chr = String.fromCharCode(code); - switch (code) { - case 66: - case 69: - case 70: - case 72: - value = 3; - break; - case 68: - case 71: - value = 4; - break; - default: - value = 2; - } - for (index = 0; index < 47; index++) { - test.push({ - k: chr + index, - v: value - }); - } - } - test.sort(function (a, b) { - return b.v - a.v; - }); - for (index = 0; index < test.length; index++) { - chr = test[index].k.charAt(0); - if (result.charAt(result.length - 1) !== chr) result += chr; - } - return result !== 'DGBEFHACIJK'; - }); - var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT; - var getSortCompare = function (comparefn) { - return function (x, y) { - if (y === undefined) return -1; - if (x === undefined) return 1; - if (comparefn !== undefined) return +comparefn(x, y) || 0; - return toString_1(x) > toString_1(y) ? 1 : -1; - }; - }; - - // `Array.prototype.sort` method - // https://tc39.es/ecma262/#sec-array.prototype.sort - _export({ - target: 'Array', - proto: true, - forced: FORCED - }, { - sort: function sort(comparefn) { - if (comparefn !== undefined) aCallable(comparefn); - var array = toObject(this); - if (STABLE_SORT) return comparefn === undefined ? nativeSort(array) : nativeSort(array, comparefn); - var items = []; - var arrayLength = lengthOfArrayLike(array); - var itemsLength, index; - for (index = 0; index < arrayLength; index++) { - if (index in array) push(items, array[index]); - } - arraySort(items, getSortCompare(comparefn)); - itemsLength = lengthOfArrayLike(items); - index = 0; - while (index < itemsLength) array[index] = items[index++]; - while (index < arrayLength) deletePropertyOrThrow(array, index++); - return array; - } - }); - var $find = arrayIteration.find; var FIND = 'find'; @@ -29093,6 +29099,602 @@ return _createClass$1(Filters); }(SvelteComponent); + // Note: this is the semver.org version of the spec that it implements + // Not necessarily the package version of this code. + const SEMVER_SPEC_VERSION = '2.0.0'; + const MAX_LENGTH$2 = 256; + const MAX_SAFE_INTEGER$1 = Number.MAX_SAFE_INTEGER || /* istanbul ignore next */9007199254740991; + + // Max safe segment length for coercion. + const MAX_SAFE_COMPONENT_LENGTH = 16; + var constants = { + SEMVER_SPEC_VERSION, + MAX_LENGTH: MAX_LENGTH$2, + MAX_SAFE_INTEGER: MAX_SAFE_INTEGER$1, + MAX_SAFE_COMPONENT_LENGTH + }; + + const debug$1 = typeof process === 'object' && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error('SEMVER', ...args) : () => {}; + var debug_1 = debug$1; + + var re_1 = createCommonjsModule(function (module, exports) { + const { + MAX_SAFE_COMPONENT_LENGTH + } = constants; + + exports = module.exports = {}; + + // The actual regexps go on exports.re + const re = exports.re = []; + const src = exports.src = []; + const t = exports.t = {}; + let R = 0; + const createToken = (name, value, isGlobal) => { + const index = R++; + debug_1(name, index, value); + t[name] = index; + src[index] = value; + re[index] = new RegExp(value, isGlobal ? 'g' : undefined); + }; + + // The following Regular Expressions can be used for tokenizing, + // validating, and parsing SemVer version strings. + + // ## Numeric Identifier + // A single `0`, or a non-zero digit followed by zero or more digits. + + createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*'); + createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+'); + + // ## Non-numeric Identifier + // Zero or more digits, followed by a letter or hyphen, and then zero or + // more letters, digits, or hyphens. + + createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*'); + + // ## Main Version + // Three dot-separated numeric identifiers. + + createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})`); + createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})`); + + // ## Pre-release Version Identifier + // A numeric identifier, or a non-numeric identifier. + + createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]}|${src[t.NONNUMERICIDENTIFIER]})`); + createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]}|${src[t.NONNUMERICIDENTIFIER]})`); + + // ## Pre-release Version + // Hyphen, followed by one or more dot-separated pre-release version + // identifiers. + + createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`); + createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`); + + // ## Build Metadata Identifier + // Any combination of digits, letters, or hyphens. + + createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+'); + + // ## Build Metadata + // Plus sign, followed by one or more period-separated build metadata + // identifiers. + + createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER]}(?:\\.${src[t.BUILDIDENTIFIER]})*))`); + + // ## Full Version String + // A main version, followed optionally by a pre-release version and + // build metadata. + + // Note that the only major, minor, patch, and pre-release sections of + // the version string are capturing groups. The build metadata is not a + // capturing group, because it should not ever be used in version + // comparison. + + createToken('FULLPLAIN', `v?${src[t.MAINVERSION]}${src[t.PRERELEASE]}?${src[t.BUILD]}?`); + createToken('FULL', `^${src[t.FULLPLAIN]}$`); + + // like full, but allows v1.2.3 and =1.2.3, which people do sometimes. + // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty + // common in the npm registry. + createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE]}${src[t.PRERELEASELOOSE]}?${src[t.BUILD]}?`); + createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`); + createToken('GTLT', '((?:<|>)?=?)'); + + // Something like "2.*" or "1.2.x". + // Note that "x.x" is a valid xRange identifer, meaning "any version" + // Only the first item is strictly required. + createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`); + createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`); + createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:${src[t.PRERELEASE]})?${src[t.BUILD]}?` + `)?)?`); + createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:${src[t.PRERELEASELOOSE]})?${src[t.BUILD]}?` + `)?)?`); + createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`); + createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`); + + // Coercion. + // Extract anything that could conceivably be a part of a valid semver + createToken('COERCE', `${'(^|[^\\d])' + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:$|[^\\d])`); + createToken('COERCERTL', src[t.COERCE], true); + + // Tilde ranges. + // Meaning is "reasonably at or greater than" + createToken('LONETILDE', '(?:~>?)'); + createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true); + exports.tildeTrimReplace = '$1~'; + createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`); + createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`); + + // Caret ranges. + // Meaning is "at least and backwards compatible with" + createToken('LONECARET', '(?:\\^)'); + createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true); + exports.caretTrimReplace = '$1^'; + createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`); + createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`); + + // A simple gt/lt/eq thing, or just "" to indicate "any version" + createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`); + createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`); + + // An expression to strip any whitespace between the gtlt and the thing + // it modifies, so that `> 1.2.3` ==> `>1.2.3` + createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true); + exports.comparatorTrimReplace = '$1$2$3'; + + // Something like `1.2.3 - 1.2.4` + // Note that these all use the loose form, because they'll be + // checked against either the strict or loose comparator form + // later. + createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAIN]})` + `\\s*$`); + createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAINLOOSE]})` + `\\s*$`); + + // Star ranges basically just allow anything at all. + createToken('STAR', '(<|>)?=?\\s*\\*'); + // >=0.0.0 is like a star + createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$'); + createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$'); + }); + re_1.re; + re_1.src; + re_1.t; + re_1.tildeTrimReplace; + re_1.caretTrimReplace; + re_1.comparatorTrimReplace; + + // parse out just the options we care about so we always get a consistent + // obj with keys in a consistent order. + const opts = ['includePrerelease', 'loose', 'rtl']; + const parseOptions$1 = options => !options ? {} : typeof options !== 'object' ? { + loose: true + } : opts.filter(k => options[k]).reduce((o, k) => { + o[k] = true; + return o; + }, {}); + var parseOptions_1 = parseOptions$1; + + const numeric = /^[0-9]+$/; + const compareIdentifiers$1 = (a, b) => { + const anum = numeric.test(a); + const bnum = numeric.test(b); + if (anum && bnum) { + a = +a; + b = +b; + } + return a === b ? 0 : anum && !bnum ? -1 : bnum && !anum ? 1 : a < b ? -1 : 1; + }; + const rcompareIdentifiers = (a, b) => compareIdentifiers$1(b, a); + var identifiers = { + compareIdentifiers: compareIdentifiers$1, + rcompareIdentifiers + }; + + const { + MAX_LENGTH: MAX_LENGTH$1, + MAX_SAFE_INTEGER + } = constants; + const { + re: re$1, + t: t$1 + } = re_1; + + const { + compareIdentifiers + } = identifiers; + class SemVer { + constructor(version, options) { + options = parseOptions_1(options); + if (version instanceof SemVer) { + if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) { + return version; + } else { + version = version.version; + } + } else if (typeof version !== 'string') { + throw new TypeError(`Invalid Version: ${version}`); + } + if (version.length > MAX_LENGTH$1) { + throw new TypeError(`version is longer than ${MAX_LENGTH$1} characters`); + } + debug_1('SemVer', version, options); + this.options = options; + this.loose = !!options.loose; + // this isn't actually relevant for versions, but keep it so that we + // don't run into trouble passing this.options around. + this.includePrerelease = !!options.includePrerelease; + const m = version.trim().match(options.loose ? re$1[t$1.LOOSE] : re$1[t$1.FULL]); + if (!m) { + throw new TypeError(`Invalid Version: ${version}`); + } + this.raw = version; + + // these are actually numbers + this.major = +m[1]; + this.minor = +m[2]; + this.patch = +m[3]; + if (this.major > MAX_SAFE_INTEGER || this.major < 0) { + throw new TypeError('Invalid major version'); + } + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { + throw new TypeError('Invalid minor version'); + } + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { + throw new TypeError('Invalid patch version'); + } + + // numberify any prerelease numeric ids + if (!m[4]) { + this.prerelease = []; + } else { + this.prerelease = m[4].split('.').map(id => { + if (/^[0-9]+$/.test(id)) { + const num = +id; + if (num >= 0 && num < MAX_SAFE_INTEGER) { + return num; + } + } + return id; + }); + } + this.build = m[5] ? m[5].split('.') : []; + this.format(); + } + format() { + this.version = `${this.major}.${this.minor}.${this.patch}`; + if (this.prerelease.length) { + this.version += `-${this.prerelease.join('.')}`; + } + return this.version; + } + toString() { + return this.version; + } + compare(other) { + debug_1('SemVer.compare', this.version, this.options, other); + if (!(other instanceof SemVer)) { + if (typeof other === 'string' && other === this.version) { + return 0; + } + other = new SemVer(other, this.options); + } + if (other.version === this.version) { + return 0; + } + return this.compareMain(other) || this.comparePre(other); + } + compareMain(other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options); + } + return compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch); + } + comparePre(other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options); + } + + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) { + return -1; + } else if (!this.prerelease.length && other.prerelease.length) { + return 1; + } else if (!this.prerelease.length && !other.prerelease.length) { + return 0; + } + let i = 0; + do { + const a = this.prerelease[i]; + const b = other.prerelease[i]; + debug_1('prerelease compare', i, a, b); + if (a === undefined && b === undefined) { + return 0; + } else if (b === undefined) { + return 1; + } else if (a === undefined) { + return -1; + } else if (a === b) { + continue; + } else { + return compareIdentifiers(a, b); + } + } while (++i); + } + compareBuild(other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options); + } + let i = 0; + do { + const a = this.build[i]; + const b = other.build[i]; + debug_1('prerelease compare', i, a, b); + if (a === undefined && b === undefined) { + return 0; + } else if (b === undefined) { + return 1; + } else if (a === undefined) { + return -1; + } else if (a === b) { + continue; + } else { + return compareIdentifiers(a, b); + } + } while (++i); + } + + // preminor will bump the version up to the next minor release, and immediately + // down to pre-release. premajor and prepatch work the same way. + inc(release, identifier) { + switch (release) { + case 'premajor': + this.prerelease.length = 0; + this.patch = 0; + this.minor = 0; + this.major++; + this.inc('pre', identifier); + break; + case 'preminor': + this.prerelease.length = 0; + this.patch = 0; + this.minor++; + this.inc('pre', identifier); + break; + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0; + this.inc('patch', identifier); + this.inc('pre', identifier); + break; + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) { + this.inc('patch', identifier); + } + this.inc('pre', identifier); + break; + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) { + this.major++; + } + this.minor = 0; + this.patch = 0; + this.prerelease = []; + break; + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) { + this.minor++; + } + this.patch = 0; + this.prerelease = []; + break; + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) { + this.patch++; + } + this.prerelease = []; + break; + // This probably shouldn't be used publicly. + // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. + case 'pre': + if (this.prerelease.length === 0) { + this.prerelease = [0]; + } else { + let i = this.prerelease.length; + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++; + i = -2; + } + } + if (i === -1) { + // didn't increment anything + this.prerelease.push(0); + } + } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (compareIdentifiers(this.prerelease[0], identifier) === 0) { + if (isNaN(this.prerelease[1])) { + this.prerelease = [identifier, 0]; + } + } else { + this.prerelease = [identifier, 0]; + } + } + break; + default: + throw new Error(`invalid increment argument: ${release}`); + } + this.format(); + this.raw = this.version; + return this; + } + } + var semver = SemVer; + + const { + MAX_LENGTH + } = constants; + const { + re, + t + } = re_1; + + + const parse$1 = (version, options) => { + options = parseOptions_1(options); + if (version instanceof semver) { + return version; + } + if (typeof version !== 'string') { + return null; + } + if (version.length > MAX_LENGTH) { + return null; + } + const r = options.loose ? re[t.LOOSE] : re[t.FULL]; + if (!r.test(version)) { + return null; + } + try { + return new semver(version, options); + } catch (er) { + return null; + } + }; + var parse_1 = parse$1; + + const valid = (version, options) => { + const v = parse_1(version, options); + return v ? v.version : null; + }; + var valid_1 = valid; + + const major = (a, loose) => new semver(a, loose).major; + var major_1 = major; + + class ProxyBus { + bus; + constructor(bus) { + if (typeof bus.getVersion !== 'function' || !valid_1(bus.getVersion())) { + console.warn('Proxying an event bus with an unknown or invalid version'); + } else if (major_1(bus.getVersion()) !== major_1(this.getVersion())) { + console.warn('Proxying an event bus of version ' + bus.getVersion() + ' with ' + this.getVersion()); + } + this.bus = bus; + } + getVersion() { + return "3.0.2"; + } + subscribe(name, handler) { + this.bus.subscribe(name, handler); + } + unsubscribe(name, handler) { + this.bus.unsubscribe(name, handler); + } + emit(name, event) { + this.bus.emit(name, event); + } + } + class SimpleBus { + handlers = new Map(); + getVersion() { + return "3.0.2"; + } + subscribe(name, handler) { + this.handlers.set(name, (this.handlers.get(name) || []).concat(handler)); + } + unsubscribe(name, handler) { + this.handlers.set(name, (this.handlers.get(name) || []).filter(h => h != handler)); + } + emit(name, event) { + (this.handlers.get(name) || []).forEach(h => { + try { + h(event); + } catch (e) { + console.error('could not invoke event listener', e); + } + }); + } + } + function getBus() { + if (typeof window.OC !== 'undefined' && window.OC._eventBus && typeof window._nc_event_bus === 'undefined') { + console.warn('found old event bus instance at OC._eventBus. Update your version!'); + window._nc_event_bus = window.OC._eventBus; + } + // Either use an existing event bus instance or create one + if (typeof window._nc_event_bus !== 'undefined') { + return new ProxyBus(window._nc_event_bus); + } else { + return window._nc_event_bus = new SimpleBus(); + } + } + const bus = getBus(); + /** + * Register an event listener + * + * @param name name of the event + * @param handler callback invoked for every matching event emitted on the bus + */ + function subscribe(name, handler) { + bus.subscribe(name, handler); + } + + const tokenElement = document.getElementsByTagName('head')[0]; + let token$1 = tokenElement ? tokenElement.getAttribute('data-requesttoken') : null; + const observers = []; + function getRequestToken() { + return token$1; + } + // Listen to server event and keep token in sync + subscribe('csrf-token-update', e => { + token$1 = e.token; + observers.forEach(observer => { + try { + observer(e.token); + } catch (e) { + console.error('error updating CSRF token observer', e); + } + }); + }); + + /// + const getAttribute = (el, attribute) => { + if (el) { + return el.getAttribute(attribute); + } + return null; + }; + const head = document.getElementsByTagName('head')[0]; + const uid = getAttribute(head, 'data-user'); + const displayName = getAttribute(head, 'data-user-displayname'); + const isAdmin = typeof OC === 'undefined' ? false : OC.isUserAdmin(); + function getCurrentUser() { + if (uid === null) { + return null; + } + return { + uid, + displayName, + isAdmin + }; + } + const subscriber_queue = []; /** * Create a `Writable` store that allows both updating and reading by subscription. @@ -29292,7 +29894,7 @@ }; var search = /*#__PURE__*/function () { var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(query) { - var response, _yield$response$json$, users, exact; + var response, current, currentUser, _yield$response$json$, users, exact; return _regeneratorRuntime().wrap(function _callee$(_context) { while (1) switch (_context.prev = _context.next) { case 0: @@ -29312,19 +29914,31 @@ }); case 5: response = _context.sent; + current = getCurrentUser(); + currentUser = []; + if (current.uid.toLowerCase().includes(query.toLowerCase()) || current.displayName.toLowerCase().includes(query.toLowerCase())) { + currentUser = [{ + value: { + shareWith: current.uid + }, + label: current.displayName + }]; + } $$invalidate(2, loading = false); if (!response.ok) { - _context.next = 14; + _context.next = 17; break; } - _context.next = 10; + _context.next = 13; return response.json(); - case 10: + case 13: _yield$response$json$ = _context.sent.ocs.data; users = _yield$response$json$.users; exact = _yield$response$json$.exact; - return _context.abrupt("return", [].concat(_toConsumableArray$1(users), _toConsumableArray$1(exact.users))); - case 14: + return _context.abrupt("return", [].concat(_toConsumableArray$1(users), _toConsumableArray$1(exact.users), _toConsumableArray$1(currentUser)).sort(function (a, b) { + return a.label.localeCompare(b.label); + })); + case 17: case "end": return _context.stop(); } @@ -32759,7 +33373,7 @@ this.charLength = this.charReceived ? 3 : 0; } Readable.ReadableState = ReadableState; - var debug$1 = debuglog('stream'); + var debug = debuglog('stream'); inherits$1(Readable, EventEmitter); function prependListener(emitter, event, fn) { // Sadly this is not cacheable as some libraries bundle their own @@ -32977,7 +33591,7 @@ // you can override either this method, or the async _read(n) below. Readable.prototype.read = function (n) { - debug$1('read', n); + debug('read', n); n = parseInt(n, 10); var state = this._readableState; var nOrig = n; @@ -32987,7 +33601,7 @@ // already have a bunch of data in the buffer, then just trigger // the 'readable' event and move on. if (n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended)) { - debug$1('read: emitReadable', state.length, state.ended); + debug('read: emitReadable', state.length, state.ended); if (state.length === 0 && state.ended) endReadable(this);else emitReadable(this); return null; } @@ -33023,21 +33637,21 @@ // if we need a readable event, then we need to do some reading. var doRead = state.needReadable; - debug$1('need readable', doRead); + debug('need readable', doRead); // if we currently have less than the highWaterMark, then also read some if (state.length === 0 || state.length - n < state.highWaterMark) { doRead = true; - debug$1('length less than watermark', doRead); + debug('length less than watermark', doRead); } // however, if we've ended, then there's no point, and if we're already // reading, then it's unnecessary. if (state.ended || state.reading) { doRead = false; - debug$1('reading or ended', doRead); + debug('reading or ended', doRead); } else if (doRead) { - debug$1('do read'); + debug('do read'); state.reading = true; state.sync = true; // if the length is currently zero, then we *need* a readable event. @@ -33097,13 +33711,13 @@ var state = stream._readableState; state.needReadable = false; if (!state.emittedReadable) { - debug$1('emitReadable', state.flowing); + debug('emitReadable', state.flowing); state.emittedReadable = true; if (state.sync) nextTick(emitReadable_, stream);else emitReadable_(stream); } } function emitReadable_(stream) { - debug$1('emit readable'); + debug('emit readable'); stream.emit('readable'); flow(stream); } @@ -33123,7 +33737,7 @@ function maybeReadMore_(stream, state) { var len = state.length; while (!state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark) { - debug$1('maybeReadMore read 0'); + debug('maybeReadMore read 0'); stream.read(0); if (len === state.length) // didn't get any data, stop spinning. @@ -33154,19 +33768,19 @@ break; } state.pipesCount += 1; - debug$1('pipe count=%d opts=%j', state.pipesCount, pipeOpts); + debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); var doEnd = !pipeOpts || pipeOpts.end !== false; var endFn = doEnd ? onend : cleanup; if (state.endEmitted) nextTick(endFn);else src.once('end', endFn); dest.on('unpipe', onunpipe); function onunpipe(readable) { - debug$1('onunpipe'); + debug('onunpipe'); if (readable === src) { cleanup(); } } function onend() { - debug$1('onend'); + debug('onend'); dest.end(); } @@ -33178,7 +33792,7 @@ dest.on('drain', ondrain); var cleanedUp = false; function cleanup() { - debug$1('cleanup'); + debug('cleanup'); // cleanup event handlers once the pipe is broken dest.removeListener('close', onclose); dest.removeListener('finish', onfinish); @@ -33205,7 +33819,7 @@ var increasedAwaitDrain = false; src.on('data', ondata); function ondata(chunk) { - debug$1('ondata'); + debug('ondata'); increasedAwaitDrain = false; var ret = dest.write(chunk); if (false === ret && !increasedAwaitDrain) { @@ -33214,7 +33828,7 @@ // also returned false. // => Check whether `dest` is still a piping destination. if ((state.pipesCount === 1 && state.pipes === dest || state.pipesCount > 1 && indexOf(state.pipes, dest) !== -1) && !cleanedUp) { - debug$1('false write response, pause', src._readableState.awaitDrain); + debug('false write response, pause', src._readableState.awaitDrain); src._readableState.awaitDrain++; increasedAwaitDrain = true; } @@ -33225,7 +33839,7 @@ // if the dest has an error, then stop piping into it. // however, don't suppress the throwing behavior for this. function onerror(er) { - debug$1('onerror', er); + debug('onerror', er); unpipe(); dest.removeListener('error', onerror); if (listenerCount(dest, 'error') === 0) dest.emit('error', er); @@ -33241,13 +33855,13 @@ } dest.once('close', onclose); function onfinish() { - debug$1('onfinish'); + debug('onfinish'); dest.removeListener('close', onclose); unpipe(); } dest.once('finish', onfinish); function unpipe() { - debug$1('unpipe'); + debug('unpipe'); src.unpipe(dest); } @@ -33256,7 +33870,7 @@ // start the flow if it hasn't been started already. if (!state.flowing) { - debug$1('pipe resume'); + debug('pipe resume'); src.resume(); } return dest; @@ -33264,7 +33878,7 @@ function pipeOnDrain(src) { return function () { var state = src._readableState; - debug$1('pipeOnDrain', state.awaitDrain); + debug('pipeOnDrain', state.awaitDrain); if (state.awaitDrain) state.awaitDrain--; if (state.awaitDrain === 0 && src.listeners('data').length) { state.flowing = true; @@ -33340,7 +33954,7 @@ }; Readable.prototype.addListener = Readable.prototype.on; function nReadingNextTick(self) { - debug$1('readable nexttick read 0'); + debug('readable nexttick read 0'); self.read(0); } @@ -33349,7 +33963,7 @@ Readable.prototype.resume = function () { var state = this._readableState; if (!state.flowing) { - debug$1('resume'); + debug('resume'); state.flowing = true; resume(this, state); } @@ -33363,7 +33977,7 @@ } function resume_(stream, state) { if (!state.reading) { - debug$1('resume read 0'); + debug('resume read 0'); stream.read(0); } state.resumeScheduled = false; @@ -33373,9 +33987,9 @@ if (state.flowing && !state.reading) stream.read(0); } Readable.prototype.pause = function () { - debug$1('call pause flowing=%j', this._readableState.flowing); + debug('call pause flowing=%j', this._readableState.flowing); if (false !== this._readableState.flowing) { - debug$1('pause'); + debug('pause'); this._readableState.flowing = false; this.emit('pause'); } @@ -33383,7 +33997,7 @@ }; function flow(stream) { var state = stream._readableState; - debug$1('flow', state.flowing); + debug('flow', state.flowing); while (state.flowing && stream.read() !== null) {} } @@ -33395,7 +34009,7 @@ var paused = false; var self = this; stream.on('end', function () { - debug$1('wrapped end'); + debug('wrapped end'); if (state.decoder && !state.ended) { var chunk = state.decoder.end(); if (chunk && chunk.length) self.push(chunk); @@ -33403,7 +34017,7 @@ self.push(null); }); stream.on('data', function (chunk) { - debug$1('wrapped data'); + debug('wrapped data'); if (state.decoder) chunk = state.decoder.write(chunk); // don't skip over falsy values in objectMode @@ -33436,7 +34050,7 @@ // when we try to consume some more bytes, simply unpause the // underlying stream. self._read = function (n) { - debug$1('wrapped _read', n); + debug('wrapped _read', n); if (paused) { paused = false; stream.resume(); @@ -35510,7 +36124,7 @@ callback(err); } } - const parse$1 = function () { + const parse = function () { let data, options, callback; for (const i in arguments) { const argument = arguments[i]; @@ -36287,2476 +36901,1890 @@ append(div2, t7); if (if_block) if_block.m(div2, null); }, - p: function p(ctx, dirty) { - if (dirty[0] & /*importPreviewData*/256 && t2_value !== (t2_value = /*project*/ctx[27].name + "")) set_data(t2, t2_value); - if (dirty[0] & /*importPreviewData*/256 && t6_value !== (t6_value = /*project*/ctx[27].note + "")) set_data(t6, t6_value); - if ( /*project*/ctx[27].tasks) { - if (if_block) { - if_block.p(ctx, dirty); - } else { - if_block = create_if_block_2(ctx); - if_block.c(); - if_block.m(div2, null); - } - } else if (if_block) { - if_block.d(1); - if_block = null; - } - }, - d: function d(detaching) { - if (detaching) detach(div2); - if (if_block) if_block.d(); - } - }; - } - - // (308:0) {#each importPreviewData as client} - function create_each_block(ctx) { - var div2; - var div0; - var span0; - var t1; - var h3; - var t2_value = /*client*/ctx[24].name + ""; - var t2; - var t3; - var div1; - var span1; - var t5; - var t6_value = /*client*/ctx[24].note + ""; - var t6; - var t7; - var if_block = /*client*/ctx[24].projects && create_if_block_1(ctx); - return { - c: function c() { - div2 = element("div"); - div0 = element("div"); - span0 = element("span"); - span0.textContent = "".concat(translate_1('timemanager', 'Client')); - t1 = space$1(); - h3 = element("h3"); - t2 = text$1(t2_value); - t3 = space$1(); - div1 = element("div"); - span1 = element("span"); - span1.textContent = "".concat(translate_1('timemanager', 'Note')); - t5 = space$1(); - t6 = text$1(t6_value); - t7 = space$1(); - if (if_block) if_block.c(); - attr(span0, "class", "tm_label"); - attr(span1, "class", "tm_label"); - attr(div2, "class", "tm_item-row"); - }, - m: function m(target, anchor) { - insert(target, div2, anchor); - append(div2, div0); - append(div0, span0); - append(div0, t1); - append(div0, h3); - append(h3, t2); - append(div2, t3); - append(div2, div1); - append(div1, span1); - append(div1, t5); - append(div1, t6); - append(div2, t7); - if (if_block) if_block.m(div2, null); - }, - p: function p(ctx, dirty) { - if (dirty[0] & /*importPreviewData*/256 && t2_value !== (t2_value = /*client*/ctx[24].name + "")) set_data(t2, t2_value); - if (dirty[0] & /*importPreviewData*/256 && t6_value !== (t6_value = /*client*/ctx[24].note + "")) set_data(t6, t6_value); - if ( /*client*/ctx[24].projects) { - if (if_block) { - if_block.p(ctx, dirty); - } else { - if_block = create_if_block_1(ctx); - if_block.c(); - if_block.m(div2, null); - } - } else if (if_block) { - if_block.d(1); - if_block = null; - } - }, - d: function d(detaching) { - if (detaching) detach(div2); - if (if_block) if_block.d(); - } - }; - } - - // (355:0) {#if importPreviewData.length} - function create_if_block$1(ctx) { - var form; - var button; - var t_value = translate_1('timemanager', 'Import now') + ""; - var t; - var form_class_value; - var mounted; - var dispose; - return { - c: function c() { - form = element("form"); - button = element("button"); - t = text$1(t_value); - button.disabled = /*loading*/ctx[5]; - attr(button, "type", "submit"); - attr(button, "class", "button primary"); - attr(form, "class", form_class_value = /*loading*/ctx[5] ? ' icon-loading' : ''); - }, - m: function m(target, anchor) { - insert(target, form, anchor); - append(form, button); - append(button, t); - if (!mounted) { - dispose = listen(form, "submit", prevent_default( /*doImport*/ctx[12])); - mounted = true; - } - }, - p: function p(ctx, dirty) { - if (dirty[0] & /*loading*/32) { - button.disabled = /*loading*/ctx[5]; - } - if (dirty[0] & /*loading*/32 && form_class_value !== (form_class_value = /*loading*/ctx[5] ? ' icon-loading' : '')) { - attr(form, "class", form_class_value); - } - }, - d: function d(detaching) { - if (detaching) detach(form); - mounted = false; - dispose(); - } - }; - } - function create_fragment$1(ctx) { - var form; - var label0; - var t0_value = translate_1('timemanager', 'Select delimiter') + ""; - var t0; - var t1; - var select; - var option0; - var option1; - var t4; - var label1; - var t5_value = translate_1('timemanager', 'Select CSV file') + ""; - var t5; - var t6; - var br; - var t7; - var input; - var t8; - var button; - var t10; - var t11; - var t12; - var t13; - var t14; - var t15; - var if_block4_anchor; - var current; - var mounted; - var dispose; - var if_block0 = /*parseError*/ctx[9] && create_if_block_6(ctx); - var if_block1 = /*importError*/ctx[6] && create_if_block_5(ctx); - var if_block2 = /*successMessage*/ctx[7] && create_if_block_4(ctx); - var if_block3 = /*importPreviewData*/ctx[8].length && create_if_block_3(ctx); - var each_value = /*importPreviewData*/ctx[8]; - var each_blocks = []; - for (var i = 0; i < each_value.length; i += 1) { - each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); - } - var if_block4 = /*importPreviewData*/ctx[8].length && create_if_block$1(ctx); - return { - c: function c() { - form = element("form"); - label0 = element("label"); - t0 = text$1(t0_value); - t1 = space$1(); - select = element("select"); - option0 = element("option"); - option0.textContent = ","; - option1 = element("option"); - option1.textContent = ";"; - t4 = space$1(); - label1 = element("label"); - t5 = text$1(t5_value); - t6 = space$1(); - br = element("br"); - t7 = space$1(); - input = element("input"); - t8 = space$1(); - button = element("button"); - button.textContent = "".concat(translate_1('timemanager', 'Generate preview from file')); - t10 = space$1(); - if (if_block0) if_block0.c(); - t11 = space$1(); - if (if_block1) if_block1.c(); - t12 = space$1(); - if (if_block2) if_block2.c(); - t13 = space$1(); - if (if_block3) if_block3.c(); - t14 = space$1(); - for (var _i7 = 0; _i7 < each_blocks.length; _i7 += 1) { - each_blocks[_i7].c(); - } - t15 = space$1(); - if (if_block4) if_block4.c(); - if_block4_anchor = empty(); - option0.selected = true; - option0.__value = ","; - option0.value = option0.__value; - option1.__value = ";"; - option1.value = option1.__value; - attr(select, "name", "delimiter"); - attr(input, "type", "file"); - attr(button, "type", "submit"); - }, - m: function m(target, anchor) { - insert(target, form, anchor); - append(form, label0); - append(label0, t0); - append(label0, t1); - append(label0, select); - append(select, option0); - append(select, option1); - /*select_binding*/ - ctx[15](select); - append(form, t4); - append(form, label1); - append(label1, t5); - append(label1, t6); - append(label1, br); - append(label1, t7); - append(label1, input); - /*input_binding*/ - ctx[16](input); - append(form, t8); - append(form, button); - insert(target, t10, anchor); - if (if_block0) if_block0.m(target, anchor); - insert(target, t11, anchor); - if (if_block1) if_block1.m(target, anchor); - insert(target, t12, anchor); - if (if_block2) if_block2.m(target, anchor); - insert(target, t13, anchor); - if (if_block3) if_block3.m(target, anchor); - insert(target, t14, anchor); - for (var _i8 = 0; _i8 < each_blocks.length; _i8 += 1) { - if (each_blocks[_i8]) { - each_blocks[_i8].m(target, anchor); - } - } - insert(target, t15, anchor); - if (if_block4) if_block4.m(target, anchor); - insert(target, if_block4_anchor, anchor); - current = true; - if (!mounted) { - dispose = listen(form, "submit", prevent_default( /*previewFile*/ctx[11])); - mounted = true; - } - }, - p: function p(ctx, dirty) { - if ( /*parseError*/ctx[9]) { - if (if_block0) { - if_block0.p(ctx, dirty); - if (dirty[0] & /*parseError*/512) { - transition_in(if_block0, 1); - } - } else { - if_block0 = create_if_block_6(ctx); - if_block0.c(); - transition_in(if_block0, 1); - if_block0.m(t11.parentNode, t11); - } - } else if (if_block0) { - group_outros(); - transition_out(if_block0, 1, 1, function () { - if_block0 = null; - }); - check_outros(); - } - if ( /*importError*/ctx[6]) { - if (if_block1) { - if_block1.p(ctx, dirty); - if (dirty[0] & /*importError*/64) { - transition_in(if_block1, 1); - } - } else { - if_block1 = create_if_block_5(ctx); - if_block1.c(); - transition_in(if_block1, 1); - if_block1.m(t12.parentNode, t12); - } - } else if (if_block1) { - group_outros(); - transition_out(if_block1, 1, 1, function () { - if_block1 = null; - }); - check_outros(); - } - if ( /*successMessage*/ctx[7]) { - if (if_block2) { - if_block2.p(ctx, dirty); - if (dirty[0] & /*successMessage*/128) { - transition_in(if_block2, 1); - } - } else { - if_block2 = create_if_block_4(ctx); - if_block2.c(); - transition_in(if_block2, 1); - if_block2.m(t13.parentNode, t13); - } - } else if (if_block2) { - group_outros(); - transition_out(if_block2, 1, 1, function () { - if_block2 = null; - }); - check_outros(); - } - if ( /*importPreviewData*/ctx[8].length) { - if (if_block3) { - if_block3.p(ctx, dirty); - } else { - if_block3 = create_if_block_3(ctx); - if_block3.c(); - if_block3.m(t14.parentNode, t14); - } - } else if (if_block3) { - if_block3.d(1); - if_block3 = null; - } - if (dirty[0] & /*allOpen, importPreviewData*/1280) { - each_value = /*importPreviewData*/ctx[8]; - var _i9; - for (_i9 = 0; _i9 < each_value.length; _i9 += 1) { - var child_ctx = get_each_context(ctx, each_value, _i9); - if (each_blocks[_i9]) { - each_blocks[_i9].p(child_ctx, dirty); - } else { - each_blocks[_i9] = create_each_block(child_ctx); - each_blocks[_i9].c(); - each_blocks[_i9].m(t15.parentNode, t15); - } - } - for (; _i9 < each_blocks.length; _i9 += 1) { - each_blocks[_i9].d(1); - } - each_blocks.length = each_value.length; - } - if ( /*importPreviewData*/ctx[8].length) { - if (if_block4) { - if_block4.p(ctx, dirty); - } else { - if_block4 = create_if_block$1(ctx); - if_block4.c(); - if_block4.m(if_block4_anchor.parentNode, if_block4_anchor); - } - } else if (if_block4) { - if_block4.d(1); - if_block4 = null; - } - }, - i: function i(local) { - if (current) return; - transition_in(if_block0); - transition_in(if_block1); - transition_in(if_block2); - current = true; - }, - o: function o(local) { - transition_out(if_block0); - transition_out(if_block1); - transition_out(if_block2); - current = false; - }, - d: function d(detaching) { - if (detaching) detach(form); - /*select_binding*/ - ctx[15](null); - /*input_binding*/ - ctx[16](null); - if (detaching) detach(t10); - if (if_block0) if_block0.d(detaching); - if (detaching) detach(t11); - if (if_block1) if_block1.d(detaching); - if (detaching) detach(t12); - if (if_block2) if_block2.d(detaching); - if (detaching) detach(t13); - if (if_block3) if_block3.d(detaching); - if (detaching) detach(t14); - destroy_each(each_blocks, detaching); - if (detaching) detach(t15); - if (if_block4) if_block4.d(detaching); - if (detaching) detach(if_block4_anchor); - mounted = false; - dispose(); + p: function p(ctx, dirty) { + if (dirty[0] & /*importPreviewData*/256 && t2_value !== (t2_value = /*project*/ctx[27].name + "")) set_data(t2, t2_value); + if (dirty[0] & /*importPreviewData*/256 && t6_value !== (t6_value = /*project*/ctx[27].note + "")) set_data(t6, t6_value); + if ( /*project*/ctx[27].tasks) { + if (if_block) { + if_block.p(ctx, dirty); + } else { + if_block = create_if_block_2(ctx); + if_block.c(); + if_block.m(div2, null); + } + } else if (if_block) { + if_block.d(1); + if_block = null; + } + }, + d: function d(detaching) { + if (detaching) detach(div2); + if (if_block) if_block.d(); } }; } - function instance$1($$self, $$props, $$invalidate) { - var parseError; - var importError; - var successMessage; - var importPreviewData; - var loading; - var allOpen; - var syncApiUrl = $$props.syncApiUrl; - var requestToken = $$props.requestToken; - var fileInput; - var delimiterInput; - - // Collect updated objects in here - var preparedClients = []; - var preparedProjects = []; - var preparedTasks = []; - // Converts all keys of an object to lowercase - var keysToLowerCase = function keysToLowerCase(object) { - var result = {}; - if (object) { - for (var _i10 = 0, _Object$entries = Object.entries(object); _i10 < _Object$entries.length; _i10++) { - var _Object$entries$_i = _slicedToArray(_Object$entries[_i10], 2), - key = _Object$entries$_i[0], - value = _Object$entries$_i[1]; - result[key.toLowerCase()] = value; + // (308:0) {#each importPreviewData as client} + function create_each_block(ctx) { + var div2; + var div0; + var span0; + var t1; + var h3; + var t2_value = /*client*/ctx[24].name + ""; + var t2; + var t3; + var div1; + var span1; + var t5; + var t6_value = /*client*/ctx[24].note + ""; + var t6; + var t7; + var if_block = /*client*/ctx[24].projects && create_if_block_1(ctx); + return { + c: function c() { + div2 = element("div"); + div0 = element("div"); + span0 = element("span"); + span0.textContent = "".concat(translate_1('timemanager', 'Client')); + t1 = space$1(); + h3 = element("h3"); + t2 = text$1(t2_value); + t3 = space$1(); + div1 = element("div"); + span1 = element("span"); + span1.textContent = "".concat(translate_1('timemanager', 'Note')); + t5 = space$1(); + t6 = text$1(t6_value); + t7 = space$1(); + if (if_block) if_block.c(); + attr(span0, "class", "tm_label"); + attr(span1, "class", "tm_label"); + attr(div2, "class", "tm_item-row"); + }, + m: function m(target, anchor) { + insert(target, div2, anchor); + append(div2, div0); + append(div0, span0); + append(div0, t1); + append(div0, h3); + append(h3, t2); + append(div2, t3); + append(div2, div1); + append(div1, span1); + append(div1, t5); + append(div1, t6); + append(div2, t7); + if (if_block) if_block.m(div2, null); + }, + p: function p(ctx, dirty) { + if (dirty[0] & /*importPreviewData*/256 && t2_value !== (t2_value = /*client*/ctx[24].name + "")) set_data(t2, t2_value); + if (dirty[0] & /*importPreviewData*/256 && t6_value !== (t6_value = /*client*/ctx[24].note + "")) set_data(t6, t6_value); + if ( /*client*/ctx[24].projects) { + if (if_block) { + if_block.p(ctx, dirty); + } else { + if_block = create_if_block_1(ctx); + if_block.c(); + if_block.m(div2, null); + } + } else if (if_block) { + if_block.d(1); + if_block = null; } + }, + d: function d(detaching) { + if (detaching) detach(div2); + if (if_block) if_block.d(); } - return result; - }; - - // Filters a list of records for type, converts all keys to lowercase and applies a uuid to each record - var filter = function filter(records, type) { - return records.map(keysToLowerCase).filter(function (record) { - return record.type && record.type.toLowerCase() === type; - }).map(function (record) { - return _objectSpread2(_objectSpread2({}, record), {}, { - uuid: v4() - }); - }); - }; - - // Previews a given file - var previewFile = /*#__PURE__*/function () { - var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() { - var _fileInput$files, file, fileReader, contents, clients, projects, tasks, associated; - return _regeneratorRuntime().wrap(function _callee$(_context) { - while (1) switch (_context.prev = _context.next) { - case 0: - if (!(fileInput && fileInput.files && fileInput.files.length)) { - _context.next = 33; - break; - } - $$invalidate(8, importPreviewData = []); - $$invalidate(9, parseError = ""); - _fileInput$files = _slicedToArray(fileInput.files, 1), file = _fileInput$files[0]; - fileReader = new FileReader(); - fileReader.readAsText(file); - _context.next = 8; - return new Promise(function (resolve, reject) { - fileReader.onload = function () { - resolve(null); - }; - fileReader.onerror = function (error) { - reject(error); - }; - }); - case 8: - contents = []; - _context.prev = 9; - _context.next = 12; - return new Promise(function (resolve, reject) { - return parse$1(fileReader.result, { - delimiter: delimiterInput && delimiterInput.value ? delimiterInput.value : ",", - // @TODO: Make encoding configurable - encoding: "utf-8", - columns: true - }, function (err, records) { - if (err) { - reject(err); - } - resolve(records); - }); - }); - case 12: - contents = _context.sent; - _context.next = 19; - break; - case 15: - _context.prev = 15; - _context.t0 = _context["catch"](9); - $$invalidate(9, parseError = _context.t0); - return _context.abrupt("return"); - case 19: - if (!(!contents || !contents.length)) { - _context.next = 22; - break; - } - $$invalidate(9, parseError = translate_1("timemanager", "It looks like this file is not a CSV file or doesn't contain any clients, projects or tasks.")); - return _context.abrupt("return"); - case 22: - // Filter by type and assign uuids - clients = filter(contents, "client"); - projects = filter(contents, "project"); - tasks = filter(contents, "task"); - if (!(!clients.length && !projects.length && !tasks.length)) { - _context.next = 28; - break; - } - $$invalidate(9, parseError = translate_1("timemanager", "It looks like this file is not a CSV file or doesn't contain any clients, projects or tasks.")); - return _context.abrupt("return"); - case 28: - // Empty arrays - $$invalidate(2, preparedClients = []); - $$invalidate(3, preparedProjects = []); - $$invalidate(4, preparedTasks = []); - - // Group entities - associated = clients.map(function (client) { - client.projects = projects.filter(function (project) { - return project.client === client.name && !preparedProjects.find(function (oneProject) { - return oneProject.uuid === project.uuid; - }); - }).map(function (project) { - return _objectSpread2(_objectSpread2({}, project), {}, { - client_uuid: client.uuid - }); - }).map(function (project) { - project.tasks = tasks.filter(function (task) { - return task.project === project.name && !preparedTasks.find(function (oneTask) { - return oneTask.uuid === task.uuid; - }); - }).map(function (task) { - return _objectSpread2(_objectSpread2({}, task), {}, { - project_uuid: project.uuid - }); - }); - - // Add tasks if not exists - project.tasks.forEach(function (task) { - if (!preparedTasks.find(function (oneTask) { - return oneTask.uuid === task.uuid; - })) { - preparedTasks.push(task); - } - }); - - // Add project if not exists - if (!preparedProjects.find(function (oneProject) { - return oneProject.uuid === project.uuid; - })) { - preparedProjects.push(project); - } - return project; - }); - - // Add client - preparedClients.push(client); - return client; - }); - $$invalidate(8, importPreviewData = associated); - case 33: - case "end": - return _context.stop(); - } - }, _callee, null, [[9, 15]]); - })); - return function previewFile() { - return _ref.apply(this, arguments); - }; - }(); // @TODO: LOW: List unassociated elements (not in import & not in store) - - // Post data to JSON API - var doImport = /*#__PURE__*/function () { - var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() { - var convertedImportData, response; - return _regeneratorRuntime().wrap(function _callee2$(_context2) { - while (1) switch (_context2.prev = _context2.next) { - case 0: - $$invalidate(5, loading = true); - $$invalidate(6, importError = ""); - convertedImportData = { - lastCommit: "", - data: { - clients: { - created: preparedClients.map(function (client) { - delete client.type; - delete client.projects; - return client; - }), - updated: [], - deleted: [] - }, - projects: { - created: preparedProjects.map(function (project) { - delete project.type; - delete project.tasks; - return project; - }), - updated: [], - deleted: [] - }, - tasks: { - created: preparedTasks.map(function (task) { - delete task.type; - return task; - }), - updated: [], - deleted: [] - }, - times: { - created: [], - updated: [], - deleted: [] - } - } - }; - _context2.prev = 3; - _context2.next = 6; - return fetch(syncApiUrl, { - method: "POST", - headers: { - requesttoken: requestToken, - "content-type": "application/json" - }, - body: JSON.stringify(convertedImportData) - }); - case 6: - response = _context2.sent; - if (response.ok) { - $$invalidate(8, importPreviewData = []); - $$invalidate(7, successMessage = translate_1("timemanager", "Imported {clientsCount} client(s), {projectsCount} project(s), {tasksCount} task(s)", { - clientsCount: preparedClients.length, - projectsCount: preparedProjects.length, - tasksCount: preparedTasks.length - })); - } - _context2.next = 13; - break; - case 10: - _context2.prev = 10; - _context2.t0 = _context2["catch"](3); - $$invalidate(6, importError = _context2.t0); - case 13: - $$invalidate(5, loading = false); - case 14: - case "end": - return _context2.stop(); - } - }, _callee2, null, [[3, 10]]); - })); - return function doImport() { - return _ref2.apply(this, arguments); - }; - }(); - function select_binding($$value) { - binding_callbacks[$$value ? 'unshift' : 'push'](function () { - delimiterInput = $$value; - $$invalidate(1, delimiterInput); - }); - } - function input_binding($$value) { - binding_callbacks[$$value ? 'unshift' : 'push'](function () { - fileInput = $$value; - $$invalidate(0, fileInput); - }); - } - var click_handler = function click_handler() { - $$invalidate(9, parseError = ''); - }; - var click_handler_1 = function click_handler_1() { - $$invalidate(6, importError = ''); - }; - var click_handler_2 = function click_handler_2() { - $$invalidate(7, successMessage = ''); - }; - var click_handler_3 = function click_handler_3() { - return $$invalidate(10, allOpen = false); - }; - var click_handler_4 = function click_handler_4() { - return $$invalidate(10, allOpen = true); - }; - $$self.$$set = function ($$props) { - if ('syncApiUrl' in $$props) $$invalidate(13, syncApiUrl = $$props.syncApiUrl); - if ('requestToken' in $$props) $$invalidate(14, requestToken = $$props.requestToken); }; - $$invalidate(9, parseError = ""); - $$invalidate(6, importError = ""); - $$invalidate(7, successMessage = ""); - $$invalidate(8, importPreviewData = []); - $$invalidate(5, loading = false); - $$invalidate(10, allOpen = false); - return [fileInput, delimiterInput, preparedClients, preparedProjects, preparedTasks, loading, importError, successMessage, importPreviewData, parseError, allOpen, previewFile, doImport, syncApiUrl, requestToken, select_binding, input_binding, click_handler, click_handler_1, click_handler_2, click_handler_3, click_handler_4]; } - var Import = /*#__PURE__*/function (_SvelteComponent) { - _inherits$1(Import, _SvelteComponent); - var _super = _createSuper$1(Import); - function Import(options) { - var _this; - _classCallCheck$1(this, Import); - _this = _super.call(this); - init$2(_assertThisInitialized$1(_this), options, instance$1, create_fragment$1, safe_not_equal, { - syncApiUrl: 13, - requestToken: 14 - }, null, [-1, -1]); - return _this; - } - return _createClass$1(Import); - }(SvelteComponent); - function create_if_block(ctx) { - var div; - var userfilterselect; - var current; + // (355:0) {#if importPreviewData.length} + function create_if_block$1(ctx) { + var form; + var button; + var t_value = translate_1('timemanager', 'Import now') + ""; + var t; + var form_class_value; var mounted; var dispose; - userfilterselect = new UserFilterSelect({ - props: { - isVisible: /*showTooltip*/ctx[1], - requestToken: /*requestToken*/ctx[0] - } - }); return { c: function c() { - div = element("div"); - create_component(userfilterselect.$$.fragment); - attr(div, "class", "popover"); + form = element("form"); + button = element("button"); + t = text$1(t_value); + button.disabled = /*loading*/ctx[5]; + attr(button, "type", "submit"); + attr(button, "class", "button primary"); + attr(form, "class", form_class_value = /*loading*/ctx[5] ? ' icon-loading' : ''); }, m: function m(target, anchor) { - insert(target, div, anchor); - mount_component(userfilterselect, div, null); - current = true; + insert(target, form, anchor); + append(form, button); + append(button, t); if (!mounted) { - dispose = action_destroyer(/*popperContent*/ctx[4].call(null, div, /*extraOpts*/ctx[5])); + dispose = listen(form, "submit", prevent_default( /*doImport*/ctx[12])); mounted = true; } }, p: function p(ctx, dirty) { - var userfilterselect_changes = {}; - if (dirty & /*showTooltip*/2) userfilterselect_changes.isVisible = /*showTooltip*/ctx[1]; - if (dirty & /*requestToken*/1) userfilterselect_changes.requestToken = /*requestToken*/ctx[0]; - userfilterselect.$set(userfilterselect_changes); - }, - i: function i(local) { - if (current) return; - transition_in(userfilterselect.$$.fragment, local); - current = true; - }, - o: function o(local) { - transition_out(userfilterselect.$$.fragment, local); - current = false; + if (dirty[0] & /*loading*/32) { + button.disabled = /*loading*/ctx[5]; + } + if (dirty[0] & /*loading*/32 && form_class_value !== (form_class_value = /*loading*/ctx[5] ? ' icon-loading' : '')) { + attr(form, "class", form_class_value); + } }, d: function d(detaching) { - if (detaching) detach(div); - destroy_component(userfilterselect); + if (detaching) detach(form); mounted = false; dispose(); } }; } - function create_fragment(ctx) { - var button; - var t0_value = translate_1('timemanager', 'Filter by person') + ""; + function create_fragment$1(ctx) { + var form; + var label0; + var t0_value = translate_1('timemanager', 'Select delimiter') + ""; var t0; - var button_class_value; var t1; - var if_block_anchor; + var select; + var option0; + var option1; + var t4; + var label1; + var t5_value = translate_1('timemanager', 'Select CSV file') + ""; + var t5; + var t6; + var br; + var t7; + var input; + var t8; + var button; + var t10; + var t11; + var t12; + var t13; + var t14; + var t15; + var if_block4_anchor; var current; var mounted; var dispose; - var if_block = /*showTooltip*/ctx[1] && create_if_block(ctx); + var if_block0 = /*parseError*/ctx[9] && create_if_block_6(ctx); + var if_block1 = /*importError*/ctx[6] && create_if_block_5(ctx); + var if_block2 = /*successMessage*/ctx[7] && create_if_block_4(ctx); + var if_block3 = /*importPreviewData*/ctx[8].length && create_if_block_3(ctx); + var each_value = /*importPreviewData*/ctx[8]; + var each_blocks = []; + for (var i = 0; i < each_value.length; i += 1) { + each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i)); + } + var if_block4 = /*importPreviewData*/ctx[8].length && create_if_block$1(ctx); return { c: function c() { - button = element("button"); + form = element("form"); + label0 = element("label"); t0 = text$1(t0_value); t1 = space$1(); - if (if_block) if_block.c(); - if_block_anchor = empty(); - attr(button, "class", button_class_value = "filter-button icon-filter button-w-icon ".concat( /*$isFilterSet*/ctx[2] ? 'active' : '')); + select = element("select"); + option0 = element("option"); + option0.textContent = ","; + option1 = element("option"); + option1.textContent = ";"; + t4 = space$1(); + label1 = element("label"); + t5 = text$1(t5_value); + t6 = space$1(); + br = element("br"); + t7 = space$1(); + input = element("input"); + t8 = space$1(); + button = element("button"); + button.textContent = "".concat(translate_1('timemanager', 'Generate preview from file')); + t10 = space$1(); + if (if_block0) if_block0.c(); + t11 = space$1(); + if (if_block1) if_block1.c(); + t12 = space$1(); + if (if_block2) if_block2.c(); + t13 = space$1(); + if (if_block3) if_block3.c(); + t14 = space$1(); + for (var _i7 = 0; _i7 < each_blocks.length; _i7 += 1) { + each_blocks[_i7].c(); + } + t15 = space$1(); + if (if_block4) if_block4.c(); + if_block4_anchor = empty(); + option0.selected = true; + option0.__value = ","; + option0.value = option0.__value; + option1.__value = ";"; + option1.value = option1.__value; + attr(select, "name", "delimiter"); + attr(input, "type", "file"); + attr(button, "type", "submit"); }, m: function m(target, anchor) { - insert(target, button, anchor); - append(button, t0); - insert(target, t1, anchor); - if (if_block) if_block.m(target, anchor); - insert(target, if_block_anchor, anchor); + insert(target, form, anchor); + append(form, label0); + append(label0, t0); + append(label0, t1); + append(label0, select); + append(select, option0); + append(select, option1); + /*select_binding*/ + ctx[15](select); + append(form, t4); + append(form, label1); + append(label1, t5); + append(label1, t6); + append(label1, br); + append(label1, t7); + append(label1, input); + /*input_binding*/ + ctx[16](input); + append(form, t8); + append(form, button); + insert(target, t10, anchor); + if (if_block0) if_block0.m(target, anchor); + insert(target, t11, anchor); + if (if_block1) if_block1.m(target, anchor); + insert(target, t12, anchor); + if (if_block2) if_block2.m(target, anchor); + insert(target, t13, anchor); + if (if_block3) if_block3.m(target, anchor); + insert(target, t14, anchor); + for (var _i8 = 0; _i8 < each_blocks.length; _i8 += 1) { + if (each_blocks[_i8]) { + each_blocks[_i8].m(target, anchor); + } + } + insert(target, t15, anchor); + if (if_block4) if_block4.m(target, anchor); + insert(target, if_block4_anchor, anchor); current = true; if (!mounted) { - dispose = [action_destroyer(/*popperRef*/ctx[3].call(null, button)), listen(button, "click", /*click_handler*/ctx[6])]; + dispose = listen(form, "submit", prevent_default( /*previewFile*/ctx[11])); mounted = true; } }, - p: function p(ctx, _ref) { - var _ref2 = _slicedToArray(_ref, 1), - dirty = _ref2[0]; - if (!current || dirty & /*$isFilterSet*/4 && button_class_value !== (button_class_value = "filter-button icon-filter button-w-icon ".concat( /*$isFilterSet*/ctx[2] ? 'active' : ''))) { - attr(button, "class", button_class_value); - } - if ( /*showTooltip*/ctx[1]) { - if (if_block) { - if_block.p(ctx, dirty); - if (dirty & /*showTooltip*/2) { - transition_in(if_block, 1); + p: function p(ctx, dirty) { + if ( /*parseError*/ctx[9]) { + if (if_block0) { + if_block0.p(ctx, dirty); + if (dirty[0] & /*parseError*/512) { + transition_in(if_block0, 1); } } else { - if_block = create_if_block(ctx); - if_block.c(); - transition_in(if_block, 1); - if_block.m(if_block_anchor.parentNode, if_block_anchor); + if_block0 = create_if_block_6(ctx); + if_block0.c(); + transition_in(if_block0, 1); + if_block0.m(t11.parentNode, t11); } - } else if (if_block) { + } else if (if_block0) { group_outros(); - transition_out(if_block, 1, 1, function () { - if_block = null; + transition_out(if_block0, 1, 1, function () { + if_block0 = null; }); check_outros(); } - }, - i: function i(local) { - if (current) return; - transition_in(if_block); - current = true; - }, - o: function o(local) { - transition_out(if_block); - current = false; - }, - d: function d(detaching) { - if (detaching) detach(button); - if (detaching) detach(t1); - if (if_block) if_block.d(detaching); - if (detaching) detach(if_block_anchor); - mounted = false; - run_all(dispose); - } - }; - } - function instance($$self, $$props, $$invalidate) { - var $isFilterSet; - component_subscribe($$self, isFilterSet, function ($$value) { - return $$invalidate(2, $isFilterSet = $$value); - }); - var requestToken = $$props.requestToken; - var _createPopperActions = createPopperActions({ - placement: "bottom", - strategy: "fixed" - }), - _createPopperActions2 = _slicedToArray(_createPopperActions, 2), - popperRef = _createPopperActions2[0], - popperContent = _createPopperActions2[1]; - var extraOpts = { - modifiers: [{ - name: "offset", - options: { - offset: [0, 8] - } - }] - }; - var showTooltip = false; - onMount(function () { - var hideTooltip = function hideTooltip(e) { - if (e.key === "Escape") { - $$invalidate(1, showTooltip = false); - } - }; - document.addEventListener("keyup", hideTooltip); - isFilterSet.set(false); - - // Parse current URL - var urlParts = document.location.href.split("?"); - if (urlParts.length > 1) { - var queryString = urlParts[1]; - var queryStringParts = queryString.split("&"); - - // Map over all query params - var _iterator = _createForOfIteratorHelper$1(queryStringParts), - _step; - try { - for (_iterator.s(); !(_step = _iterator.n()).done;) { - var part = _step.value; - // Split query params - var partParts = part.split("="); - var _partParts = _slicedToArray(partParts, 2), - name = _partParts[0], - value = _partParts[1]; - - // Apply filters from query params - if (name === "userFilter" && value) { - isFilterSet.set(true); - } - } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } - } - return function () { - document.removeEventListener("keyup", hideTooltip); - isFilterSet.set(false); - }; - }); - var click_handler = function click_handler() { - $$invalidate(1, showTooltip = !showTooltip); - }; - $$self.$$set = function ($$props) { - if ('requestToken' in $$props) $$invalidate(0, requestToken = $$props.requestToken); - }; - return [requestToken, showTooltip, $isFilterSet, popperRef, popperContent, extraOpts, click_handler]; - } - var UserFilterButton = /*#__PURE__*/function (_SvelteComponent) { - _inherits$1(UserFilterButton, _SvelteComponent); - var _super = _createSuper$1(UserFilterButton); - function UserFilterButton(options) { - var _this; - _classCallCheck$1(this, UserFilterButton); - _this = _super.call(this); - init$2(_assertThisInitialized$1(_this), options, instance, create_fragment, safe_not_equal, { - requestToken: 0 - }); - return _this; - } - return _createClass$1(UserFilterButton); - }(SvelteComponent); - - /* global HTMLCollection: true */ - - var foreachEls = function (els, fn, context) { - if (els instanceof HTMLCollection || els instanceof NodeList || els instanceof Array) { - return Array.prototype.forEach.call(els, fn, context); - } - // assume simple DOM element - return fn.call(context, els); - }; - - var evalScript = function (el) { - var code = el.text || el.textContent || el.innerHTML || ""; - var src = el.src || ""; - var parent = el.parentNode || document.querySelector("head") || document.documentElement; - var script = document.createElement("script"); - if (code.match("document.write")) { - if (console && console.log) { - console.log("Script contains document.write. Can’t be executed correctly. Code skipped ", el); - } - return false; - } - script.type = "text/javascript"; - script.id = el.id; - - /* istanbul ignore if */ - if (src !== "") { - script.src = src; - script.async = false; // force synchronous loading of peripheral JS - } - - if (code !== "") { - try { - script.appendChild(document.createTextNode(code)); - } catch (e) { - /* istanbul ignore next */ - // old IEs have funky script nodes - script.text = code; - } - } - - // execute - parent.appendChild(script); - // avoid pollution only in head or body tags - if ((parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) && parent.contains(script)) { - parent.removeChild(script); - } - return true; - }; - - // Finds and executes scripts (used for newly added elements) - // Needed since innerHTML does not run scripts - var executeScripts = function (el) { - if (el.tagName.toLowerCase() === "script") { - evalScript(el); - } - foreachEls(el.querySelectorAll("script"), function (script) { - if (!script.type || script.type.toLowerCase() === "text/javascript") { - if (script.parentNode) { - script.parentNode.removeChild(script); - } - evalScript(script); - } - }); - }; - - var on = function (els, events, listener, useCapture) { - events = typeof events === "string" ? events.split(" ") : events; - events.forEach(function (e) { - foreachEls(els, function (el) { - el.addEventListener(e, listener, useCapture); - }); - }); - }; - - var switches = { - outerHTML: function (oldEl, newEl) { - oldEl.outerHTML = newEl.outerHTML; - this.onSwitch(); - }, - innerHTML: function (oldEl, newEl) { - oldEl.innerHTML = newEl.innerHTML; - if (newEl.className === "") { - oldEl.removeAttribute("class"); - } else { - oldEl.className = newEl.className; - } - this.onSwitch(); - }, - switchElementsAlt: function (oldEl, newEl) { - oldEl.innerHTML = newEl.innerHTML; - - // Copy attributes from the new element to the old one - if (newEl.hasAttributes()) { - var attrs = newEl.attributes; - for (var i = 0; i < attrs.length; i++) { - oldEl.attributes.setNamedItem(attrs[i].cloneNode()); - } - } - this.onSwitch(); - }, - // Equivalent to outerHTML(), but doesn't require switchElementsAlt() for and - replaceNode: function (oldEl, newEl) { - oldEl.parentNode.replaceChild(newEl, oldEl); - this.onSwitch(); - }, - sideBySide: function (oldEl, newEl, options, switchOptions) { - var forEach = Array.prototype.forEach; - var elsToRemove = []; - var elsToAdd = []; - var fragToAppend = document.createDocumentFragment(); - var animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"; - var animatedElsNumber = 0; - var sexyAnimationEnd = function (e) { - if (e.target !== e.currentTarget) { - // end triggered by an animation on a child - return; - } - animatedElsNumber--; - if (animatedElsNumber <= 0 && elsToRemove) { - elsToRemove.forEach(function (el) { - // browsing quickly can make the el - // already removed by last page update ? - if (el.parentNode) { - el.parentNode.removeChild(el); + if ( /*importError*/ctx[6]) { + if (if_block1) { + if_block1.p(ctx, dirty); + if (dirty[0] & /*importError*/64) { + transition_in(if_block1, 1); } - }); - elsToAdd.forEach(function (el) { - el.className = el.className.replace(el.getAttribute("data-pjax-classes"), ""); - el.removeAttribute("data-pjax-classes"); - }); - elsToAdd = null; // free memory - elsToRemove = null; // free memory - - // this is to trigger some repaint (example: picturefill) - this.onSwitch(); - } - }.bind(this); - switchOptions = switchOptions || {}; - forEach.call(oldEl.childNodes, function (el) { - elsToRemove.push(el); - if (el.classList && !el.classList.contains("js-Pjax-remove")) { - // for fast switch, clean element that just have been added, & not cleaned yet. - if (el.hasAttribute("data-pjax-classes")) { - el.className = el.className.replace(el.getAttribute("data-pjax-classes"), ""); - el.removeAttribute("data-pjax-classes"); - } - el.classList.add("js-Pjax-remove"); - if (switchOptions.callbacks && switchOptions.callbacks.removeElement) { - switchOptions.callbacks.removeElement(el); - } - if (switchOptions.classNames) { - el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward); - } - animatedElsNumber++; - on(el, animationEventNames, sexyAnimationEnd, true); - } - }); - forEach.call(newEl.childNodes, function (el) { - if (el.classList) { - var addClasses = ""; - if (switchOptions.classNames) { - addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward); - } - if (switchOptions.callbacks && switchOptions.callbacks.addElement) { - switchOptions.callbacks.addElement(el); - } - el.className += addClasses; - el.setAttribute("data-pjax-classes", addClasses); - elsToAdd.push(el); - fragToAppend.appendChild(el); - animatedElsNumber++; - on(el, animationEventNames, sexyAnimationEnd, true); - } - }); - - // pass all className of the parent - oldEl.className = newEl.className; - oldEl.appendChild(fragToAppend); - } - }; - switches.outerHTML; - switches.innerHTML; - switches.switchElementsAlt; - switches.replaceNode; - switches.sideBySide; - - /* global _gaq: true, ga: true */ - - - var parseOptions$1 = function (options) { - options = options || {}; - options.elements = options.elements || "a[href], form[action]"; - options.selectors = options.selectors || ["title", ".js-Pjax"]; - options.switches = options.switches || {}; - options.switchesOptions = options.switchesOptions || {}; - options.history = typeof options.history === "undefined" ? true : options.history; - options.analytics = typeof options.analytics === "function" || options.analytics === false ? options.analytics : defaultAnalytics; - options.scrollTo = typeof options.scrollTo === "undefined" ? 0 : options.scrollTo; - options.scrollRestoration = typeof options.scrollRestoration !== "undefined" ? options.scrollRestoration : true; - options.cacheBust = typeof options.cacheBust === "undefined" ? true : options.cacheBust; - options.debug = options.debug || false; - options.timeout = options.timeout || 0; - options.currentUrlFullReload = typeof options.currentUrlFullReload === "undefined" ? false : options.currentUrlFullReload; - - // We can’t replace body.outerHTML or head.outerHTML. - // It creates a bug where a new body or head are created in the DOM. - // If you set head.outerHTML, a new body tag is appended, so the DOM has 2 body nodes, and vice versa - if (!options.switches.head) { - options.switches.head = switches.switchElementsAlt; - } - if (!options.switches.body) { - options.switches.body = switches.switchElementsAlt; - } - return options; - }; - - /* istanbul ignore next */ - function defaultAnalytics() { - if (window._gaq) { - _gaq.push(["_trackPageview"]); - } - if (window.ga) { - ga("send", "pageview", { - page: location.pathname, - title: document.title - }); - } - } - - var uniqueid = function () { - var counter = 0; - return function () { - var id = "pjax" + new Date().getTime() + "_" + counter; - counter++; - return id; - }; - }(); - - var trigger = function (els, events, opts) { - events = typeof events === "string" ? events.split(" ") : events; - events.forEach(function (e) { - var event; - event = document.createEvent("HTMLEvents"); - event.initEvent(e, true, true); - event.eventName = e; - if (opts) { - Object.keys(opts).forEach(function (key) { - event[key] = opts[key]; - }); - } - foreachEls(els, function (el) { - var domFix = false; - if (!el.parentNode && el !== document && el !== window) { - // THANK YOU IE (9/10/11) - // dispatchEvent doesn't work if the element is not in the DOM - domFix = true; - document.body.appendChild(el); - } - el.dispatchEvent(event); - if (domFix) { - el.parentNode.removeChild(el); - } - }); - }); - }; - - var clone = function (obj) { - /* istanbul ignore if */ - if (null === obj || "object" !== typeof obj) { - return obj; - } - var copy = obj.constructor(); - for (var attr in obj) { - if (obj.hasOwnProperty(attr)) { - copy[attr] = obj[attr]; - } - } - return copy; - }; - - var contains = function contains(doc, selectors, el) { - for (var i = 0; i < selectors.length; i++) { - var selectedEls = doc.querySelectorAll(selectors[i]); - for (var j = 0; j < selectedEls.length; j++) { - if (selectedEls[j].contains(el)) { - return true; - } - } - } - return false; - }; - - var extend = function (target) { - if (target == null) { - return null; - } - var to = Object(target); - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - if (source != null) { - for (var key in source) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(source, key)) { - to[key] = source[key]; + } else { + if_block1 = create_if_block_5(ctx); + if_block1.c(); + transition_in(if_block1, 1); + if_block1.m(t12.parentNode, t12); } + } else if (if_block1) { + group_outros(); + transition_out(if_block1, 1, 1, function () { + if_block1 = null; + }); + check_outros(); } - } - } - return to; - }; - - var noop = function () {}; - - var log = function () { - if (this.options.debug && console) { - if (typeof console.log === "function") { - console.log.apply(console, arguments); - } - // IE is weird - else if (console.log) { - console.log(arguments); - } - } - }; - - var attrState$2 = "data-pjax-state"; - var parseElement = function (el) { - switch (el.tagName.toLowerCase()) { - case "a": - // only attach link if el does not already have link attached - if (!el.hasAttribute(attrState$2)) { - this.attachLink(el); + if ( /*successMessage*/ctx[7]) { + if (if_block2) { + if_block2.p(ctx, dirty); + if (dirty[0] & /*successMessage*/128) { + transition_in(if_block2, 1); + } + } else { + if_block2 = create_if_block_4(ctx); + if_block2.c(); + transition_in(if_block2, 1); + if_block2.m(t13.parentNode, t13); + } + } else if (if_block2) { + group_outros(); + transition_out(if_block2, 1, 1, function () { + if_block2 = null; + }); + check_outros(); } - break; - case "form": - // only attach link if el does not already have link attached - if (!el.hasAttribute(attrState$2)) { - this.attachForm(el); + if ( /*importPreviewData*/ctx[8].length) { + if (if_block3) { + if_block3.p(ctx, dirty); + } else { + if_block3 = create_if_block_3(ctx); + if_block3.c(); + if_block3.m(t14.parentNode, t14); + } + } else if (if_block3) { + if_block3.d(1); + if_block3 = null; } - break; - default: - throw "Pjax can only be applied on or submit"; - } - }; - - var attrState$1 = "data-pjax-state"; - var linkAction = function (el, event) { - if (isDefaultPrevented$1(event)) { - return; - } - - // Since loadUrl modifies options and we may add our own modifications below, - // clone it so the changes don't persist - var options = clone(this.options); - var attrValue = checkIfShouldAbort$1(el, event); - if (attrValue) { - el.setAttribute(attrState$1, attrValue); - return; - } - event.preventDefault(); - - // don’t do "nothing" if user try to reload the page by clicking the same link twice - if (this.options.currentUrlFullReload && el.href === window.location.href.split("#")[0]) { - el.setAttribute(attrState$1, "reload"); - this.reload(); - return; - } - el.setAttribute(attrState$1, "load"); - options.triggerElement = el; - this.loadUrl(el.href, options); - }; - function checkIfShouldAbort$1(el, event) { - // Don’t break browser special behavior on links (like page in new window) - if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { - return "modifier"; - } - - // we do test on href now to prevent unexpected behavior if for some reason - // user have href that can be dynamically updated - - // Ignore external links. - if (el.protocol !== window.location.protocol || el.host !== window.location.host) { - return "external"; - } - - // Ignore anchors on the same page (keep native behavior) - if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) { - return "anchor"; - } - - // Ignore empty anchor "foo.html#" - if (el.href === window.location.href.split("#")[0] + "#") { - return "anchor-empty"; - } - } - var isDefaultPrevented$1 = function (event) { - return event.defaultPrevented || event.returnValue === false; - }; - var attachLink = function (el) { - var that = this; - el.setAttribute(attrState$1, ""); - on(el, "click", function (event) { - linkAction.call(that, el, event); - }); - on(el, "keyup", function (event) { - if (event.keyCode === 13) { - linkAction.call(that, el, event); - } - }.bind(this)); - }; - - var attrState = "data-pjax-state"; - var formAction = function (el, event) { - if (isDefaultPrevented(event)) { - return; - } - - // Since loadUrl modifies options and we may add our own modifications below, - // clone it so the changes don't persist - var options = clone(this.options); - - // Initialize requestOptions - options.requestOptions = { - requestUrl: el.getAttribute("action") || window.location.href, - requestMethod: el.getAttribute("method") || "GET" - }; - - // create a testable virtual link of the form action - var virtLinkElement = document.createElement("a"); - virtLinkElement.setAttribute("href", options.requestOptions.requestUrl); - var attrValue = checkIfShouldAbort(virtLinkElement, options); - if (attrValue) { - el.setAttribute(attrState, attrValue); - return; - } - event.preventDefault(); - if (el.enctype === "multipart/form-data") { - options.requestOptions.formData = new FormData(el); - } else { - options.requestOptions.requestParams = parseFormElements(el); - } - el.setAttribute(attrState, "submit"); - options.triggerElement = el; - this.loadUrl(virtLinkElement.href, options); - }; - function parseFormElements(el) { - var requestParams = []; - var formElements = el.elements; - for (var i = 0; i < formElements.length; i++) { - var element = formElements[i]; - var tagName = element.tagName.toLowerCase(); - // jscs:disable disallowImplicitTypeConversion - if (!!element.name && element.attributes !== undefined && tagName !== "button") { - // jscs:enable disallowImplicitTypeConversion - var type = element.attributes.type; - if (!type || type.value !== "checkbox" && type.value !== "radio" || element.checked) { - // Build array of values to submit - var values = []; - if (tagName === "select") { - var opt; - for (var j = 0; j < element.options.length; j++) { - opt = element.options[j]; - if (opt.selected && !opt.disabled) { - values.push(opt.hasAttribute("value") ? opt.value : opt.text); - } + if (dirty[0] & /*allOpen, importPreviewData*/1280) { + each_value = /*importPreviewData*/ctx[8]; + var _i9; + for (_i9 = 0; _i9 < each_value.length; _i9 += 1) { + var child_ctx = get_each_context(ctx, each_value, _i9); + if (each_blocks[_i9]) { + each_blocks[_i9].p(child_ctx, dirty); + } else { + each_blocks[_i9] = create_each_block(child_ctx); + each_blocks[_i9].c(); + each_blocks[_i9].m(t15.parentNode, t15); } - } else { - values.push(element.value); } - for (var k = 0; k < values.length; k++) { - requestParams.push({ - name: encodeURIComponent(element.name), - value: encodeURIComponent(values[k]) - }); + for (; _i9 < each_blocks.length; _i9 += 1) { + each_blocks[_i9].d(1); + } + each_blocks.length = each_value.length; + } + if ( /*importPreviewData*/ctx[8].length) { + if (if_block4) { + if_block4.p(ctx, dirty); + } else { + if_block4 = create_if_block$1(ctx); + if_block4.c(); + if_block4.m(if_block4_anchor.parentNode, if_block4_anchor); } + } else if (if_block4) { + if_block4.d(1); + if_block4 = null; } + }, + i: function i(local) { + if (current) return; + transition_in(if_block0); + transition_in(if_block1); + transition_in(if_block2); + current = true; + }, + o: function o(local) { + transition_out(if_block0); + transition_out(if_block1); + transition_out(if_block2); + current = false; + }, + d: function d(detaching) { + if (detaching) detach(form); + /*select_binding*/ + ctx[15](null); + /*input_binding*/ + ctx[16](null); + if (detaching) detach(t10); + if (if_block0) if_block0.d(detaching); + if (detaching) detach(t11); + if (if_block1) if_block1.d(detaching); + if (detaching) detach(t12); + if (if_block2) if_block2.d(detaching); + if (detaching) detach(t13); + if (if_block3) if_block3.d(detaching); + if (detaching) detach(t14); + destroy_each(each_blocks, detaching); + if (detaching) detach(t15); + if (if_block4) if_block4.d(detaching); + if (detaching) detach(if_block4_anchor); + mounted = false; + dispose(); } - } - return requestParams; - } - function checkIfShouldAbort(virtLinkElement, options) { - // Ignore external links. - if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) { - return "external"; - } - - // Ignore click if we are on an anchor on the same page - if (virtLinkElement.hash && virtLinkElement.href.replace(virtLinkElement.hash, "") === window.location.href.replace(location.hash, "")) { - return "anchor"; - } - - // Ignore empty anchor "foo.html#" - if (virtLinkElement.href === window.location.href.split("#")[0] + "#") { - return "anchor-empty"; - } - - // if declared as a full reload, just normally submit the form - if (options.currentUrlFullReload && virtLinkElement.href === window.location.href.split("#")[0]) { - return "reload"; - } + }; } - var isDefaultPrevented = function (event) { - return event.defaultPrevented || event.returnValue === false; - }; - var attachForm = function (el) { - var that = this; - el.setAttribute(attrState, ""); - on(el, "submit", function (event) { - formAction.call(that, el, event); - }); - }; - - var foreachSelectors = function (selectors, cb, context, DOMcontext) { - DOMcontext = DOMcontext || document; - selectors.forEach(function (selector) { - foreachEls(DOMcontext.querySelectorAll(selector), cb, context); - }); - }; - - var switchesSelectors = function (switches$1, switchesOptions, selectors, fromEl, toEl, options) { - var switchesQueue = []; - selectors.forEach(function (selector) { - var newEls = fromEl.querySelectorAll(selector); - var oldEls = toEl.querySelectorAll(selector); - if (this.log) { - this.log("Pjax switch", selector, newEls, oldEls); - } - if (newEls.length !== oldEls.length) { - throw "DOM doesn’t look the same on new loaded page: ’" + selector + "’ - new " + newEls.length + ", old " + oldEls.length; - } - foreachEls(newEls, function (newEl, i) { - var oldEl = oldEls[i]; - if (this.log) { - this.log("newEl", newEl, "oldEl", oldEl); - } - var callback = switches$1[selector] ? switches$1[selector].bind(this, oldEl, newEl, options, switchesOptions[selector]) : switches.outerHTML.bind(this, oldEl, newEl, options); - switchesQueue.push(callback); - }, this); - }, this); - this.state.numPendingSwitches = switchesQueue.length; - switchesQueue.forEach(function (queuedSwitch) { - queuedSwitch(); - }); - }; - - var abortRequest = function (request) { - if (request && request.readyState < 4) { - request.onreadystatechange = noop; - request.abort(); - } - }; + function instance$1($$self, $$props, $$invalidate) { + var parseError; + var importError; + var successMessage; + var importPreviewData; + var loading; + var allOpen; + var syncApiUrl = $$props.syncApiUrl; + var requestToken = $$props.requestToken; + var fileInput; + var delimiterInput; - var updateQueryString = function (uri, key, value) { - var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); - var separator = uri.indexOf("?") !== -1 ? "&" : "?"; - if (uri.match(re)) { - return uri.replace(re, "$1" + key + "=" + value + "$2"); - } else { - return uri + separator + key + "=" + value; - } - }; + // Collect updated objects in here + var preparedClients = []; + var preparedProjects = []; + var preparedTasks = []; - var sendRequest = function (location, options, callback) { - options = options || {}; - var queryString; - var requestOptions = options.requestOptions || {}; - var requestMethod = (requestOptions.requestMethod || "GET").toUpperCase(); - var requestParams = requestOptions.requestParams || null; - var formData = requestOptions.formData || null; - var requestPayload = null; - var request = new XMLHttpRequest(); - var timeout = options.timeout || 0; - request.onreadystatechange = function () { - if (request.readyState === 4) { - if (request.status === 200) { - callback(request.responseText, request, location, options); - } else if (request.status !== 0) { - callback(null, request, location, options); + // Converts all keys of an object to lowercase + var keysToLowerCase = function keysToLowerCase(object) { + var result = {}; + if (object) { + for (var _i10 = 0, _Object$entries = Object.entries(object); _i10 < _Object$entries.length; _i10++) { + var _Object$entries$_i = _slicedToArray(_Object$entries[_i10], 2), + key = _Object$entries$_i[0], + value = _Object$entries$_i[1]; + result[key.toLowerCase()] = value; } } + return result; }; - request.onerror = function (e) { - console.log(e); - callback(null, request, location, options); - }; - request.ontimeout = function () { - callback(null, request, location, options); - }; - - // Prepare the request payload for forms, if available - if (requestParams && requestParams.length) { - // Build query string - queryString = requestParams.map(function (param) { - return param.name + "=" + param.value; - }).join("&"); - switch (requestMethod) { - case "GET": - // Reset query string to avoid an issue with repeat submissions where checkboxes that were - // previously checked are incorrectly preserved - location = location.split("?")[0]; - - // Append new query string - location += "?" + queryString; - break; - case "POST": - // Send query string as request payload - requestPayload = queryString; - break; - } - } else if (formData) { - requestPayload = formData; - } - // Add a timestamp as part of the query string if cache busting is enabled - if (options.cacheBust) { - location = updateQueryString(location, "t", Date.now()); - } - request.open(requestMethod, location, true); - request.timeout = timeout; - request.setRequestHeader("X-Requested-With", "XMLHttpRequest"); - request.setRequestHeader("X-PJAX", "true"); - request.setRequestHeader("X-PJAX-Selectors", JSON.stringify(options.selectors)); + // Filters a list of records for type, converts all keys to lowercase and applies a uuid to each record + var filter = function filter(records, type) { + return records.map(keysToLowerCase).filter(function (record) { + return record.type && record.type.toLowerCase() === type; + }).map(function (record) { + return _objectSpread2(_objectSpread2({}, record), {}, { + uuid: v4() + }); + }); + }; - // Send the proper header information for POST forms - if (requestPayload && requestMethod === "POST" && !formData) { - request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - } - request.send(requestPayload); - return request; - }; + // Previews a given file + var previewFile = /*#__PURE__*/function () { + var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() { + var _fileInput$files, file, fileReader, contents, clients, projects, tasks, associated; + return _regeneratorRuntime().wrap(function _callee$(_context) { + while (1) switch (_context.prev = _context.next) { + case 0: + if (!(fileInput && fileInput.files && fileInput.files.length)) { + _context.next = 33; + break; + } + $$invalidate(8, importPreviewData = []); + $$invalidate(9, parseError = ""); + _fileInput$files = _slicedToArray(fileInput.files, 1), file = _fileInput$files[0]; + fileReader = new FileReader(); + fileReader.readAsText(file); + _context.next = 8; + return new Promise(function (resolve, reject) { + fileReader.onload = function () { + resolve(null); + }; + fileReader.onerror = function (error) { + reject(error); + }; + }); + case 8: + contents = []; + _context.prev = 9; + _context.next = 12; + return new Promise(function (resolve, reject) { + return parse(fileReader.result, { + delimiter: delimiterInput && delimiterInput.value ? delimiterInput.value : ",", + // @TODO: Make encoding configurable + encoding: "utf-8", + columns: true + }, function (err, records) { + if (err) { + reject(err); + } + resolve(records); + }); + }); + case 12: + contents = _context.sent; + _context.next = 19; + break; + case 15: + _context.prev = 15; + _context.t0 = _context["catch"](9); + $$invalidate(9, parseError = _context.t0); + return _context.abrupt("return"); + case 19: + if (!(!contents || !contents.length)) { + _context.next = 22; + break; + } + $$invalidate(9, parseError = translate_1("timemanager", "It looks like this file is not a CSV file or doesn't contain any clients, projects or tasks.")); + return _context.abrupt("return"); + case 22: + // Filter by type and assign uuids + clients = filter(contents, "client"); + projects = filter(contents, "project"); + tasks = filter(contents, "task"); + if (!(!clients.length && !projects.length && !tasks.length)) { + _context.next = 28; + break; + } + $$invalidate(9, parseError = translate_1("timemanager", "It looks like this file is not a CSV file or doesn't contain any clients, projects or tasks.")); + return _context.abrupt("return"); + case 28: + // Empty arrays + $$invalidate(2, preparedClients = []); + $$invalidate(3, preparedProjects = []); + $$invalidate(4, preparedTasks = []); - var handleResponse = function (responseText, request, href, options) { - options = clone(options || this.options); - options.request = request; + // Group entities + associated = clients.map(function (client) { + client.projects = projects.filter(function (project) { + return project.client === client.name && !preparedProjects.find(function (oneProject) { + return oneProject.uuid === project.uuid; + }); + }).map(function (project) { + return _objectSpread2(_objectSpread2({}, project), {}, { + client_uuid: client.uuid + }); + }).map(function (project) { + project.tasks = tasks.filter(function (task) { + return task.project === project.name && !preparedTasks.find(function (oneTask) { + return oneTask.uuid === task.uuid; + }); + }).map(function (task) { + return _objectSpread2(_objectSpread2({}, task), {}, { + project_uuid: project.uuid + }); + }); - // Fail if unable to load HTML via AJAX - if (responseText === false) { - trigger(document, "pjax:complete pjax:error", options); - return; - } + // Add tasks if not exists + project.tasks.forEach(function (task) { + if (!preparedTasks.find(function (oneTask) { + return oneTask.uuid === task.uuid; + })) { + preparedTasks.push(task); + } + }); - // push scroll position to history - var currentState = window.history.state || {}; - window.history.replaceState({ - url: currentState.url || window.location.href, - title: currentState.title || document.title, - uid: currentState.uid || uniqueid(), - scrollPos: [document.documentElement.scrollLeft || document.body.scrollLeft, document.documentElement.scrollTop || document.body.scrollTop] - }, document.title, window.location.href); + // Add project if not exists + if (!preparedProjects.find(function (oneProject) { + return oneProject.uuid === project.uuid; + })) { + preparedProjects.push(project); + } + return project; + }); - // Check for redirects - var oldHref = href; - if (request.responseURL) { - if (href !== request.responseURL) { - href = request.responseURL; - } - } else if (request.getResponseHeader("X-PJAX-URL")) { - href = request.getResponseHeader("X-PJAX-URL"); - } else if (request.getResponseHeader("X-XHR-Redirected-To")) { - href = request.getResponseHeader("X-XHR-Redirected-To"); - } + // Add client + preparedClients.push(client); + return client; + }); + $$invalidate(8, importPreviewData = associated); + case 33: + case "end": + return _context.stop(); + } + }, _callee, null, [[9, 15]]); + })); + return function previewFile() { + return _ref.apply(this, arguments); + }; + }(); // @TODO: LOW: List unassociated elements (not in import & not in store) - // Add back the hash if it was removed - var a = document.createElement("a"); - a.href = oldHref; - var oldHash = a.hash; - a.href = href; - if (oldHash && !a.hash) { - a.hash = oldHash; - href = a.href; + // Post data to JSON API + var doImport = /*#__PURE__*/function () { + var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() { + var convertedImportData, response; + return _regeneratorRuntime().wrap(function _callee2$(_context2) { + while (1) switch (_context2.prev = _context2.next) { + case 0: + $$invalidate(5, loading = true); + $$invalidate(6, importError = ""); + convertedImportData = { + lastCommit: "", + data: { + clients: { + created: preparedClients.map(function (client) { + delete client.type; + delete client.projects; + return client; + }), + updated: [], + deleted: [] + }, + projects: { + created: preparedProjects.map(function (project) { + delete project.type; + delete project.tasks; + return project; + }), + updated: [], + deleted: [] + }, + tasks: { + created: preparedTasks.map(function (task) { + delete task.type; + return task; + }), + updated: [], + deleted: [] + }, + times: { + created: [], + updated: [], + deleted: [] + } + } + }; + _context2.prev = 3; + _context2.next = 6; + return fetch(syncApiUrl, { + method: "POST", + headers: { + requesttoken: requestToken, + "content-type": "application/json" + }, + body: JSON.stringify(convertedImportData) + }); + case 6: + response = _context2.sent; + if (response.ok) { + $$invalidate(8, importPreviewData = []); + $$invalidate(7, successMessage = translate_1("timemanager", "Imported {clientsCount} client(s), {projectsCount} project(s), {tasksCount} task(s)", { + clientsCount: preparedClients.length, + projectsCount: preparedProjects.length, + tasksCount: preparedTasks.length + })); + } + _context2.next = 13; + break; + case 10: + _context2.prev = 10; + _context2.t0 = _context2["catch"](3); + $$invalidate(6, importError = _context2.t0); + case 13: + $$invalidate(5, loading = false); + case 14: + case "end": + return _context2.stop(); + } + }, _callee2, null, [[3, 10]]); + })); + return function doImport() { + return _ref2.apply(this, arguments); + }; + }(); + function select_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](function () { + delimiterInput = $$value; + $$invalidate(1, delimiterInput); + }); } - this.state.href = href; - this.state.options = options; - try { - this.loadContent(responseText, options); - } catch (e) { - trigger(document, "pjax:error", options); - if (!this.options.debug) { - if (console && console.error) { - console.error("Pjax switch fail: ", e); - } - return this.latestChance(href); - } else { - throw e; - } + function input_binding($$value) { + binding_callbacks[$$value ? 'unshift' : 'push'](function () { + fileInput = $$value; + $$invalidate(0, fileInput); + }); } - }; - - var isSupported = function () { - // Borrowed wholesale from https://github.com/defunkt/jquery-pjax - return window.history && window.history.pushState && window.history.replaceState && - // pushState isn’t reliable on iOS until 5. - !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/); - }; - - var pjax$1 = createCommonjsModule(function (module) { - var Pjax = function (options) { - this.state = { - numPendingSwitches: 0, - href: null, - options: null + var click_handler = function click_handler() { + $$invalidate(9, parseError = ''); }; - this.options = parseOptions$1(options); - this.log("Pjax options", this.options); - if (this.options.scrollRestoration && "scrollRestoration" in history) { - history.scrollRestoration = "manual"; + var click_handler_1 = function click_handler_1() { + $$invalidate(6, importError = ''); + }; + var click_handler_2 = function click_handler_2() { + $$invalidate(7, successMessage = ''); + }; + var click_handler_3 = function click_handler_3() { + return $$invalidate(10, allOpen = false); + }; + var click_handler_4 = function click_handler_4() { + return $$invalidate(10, allOpen = true); + }; + $$self.$$set = function ($$props) { + if ('syncApiUrl' in $$props) $$invalidate(13, syncApiUrl = $$props.syncApiUrl); + if ('requestToken' in $$props) $$invalidate(14, requestToken = $$props.requestToken); + }; + $$invalidate(9, parseError = ""); + $$invalidate(6, importError = ""); + $$invalidate(7, successMessage = ""); + $$invalidate(8, importPreviewData = []); + $$invalidate(5, loading = false); + $$invalidate(10, allOpen = false); + return [fileInput, delimiterInput, preparedClients, preparedProjects, preparedTasks, loading, importError, successMessage, importPreviewData, parseError, allOpen, previewFile, doImport, syncApiUrl, requestToken, select_binding, input_binding, click_handler, click_handler_1, click_handler_2, click_handler_3, click_handler_4]; + } + var Import = /*#__PURE__*/function (_SvelteComponent) { + _inherits$1(Import, _SvelteComponent); + var _super = _createSuper$1(Import); + function Import(options) { + var _this; + _classCallCheck$1(this, Import); + _this = _super.call(this); + init$2(_assertThisInitialized$1(_this), options, instance$1, create_fragment$1, safe_not_equal, { + syncApiUrl: 13, + requestToken: 14 + }, null, [-1, -1]); + return _this; } - this.maxUid = this.lastUid = uniqueid(); - this.parseDOM(document); - on(window, "popstate", function (st) { - if (st.state) { - var opt = clone(this.options); - opt.url = st.state.url; - opt.title = st.state.title; - // Since state already exists, prevent it from being pushed again - opt.history = false; - opt.scrollPos = st.state.scrollPos; - if (st.state.uid < this.lastUid) { - opt.backward = true; - } else { - opt.forward = true; - } - this.lastUid = st.state.uid; - - // @todo implement history cache here, based on uid - this.loadUrl(st.state.url, opt); - } - }.bind(this)); - }; - Pjax.switches = switches; - Pjax.prototype = { - log: log, - getElements: function (el) { - return el.querySelectorAll(this.options.elements); - }, - parseDOM: function (el) { - var parseElement$1 = parseElement; - foreachEls(this.getElements(el), parseElement$1, this); - }, - refresh: function (el) { - this.parseDOM(el || document); - }, - reload: function () { - window.location.reload(); - }, - attachLink: attachLink, - attachForm: attachForm, - forEachSelectors: function (cb, context, DOMcontext) { - return foreachSelectors.bind(this)(this.options.selectors, cb, context, DOMcontext); - }, - switchSelectors: function (selectors, fromEl, toEl, options) { - return switchesSelectors.bind(this)(this.options.switches, this.options.switchesOptions, selectors, fromEl, toEl, options); - }, - latestChance: function (href) { - window.location = href; - }, - onSwitch: function () { - trigger(window, "resize scroll"); - this.state.numPendingSwitches--; + return _createClass$1(Import); + }(SvelteComponent); - // debounce calls, so we only run this once after all switches are finished. - if (this.state.numPendingSwitches === 0) { - this.afterAllSwitches(); - } - }, - loadContent: function (html, options) { - if (typeof html !== "string") { - trigger(document, "pjax:complete pjax:error", options); - return; + function create_if_block(ctx) { + var div; + var userfilterselect; + var current; + var mounted; + var dispose; + userfilterselect = new UserFilterSelect({ + props: { + isVisible: /*showTooltip*/ctx[1], + requestToken: /*requestToken*/ctx[0] } - var tmpEl = document.implementation.createHTMLDocument("pjax"); - - // parse HTML attributes to copy them - // since we are forced to use documentElement.innerHTML (outerHTML can't be used for ) - var htmlRegex = /]+>/gi; - var htmlAttribsRegex = /\s?[a-z:]+(?:=['"][^'">]+['"])*/gi; - var matches = html.match(htmlRegex); - if (matches && matches.length) { - matches = matches[0].match(htmlAttribsRegex); - if (matches.length) { - matches.shift(); - matches.forEach(function (htmlAttrib) { - var attr = htmlAttrib.trim().split("="); - if (attr.length === 1) { - tmpEl.documentElement.setAttribute(attr[0], true); - } else { - tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1)); - } - }); + }); + return { + c: function c() { + div = element("div"); + create_component(userfilterselect.$$.fragment); + attr(div, "class", "popover"); + }, + m: function m(target, anchor) { + insert(target, div, anchor); + mount_component(userfilterselect, div, null); + current = true; + if (!mounted) { + dispose = action_destroyer(/*popperContent*/ctx[4].call(null, div, /*extraOpts*/ctx[5])); + mounted = true; } + }, + p: function p(ctx, dirty) { + var userfilterselect_changes = {}; + if (dirty & /*showTooltip*/2) userfilterselect_changes.isVisible = /*showTooltip*/ctx[1]; + if (dirty & /*requestToken*/1) userfilterselect_changes.requestToken = /*requestToken*/ctx[0]; + userfilterselect.$set(userfilterselect_changes); + }, + i: function i(local) { + if (current) return; + transition_in(userfilterselect.$$.fragment, local); + current = true; + }, + o: function o(local) { + transition_out(userfilterselect.$$.fragment, local); + current = false; + }, + d: function d(detaching) { + if (detaching) detach(div); + destroy_component(userfilterselect); + mounted = false; + dispose(); } - tmpEl.documentElement.innerHTML = html; - this.log("load content", tmpEl.documentElement.attributes, tmpEl.documentElement.innerHTML.length); - - // Clear out any focused controls before inserting new page contents. - if (document.activeElement && contains(document, this.options.selectors, document.activeElement)) { - try { - document.activeElement.blur(); - } catch (e) {} // eslint-disable-line no-empty - } - - this.switchSelectors(this.options.selectors, tmpEl, document, options); - }, - abortRequest: abortRequest, - doRequest: sendRequest, - handleResponse: handleResponse, - loadUrl: function (href, options) { - options = typeof options === "object" ? extend({}, this.options, options) : clone(this.options); - this.log("load href", href, options); - - // Abort any previous request - this.abortRequest(this.request); - trigger(document, "pjax:send", options); - - // Do the request - this.request = this.doRequest(href, options, this.handleResponse.bind(this)); - }, - afterAllSwitches: function () { - // FF bug: Won’t autofocus fields that are inserted via JS. - // This behavior is incorrect. So if theres no current focus, autofocus - // the last field. - // - // http://www.w3.org/html/wg/drafts/html/master/forms.html - var autofocusEl = Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop(); - if (autofocusEl && document.activeElement !== autofocusEl) { - autofocusEl.focus(); - } - - // execute scripts when DOM have been completely updated - this.options.selectors.forEach(function (selector) { - foreachEls(document.querySelectorAll(selector), function (el) { - executeScripts(el); - }); - }); - var state = this.state; - if (state.options.history) { - if (!window.history.state) { - this.lastUid = this.maxUid = uniqueid(); - window.history.replaceState({ - url: window.location.href, - title: document.title, - uid: this.maxUid, - scrollPos: [0, 0] - }, document.title); + }; + } + function create_fragment(ctx) { + var button; + var t0_value = translate_1('timemanager', 'Filter by person') + ""; + var t0; + var button_class_value; + var t1; + var if_block_anchor; + var current; + var mounted; + var dispose; + var if_block = /*showTooltip*/ctx[1] && create_if_block(ctx); + return { + c: function c() { + button = element("button"); + t0 = text$1(t0_value); + t1 = space$1(); + if (if_block) if_block.c(); + if_block_anchor = empty(); + attr(button, "class", button_class_value = "filter-button icon-filter button-w-icon ".concat( /*$isFilterSet*/ctx[2] ? 'active' : '')); + }, + m: function m(target, anchor) { + insert(target, button, anchor); + append(button, t0); + insert(target, t1, anchor); + if (if_block) if_block.m(target, anchor); + insert(target, if_block_anchor, anchor); + current = true; + if (!mounted) { + dispose = [action_destroyer(/*popperRef*/ctx[3].call(null, button)), listen(button, "click", /*click_handler*/ctx[6])]; + mounted = true; } - - // Update browser history - this.lastUid = this.maxUid = uniqueid(); - window.history.pushState({ - url: state.href, - title: state.options.title, - uid: this.maxUid, - scrollPos: [0, 0] - }, state.options.title, state.href); - } - this.forEachSelectors(function (el) { - this.parseDOM(el); - }, this); - - // Fire Events - trigger(document, "pjax:complete pjax:success", state.options); - if (typeof state.options.analytics === "function") { - state.options.analytics(); - } - if (state.options.history) { - // First parse url and check for hash to override scroll - var a = document.createElement("a"); - a.href = this.state.href; - if (a.hash) { - var name = a.hash.slice(1); - name = decodeURIComponent(name); - var curtop = 0; - var target = document.getElementById(name) || document.getElementsByName(name)[0]; - if (target) { - // http://stackoverflow.com/questions/8111094/cross-browser-javascript-function-to-find-actual-position-of-an-element-in-page - if (target.offsetParent) { - do { - curtop += target.offsetTop; - target = target.offsetParent; - } while (target); + }, + p: function p(ctx, _ref) { + var _ref2 = _slicedToArray(_ref, 1), + dirty = _ref2[0]; + if (!current || dirty & /*$isFilterSet*/4 && button_class_value !== (button_class_value = "filter-button icon-filter button-w-icon ".concat( /*$isFilterSet*/ctx[2] ? 'active' : ''))) { + attr(button, "class", button_class_value); + } + if ( /*showTooltip*/ctx[1]) { + if (if_block) { + if_block.p(ctx, dirty); + if (dirty & /*showTooltip*/2) { + transition_in(if_block, 1); } - } - window.scrollTo(0, curtop); - } else if (state.options.scrollTo !== false) { - // Scroll page to top on new page load - if (state.options.scrollTo.length > 1) { - window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]); } else { - window.scrollTo(0, state.options.scrollTo); + if_block = create_if_block(ctx); + if_block.c(); + transition_in(if_block, 1); + if_block.m(if_block_anchor.parentNode, if_block_anchor); } + } else if (if_block) { + group_outros(); + transition_out(if_block, 1, 1, function () { + if_block = null; + }); + check_outros(); } - } else if (state.options.scrollRestoration && state.options.scrollPos) { - window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]); + }, + i: function i(local) { + if (current) return; + transition_in(if_block); + current = true; + }, + o: function o(local) { + transition_out(if_block); + current = false; + }, + d: function d(detaching) { + if (detaching) detach(button); + if (detaching) detach(t1); + if (if_block) if_block.d(detaching); + if (detaching) detach(if_block_anchor); + mounted = false; + run_all(dispose); } - this.state = { - numPendingSwitches: 0, - href: null, - options: null + }; + } + function instance($$self, $$props, $$invalidate) { + var $isFilterSet; + component_subscribe($$self, isFilterSet, function ($$value) { + return $$invalidate(2, $isFilterSet = $$value); + }); + var requestToken = $$props.requestToken; + var _createPopperActions = createPopperActions({ + placement: "bottom", + strategy: "fixed" + }), + _createPopperActions2 = _slicedToArray(_createPopperActions, 2), + popperRef = _createPopperActions2[0], + popperContent = _createPopperActions2[1]; + var extraOpts = { + modifiers: [{ + name: "offset", + options: { + offset: [0, 8] + } + }] + }; + var showTooltip = false; + onMount(function () { + var hideTooltip = function hideTooltip(e) { + if (e.key === "Escape") { + $$invalidate(1, showTooltip = false); + } }; - } - }; - Pjax.isSupported = isSupported; + document.addEventListener("keyup", hideTooltip); + isFilterSet.set(false); - // arguably could do `if( require("./lib/is-supported")()) {` but that might be a little to simple - if (Pjax.isSupported()) { - module.exports = Pjax; - } - // if there isn’t required browser functions, returning stupid api - else { - var stupidPjax = noop; - for (var key in Pjax.prototype) { - if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") { - stupidPjax[key] = noop; - } - } - module.exports = stupidPjax; - } - }); + // Parse current URL + var urlParts = document.location.href.split("?"); + if (urlParts.length > 1) { + var queryString = urlParts[1]; + var queryStringParts = queryString.split("&"); - var PagePjax = /*#__PURE__*/_createClass$1(function PagePjax(reload) { - _classCallCheck$1(this, PagePjax); - /** - * Enable seamless page navigation with pjax. - */ - this.pjaxInstance = new pjax$1({ - elements: [".timemanager-pjax-link"], - selectors: [".app-timemanager #app-navigation ul", ".app-timemanager #app-content .container"], - cacheBust: false, - scrollTo: true - }); - document.addEventListener("pjax:send", function () { - document.body.classList.add("loading"); - document.body.classList.remove("loading-error"); - document.body.classList.remove("tm_ready"); - }); - document.addEventListener("pjax:success", function () { - setTimeout(function () { - document.body.classList.remove("loading"); - reload(); - }, 300); - }); - document.addEventListener("pjax:error", function (error) { - // Catch session timeout and redirect to login - if (error && error.request && error.request.status === 401) { - document.location.href = "".concat(dist_4("login"), "?redirect_url=").concat(dist_4("timemanager", "index")); + // Map over all query params + var _iterator = _createForOfIteratorHelper$1(queryStringParts), + _step; + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + var part = _step.value; + // Split query params + var partParts = part.split("="); + var _partParts = _slicedToArray(partParts, 2), + name = _partParts[0], + value = _partParts[1]; + + // Apply filters from query params + if (name === "userFilter" && value) { + isFilterSet.set(true); + } + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } } - document.body.classList.remove("loading"); - document.body.classList.add("loading-error"); + return function () { + document.removeEventListener("keyup", hideTooltip); + isFilterSet.set(false); + }; }); - }); + var click_handler = function click_handler() { + $$invalidate(1, showTooltip = !showTooltip); + }; + $$self.$$set = function ($$props) { + if ('requestToken' in $$props) $$invalidate(0, requestToken = $$props.requestToken); + }; + return [requestToken, showTooltip, $isFilterSet, popperRef, popperContent, extraOpts, click_handler]; + } + var UserFilterButton = /*#__PURE__*/function (_SvelteComponent) { + _inherits$1(UserFilterButton, _SvelteComponent); + var _super = _createSuper$1(UserFilterButton); + function UserFilterButton(options) { + var _this; + _classCallCheck$1(this, UserFilterButton); + _this = _super.call(this); + init$2(_assertThisInitialized$1(_this), options, instance, create_fragment, safe_not_equal, { + requestToken: 0 + }); + return _this; + } + return _createClass$1(UserFilterButton); + }(SvelteComponent); - // Note: this is the semver.org version of the spec that it implements - // Not necessarily the package version of this code. - const SEMVER_SPEC_VERSION = '2.0.0'; - const MAX_LENGTH$2 = 256; - const MAX_SAFE_INTEGER$1 = Number.MAX_SAFE_INTEGER || /* istanbul ignore next */9007199254740991; + /* global HTMLCollection: true */ - // Max safe segment length for coercion. - const MAX_SAFE_COMPONENT_LENGTH = 16; - var constants = { - SEMVER_SPEC_VERSION, - MAX_LENGTH: MAX_LENGTH$2, - MAX_SAFE_INTEGER: MAX_SAFE_INTEGER$1, - MAX_SAFE_COMPONENT_LENGTH + var foreachEls = function (els, fn, context) { + if (els instanceof HTMLCollection || els instanceof NodeList || els instanceof Array) { + return Array.prototype.forEach.call(els, fn, context); + } + // assume simple DOM element + return fn.call(context, els); }; - const debug = typeof process === 'object' && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error('SEMVER', ...args) : () => {}; - var debug_1 = debug; + var evalScript = function (el) { + var code = el.text || el.textContent || el.innerHTML || ""; + var src = el.src || ""; + var parent = el.parentNode || document.querySelector("head") || document.documentElement; + var script = document.createElement("script"); + if (code.match("document.write")) { + if (console && console.log) { + console.log("Script contains document.write. Can’t be executed correctly. Code skipped ", el); + } + return false; + } + script.type = "text/javascript"; + script.id = el.id; - var re_1 = createCommonjsModule(function (module, exports) { - const { - MAX_SAFE_COMPONENT_LENGTH - } = constants; + /* istanbul ignore if */ + if (src !== "") { + script.src = src; + script.async = false; // force synchronous loading of peripheral JS + } - exports = module.exports = {}; + if (code !== "") { + try { + script.appendChild(document.createTextNode(code)); + } catch (e) { + /* istanbul ignore next */ + // old IEs have funky script nodes + script.text = code; + } + } - // The actual regexps go on exports.re - const re = exports.re = []; - const src = exports.src = []; - const t = exports.t = {}; - let R = 0; - const createToken = (name, value, isGlobal) => { - const index = R++; - debug_1(name, index, value); - t[name] = index; - src[index] = value; - re[index] = new RegExp(value, isGlobal ? 'g' : undefined); + // execute + parent.appendChild(script); + // avoid pollution only in head or body tags + if ((parent instanceof HTMLHeadElement || parent instanceof HTMLBodyElement) && parent.contains(script)) { + parent.removeChild(script); + } + return true; }; - // The following Regular Expressions can be used for tokenizing, - // validating, and parsing SemVer version strings. - - // ## Numeric Identifier - // A single `0`, or a non-zero digit followed by zero or more digits. - - createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*'); - createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+'); - - // ## Non-numeric Identifier - // Zero or more digits, followed by a letter or hyphen, and then zero or - // more letters, digits, or hyphens. + // Finds and executes scripts (used for newly added elements) + // Needed since innerHTML does not run scripts + var executeScripts = function (el) { + if (el.tagName.toLowerCase() === "script") { + evalScript(el); + } + foreachEls(el.querySelectorAll("script"), function (script) { + if (!script.type || script.type.toLowerCase() === "text/javascript") { + if (script.parentNode) { + script.parentNode.removeChild(script); + } + evalScript(script); + } + }); + }; - createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*'); + var on = function (els, events, listener, useCapture) { + events = typeof events === "string" ? events.split(" ") : events; + events.forEach(function (e) { + foreachEls(els, function (el) { + el.addEventListener(e, listener, useCapture); + }); + }); + }; - // ## Main Version - // Three dot-separated numeric identifiers. + var switches = { + outerHTML: function (oldEl, newEl) { + oldEl.outerHTML = newEl.outerHTML; + this.onSwitch(); + }, + innerHTML: function (oldEl, newEl) { + oldEl.innerHTML = newEl.innerHTML; + if (newEl.className === "") { + oldEl.removeAttribute("class"); + } else { + oldEl.className = newEl.className; + } + this.onSwitch(); + }, + switchElementsAlt: function (oldEl, newEl) { + oldEl.innerHTML = newEl.innerHTML; - createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})\\.` + `(${src[t.NUMERICIDENTIFIER]})`); - createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + `(${src[t.NUMERICIDENTIFIERLOOSE]})`); + // Copy attributes from the new element to the old one + if (newEl.hasAttributes()) { + var attrs = newEl.attributes; + for (var i = 0; i < attrs.length; i++) { + oldEl.attributes.setNamedItem(attrs[i].cloneNode()); + } + } + this.onSwitch(); + }, + // Equivalent to outerHTML(), but doesn't require switchElementsAlt() for and + replaceNode: function (oldEl, newEl) { + oldEl.parentNode.replaceChild(newEl, oldEl); + this.onSwitch(); + }, + sideBySide: function (oldEl, newEl, options, switchOptions) { + var forEach = Array.prototype.forEach; + var elsToRemove = []; + var elsToAdd = []; + var fragToAppend = document.createDocumentFragment(); + var animationEventNames = "animationend webkitAnimationEnd MSAnimationEnd oanimationend"; + var animatedElsNumber = 0; + var sexyAnimationEnd = function (e) { + if (e.target !== e.currentTarget) { + // end triggered by an animation on a child + return; + } + animatedElsNumber--; + if (animatedElsNumber <= 0 && elsToRemove) { + elsToRemove.forEach(function (el) { + // browsing quickly can make the el + // already removed by last page update ? + if (el.parentNode) { + el.parentNode.removeChild(el); + } + }); + elsToAdd.forEach(function (el) { + el.className = el.className.replace(el.getAttribute("data-pjax-classes"), ""); + el.removeAttribute("data-pjax-classes"); + }); + elsToAdd = null; // free memory + elsToRemove = null; // free memory - // ## Pre-release Version Identifier - // A numeric identifier, or a non-numeric identifier. + // this is to trigger some repaint (example: picturefill) + this.onSwitch(); + } + }.bind(this); + switchOptions = switchOptions || {}; + forEach.call(oldEl.childNodes, function (el) { + elsToRemove.push(el); + if (el.classList && !el.classList.contains("js-Pjax-remove")) { + // for fast switch, clean element that just have been added, & not cleaned yet. + if (el.hasAttribute("data-pjax-classes")) { + el.className = el.className.replace(el.getAttribute("data-pjax-classes"), ""); + el.removeAttribute("data-pjax-classes"); + } + el.classList.add("js-Pjax-remove"); + if (switchOptions.callbacks && switchOptions.callbacks.removeElement) { + switchOptions.callbacks.removeElement(el); + } + if (switchOptions.classNames) { + el.className += " " + switchOptions.classNames.remove + " " + (options.backward ? switchOptions.classNames.backward : switchOptions.classNames.forward); + } + animatedElsNumber++; + on(el, animationEventNames, sexyAnimationEnd, true); + } + }); + forEach.call(newEl.childNodes, function (el) { + if (el.classList) { + var addClasses = ""; + if (switchOptions.classNames) { + addClasses = " js-Pjax-add " + switchOptions.classNames.add + " " + (options.backward ? switchOptions.classNames.forward : switchOptions.classNames.backward); + } + if (switchOptions.callbacks && switchOptions.callbacks.addElement) { + switchOptions.callbacks.addElement(el); + } + el.className += addClasses; + el.setAttribute("data-pjax-classes", addClasses); + elsToAdd.push(el); + fragToAppend.appendChild(el); + animatedElsNumber++; + on(el, animationEventNames, sexyAnimationEnd, true); + } + }); - createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER]}|${src[t.NONNUMERICIDENTIFIER]})`); - createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE]}|${src[t.NONNUMERICIDENTIFIER]})`); + // pass all className of the parent + oldEl.className = newEl.className; + oldEl.appendChild(fragToAppend); + } + }; + switches.outerHTML; + switches.innerHTML; + switches.switchElementsAlt; + switches.replaceNode; + switches.sideBySide; - // ## Pre-release Version - // Hyphen, followed by one or more dot-separated pre-release version - // identifiers. + /* global _gaq: true, ga: true */ - createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER]}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`); - createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`); - // ## Build Metadata Identifier - // Any combination of digits, letters, or hyphens. + var parseOptions = function (options) { + options = options || {}; + options.elements = options.elements || "a[href], form[action]"; + options.selectors = options.selectors || ["title", ".js-Pjax"]; + options.switches = options.switches || {}; + options.switchesOptions = options.switchesOptions || {}; + options.history = typeof options.history === "undefined" ? true : options.history; + options.analytics = typeof options.analytics === "function" || options.analytics === false ? options.analytics : defaultAnalytics; + options.scrollTo = typeof options.scrollTo === "undefined" ? 0 : options.scrollTo; + options.scrollRestoration = typeof options.scrollRestoration !== "undefined" ? options.scrollRestoration : true; + options.cacheBust = typeof options.cacheBust === "undefined" ? true : options.cacheBust; + options.debug = options.debug || false; + options.timeout = options.timeout || 0; + options.currentUrlFullReload = typeof options.currentUrlFullReload === "undefined" ? false : options.currentUrlFullReload; - createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+'); + // We can’t replace body.outerHTML or head.outerHTML. + // It creates a bug where a new body or head are created in the DOM. + // If you set head.outerHTML, a new body tag is appended, so the DOM has 2 body nodes, and vice versa + if (!options.switches.head) { + options.switches.head = switches.switchElementsAlt; + } + if (!options.switches.body) { + options.switches.body = switches.switchElementsAlt; + } + return options; + }; - // ## Build Metadata - // Plus sign, followed by one or more period-separated build metadata - // identifiers. + /* istanbul ignore next */ + function defaultAnalytics() { + if (window._gaq) { + _gaq.push(["_trackPageview"]); + } + if (window.ga) { + ga("send", "pageview", { + page: location.pathname, + title: document.title + }); + } + } - createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER]}(?:\\.${src[t.BUILDIDENTIFIER]})*))`); + var uniqueid = function () { + var counter = 0; + return function () { + var id = "pjax" + new Date().getTime() + "_" + counter; + counter++; + return id; + }; + }(); - // ## Full Version String - // A main version, followed optionally by a pre-release version and - // build metadata. + var trigger = function (els, events, opts) { + events = typeof events === "string" ? events.split(" ") : events; + events.forEach(function (e) { + var event; + event = document.createEvent("HTMLEvents"); + event.initEvent(e, true, true); + event.eventName = e; + if (opts) { + Object.keys(opts).forEach(function (key) { + event[key] = opts[key]; + }); + } + foreachEls(els, function (el) { + var domFix = false; + if (!el.parentNode && el !== document && el !== window) { + // THANK YOU IE (9/10/11) + // dispatchEvent doesn't work if the element is not in the DOM + domFix = true; + document.body.appendChild(el); + } + el.dispatchEvent(event); + if (domFix) { + el.parentNode.removeChild(el); + } + }); + }); + }; - // Note that the only major, minor, patch, and pre-release sections of - // the version string are capturing groups. The build metadata is not a - // capturing group, because it should not ever be used in version - // comparison. + var clone = function (obj) { + /* istanbul ignore if */ + if (null === obj || "object" !== typeof obj) { + return obj; + } + var copy = obj.constructor(); + for (var attr in obj) { + if (obj.hasOwnProperty(attr)) { + copy[attr] = obj[attr]; + } + } + return copy; + }; - createToken('FULLPLAIN', `v?${src[t.MAINVERSION]}${src[t.PRERELEASE]}?${src[t.BUILD]}?`); - createToken('FULL', `^${src[t.FULLPLAIN]}$`); + var contains = function contains(doc, selectors, el) { + for (var i = 0; i < selectors.length; i++) { + var selectedEls = doc.querySelectorAll(selectors[i]); + for (var j = 0; j < selectedEls.length; j++) { + if (selectedEls[j].contains(el)) { + return true; + } + } + } + return false; + }; - // like full, but allows v1.2.3 and =1.2.3, which people do sometimes. - // also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty - // common in the npm registry. - createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE]}${src[t.PRERELEASELOOSE]}?${src[t.BUILD]}?`); - createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`); - createToken('GTLT', '((?:<|>)?=?)'); + var extend = function (target) { + if (target == null) { + return null; + } + var to = Object(target); + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + if (source != null) { + for (var key in source) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(source, key)) { + to[key] = source[key]; + } + } + } + } + return to; + }; - // Something like "2.*" or "1.2.x". - // Note that "x.x" is a valid xRange identifer, meaning "any version" - // Only the first item is strictly required. - createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`); - createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`); - createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + `(?:${src[t.PRERELEASE]})?${src[t.BUILD]}?` + `)?)?`); - createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + `(?:${src[t.PRERELEASELOOSE]})?${src[t.BUILD]}?` + `)?)?`); - createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`); - createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`); + var noop = function () {}; - // Coercion. - // Extract anything that could conceivably be a part of a valid semver - createToken('COERCE', `${'(^|[^\\d])' + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + `(?:$|[^\\d])`); - createToken('COERCERTL', src[t.COERCE], true); + var log = function () { + if (this.options.debug && console) { + if (typeof console.log === "function") { + console.log.apply(console, arguments); + } + // IE is weird + else if (console.log) { + console.log(arguments); + } + } + }; - // Tilde ranges. - // Meaning is "reasonably at or greater than" - createToken('LONETILDE', '(?:~>?)'); - createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true); - exports.tildeTrimReplace = '$1~'; - createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`); - createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`); + var attrState$2 = "data-pjax-state"; + var parseElement = function (el) { + switch (el.tagName.toLowerCase()) { + case "a": + // only attach link if el does not already have link attached + if (!el.hasAttribute(attrState$2)) { + this.attachLink(el); + } + break; + case "form": + // only attach link if el does not already have link attached + if (!el.hasAttribute(attrState$2)) { + this.attachForm(el); + } + break; + default: + throw "Pjax can only be applied on or submit"; + } + }; - // Caret ranges. - // Meaning is "at least and backwards compatible with" - createToken('LONECARET', '(?:\\^)'); - createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true); - exports.caretTrimReplace = '$1^'; - createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`); - createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`); + var attrState$1 = "data-pjax-state"; + var linkAction = function (el, event) { + if (isDefaultPrevented$1(event)) { + return; + } - // A simple gt/lt/eq thing, or just "" to indicate "any version" - createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`); - createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`); + // Since loadUrl modifies options and we may add our own modifications below, + // clone it so the changes don't persist + var options = clone(this.options); + var attrValue = checkIfShouldAbort$1(el, event); + if (attrValue) { + el.setAttribute(attrState$1, attrValue); + return; + } + event.preventDefault(); - // An expression to strip any whitespace between the gtlt and the thing - // it modifies, so that `> 1.2.3` ==> `>1.2.3` - createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true); - exports.comparatorTrimReplace = '$1$2$3'; + // don’t do "nothing" if user try to reload the page by clicking the same link twice + if (this.options.currentUrlFullReload && el.href === window.location.href.split("#")[0]) { + el.setAttribute(attrState$1, "reload"); + this.reload(); + return; + } + el.setAttribute(attrState$1, "load"); + options.triggerElement = el; + this.loadUrl(el.href, options); + }; + function checkIfShouldAbort$1(el, event) { + // Don’t break browser special behavior on links (like page in new window) + if (event.which > 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { + return "modifier"; + } - // Something like `1.2.3 - 1.2.4` - // Note that these all use the loose form, because they'll be - // checked against either the strict or loose comparator form - // later. - createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAIN]})` + `\\s*$`); - createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + `\\s+-\\s+` + `(${src[t.XRANGEPLAINLOOSE]})` + `\\s*$`); + // we do test on href now to prevent unexpected behavior if for some reason + // user have href that can be dynamically updated - // Star ranges basically just allow anything at all. - createToken('STAR', '(<|>)?=?\\s*\\*'); - // >=0.0.0 is like a star - createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$'); - createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$'); - }); - re_1.re; - re_1.src; - re_1.t; - re_1.tildeTrimReplace; - re_1.caretTrimReplace; - re_1.comparatorTrimReplace; + // Ignore external links. + if (el.protocol !== window.location.protocol || el.host !== window.location.host) { + return "external"; + } - // parse out just the options we care about so we always get a consistent - // obj with keys in a consistent order. - const opts = ['includePrerelease', 'loose', 'rtl']; - const parseOptions = options => !options ? {} : typeof options !== 'object' ? { - loose: true - } : opts.filter(k => options[k]).reduce((o, k) => { - o[k] = true; - return o; - }, {}); - var parseOptions_1 = parseOptions; + // Ignore anchors on the same page (keep native behavior) + if (el.hash && el.href.replace(el.hash, "") === window.location.href.replace(location.hash, "")) { + return "anchor"; + } - const numeric = /^[0-9]+$/; - const compareIdentifiers$1 = (a, b) => { - const anum = numeric.test(a); - const bnum = numeric.test(b); - if (anum && bnum) { - a = +a; - b = +b; + // Ignore empty anchor "foo.html#" + if (el.href === window.location.href.split("#")[0] + "#") { + return "anchor-empty"; } - return a === b ? 0 : anum && !bnum ? -1 : bnum && !anum ? 1 : a < b ? -1 : 1; + } + var isDefaultPrevented$1 = function (event) { + return event.defaultPrevented || event.returnValue === false; }; - const rcompareIdentifiers = (a, b) => compareIdentifiers$1(b, a); - var identifiers = { - compareIdentifiers: compareIdentifiers$1, - rcompareIdentifiers + var attachLink = function (el) { + var that = this; + el.setAttribute(attrState$1, ""); + on(el, "click", function (event) { + linkAction.call(that, el, event); + }); + on(el, "keyup", function (event) { + if (event.keyCode === 13) { + linkAction.call(that, el, event); + } + }.bind(this)); }; - const { - MAX_LENGTH: MAX_LENGTH$1, - MAX_SAFE_INTEGER - } = constants; - const { - re: re$1, - t: t$1 - } = re_1; + var attrState = "data-pjax-state"; + var formAction = function (el, event) { + if (isDefaultPrevented(event)) { + return; + } - const { - compareIdentifiers - } = identifiers; - class SemVer { - constructor(version, options) { - options = parseOptions_1(options); - if (version instanceof SemVer) { - if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) { - return version; - } else { - version = version.version; - } - } else if (typeof version !== 'string') { - throw new TypeError(`Invalid Version: ${version}`); - } - if (version.length > MAX_LENGTH$1) { - throw new TypeError(`version is longer than ${MAX_LENGTH$1} characters`); - } - debug_1('SemVer', version, options); - this.options = options; - this.loose = !!options.loose; - // this isn't actually relevant for versions, but keep it so that we - // don't run into trouble passing this.options around. - this.includePrerelease = !!options.includePrerelease; - const m = version.trim().match(options.loose ? re$1[t$1.LOOSE] : re$1[t$1.FULL]); - if (!m) { - throw new TypeError(`Invalid Version: ${version}`); - } - this.raw = version; + // Since loadUrl modifies options and we may add our own modifications below, + // clone it so the changes don't persist + var options = clone(this.options); - // these are actually numbers - this.major = +m[1]; - this.minor = +m[2]; - this.patch = +m[3]; - if (this.major > MAX_SAFE_INTEGER || this.major < 0) { - throw new TypeError('Invalid major version'); - } - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { - throw new TypeError('Invalid minor version'); - } - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { - throw new TypeError('Invalid patch version'); - } + // Initialize requestOptions + options.requestOptions = { + requestUrl: el.getAttribute("action") || window.location.href, + requestMethod: el.getAttribute("method") || "GET" + }; - // numberify any prerelease numeric ids - if (!m[4]) { - this.prerelease = []; - } else { - this.prerelease = m[4].split('.').map(id => { - if (/^[0-9]+$/.test(id)) { - const num = +id; - if (num >= 0 && num < MAX_SAFE_INTEGER) { - return num; + // create a testable virtual link of the form action + var virtLinkElement = document.createElement("a"); + virtLinkElement.setAttribute("href", options.requestOptions.requestUrl); + var attrValue = checkIfShouldAbort(virtLinkElement, options); + if (attrValue) { + el.setAttribute(attrState, attrValue); + return; + } + event.preventDefault(); + if (el.enctype === "multipart/form-data") { + options.requestOptions.formData = new FormData(el); + } else { + options.requestOptions.requestParams = parseFormElements(el); + } + el.setAttribute(attrState, "submit"); + options.triggerElement = el; + this.loadUrl(virtLinkElement.href, options); + }; + function parseFormElements(el) { + var requestParams = []; + var formElements = el.elements; + for (var i = 0; i < formElements.length; i++) { + var element = formElements[i]; + var tagName = element.tagName.toLowerCase(); + // jscs:disable disallowImplicitTypeConversion + if (!!element.name && element.attributes !== undefined && tagName !== "button") { + // jscs:enable disallowImplicitTypeConversion + var type = element.attributes.type; + if (!type || type.value !== "checkbox" && type.value !== "radio" || element.checked) { + // Build array of values to submit + var values = []; + if (tagName === "select") { + var opt; + for (var j = 0; j < element.options.length; j++) { + opt = element.options[j]; + if (opt.selected && !opt.disabled) { + values.push(opt.hasAttribute("value") ? opt.value : opt.text); + } } + } else { + values.push(element.value); } - return id; - }); + for (var k = 0; k < values.length; k++) { + requestParams.push({ + name: encodeURIComponent(element.name), + value: encodeURIComponent(values[k]) + }); + } + } } - this.build = m[5] ? m[5].split('.') : []; - this.format(); } - format() { - this.version = `${this.major}.${this.minor}.${this.patch}`; - if (this.prerelease.length) { - this.version += `-${this.prerelease.join('.')}`; - } - return this.version; + return requestParams; + } + function checkIfShouldAbort(virtLinkElement, options) { + // Ignore external links. + if (virtLinkElement.protocol !== window.location.protocol || virtLinkElement.host !== window.location.host) { + return "external"; } - toString() { - return this.version; + + // Ignore click if we are on an anchor on the same page + if (virtLinkElement.hash && virtLinkElement.href.replace(virtLinkElement.hash, "") === window.location.href.replace(location.hash, "")) { + return "anchor"; } - compare(other) { - debug_1('SemVer.compare', this.version, this.options, other); - if (!(other instanceof SemVer)) { - if (typeof other === 'string' && other === this.version) { - return 0; - } - other = new SemVer(other, this.options); - } - if (other.version === this.version) { - return 0; - } - return this.compareMain(other) || this.comparePre(other); + + // Ignore empty anchor "foo.html#" + if (virtLinkElement.href === window.location.href.split("#")[0] + "#") { + return "anchor-empty"; } - compareMain(other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options); - } - return compareIdentifiers(this.major, other.major) || compareIdentifiers(this.minor, other.minor) || compareIdentifiers(this.patch, other.patch); + + // if declared as a full reload, just normally submit the form + if (options.currentUrlFullReload && virtLinkElement.href === window.location.href.split("#")[0]) { + return "reload"; } - comparePre(other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options); - } + } + var isDefaultPrevented = function (event) { + return event.defaultPrevented || event.returnValue === false; + }; + var attachForm = function (el) { + var that = this; + el.setAttribute(attrState, ""); + on(el, "submit", function (event) { + formAction.call(that, el, event); + }); + }; - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) { - return -1; - } else if (!this.prerelease.length && other.prerelease.length) { - return 1; - } else if (!this.prerelease.length && !other.prerelease.length) { - return 0; + var foreachSelectors = function (selectors, cb, context, DOMcontext) { + DOMcontext = DOMcontext || document; + selectors.forEach(function (selector) { + foreachEls(DOMcontext.querySelectorAll(selector), cb, context); + }); + }; + + var switchesSelectors = function (switches$1, switchesOptions, selectors, fromEl, toEl, options) { + var switchesQueue = []; + selectors.forEach(function (selector) { + var newEls = fromEl.querySelectorAll(selector); + var oldEls = toEl.querySelectorAll(selector); + if (this.log) { + this.log("Pjax switch", selector, newEls, oldEls); } - let i = 0; - do { - const a = this.prerelease[i]; - const b = other.prerelease[i]; - debug_1('prerelease compare', i, a, b); - if (a === undefined && b === undefined) { - return 0; - } else if (b === undefined) { - return 1; - } else if (a === undefined) { - return -1; - } else if (a === b) { - continue; - } else { - return compareIdentifiers(a, b); - } - } while (++i); - } - compareBuild(other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options); + if (newEls.length !== oldEls.length) { + throw "DOM doesn’t look the same on new loaded page: ’" + selector + "’ - new " + newEls.length + ", old " + oldEls.length; } - let i = 0; - do { - const a = this.build[i]; - const b = other.build[i]; - debug_1('prerelease compare', i, a, b); - if (a === undefined && b === undefined) { - return 0; - } else if (b === undefined) { - return 1; - } else if (a === undefined) { - return -1; - } else if (a === b) { - continue; - } else { - return compareIdentifiers(a, b); + foreachEls(newEls, function (newEl, i) { + var oldEl = oldEls[i]; + if (this.log) { + this.log("newEl", newEl, "oldEl", oldEl); } - } while (++i); + var callback = switches$1[selector] ? switches$1[selector].bind(this, oldEl, newEl, options, switchesOptions[selector]) : switches.outerHTML.bind(this, oldEl, newEl, options); + switchesQueue.push(callback); + }, this); + }, this); + this.state.numPendingSwitches = switchesQueue.length; + switchesQueue.forEach(function (queuedSwitch) { + queuedSwitch(); + }); + }; + + var abortRequest = function (request) { + if (request && request.readyState < 4) { + request.onreadystatechange = noop; + request.abort(); } + }; - // preminor will bump the version up to the next minor release, and immediately - // down to pre-release. premajor and prepatch work the same way. - inc(release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0; - this.patch = 0; - this.minor = 0; - this.major++; - this.inc('pre', identifier); - break; - case 'preminor': - this.prerelease.length = 0; - this.patch = 0; - this.minor++; - this.inc('pre', identifier); - break; - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0; - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) { - this.inc('patch', identifier); - } - this.inc('pre', identifier); - break; - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) { - this.major++; - } - this.minor = 0; - this.patch = 0; - this.prerelease = []; - break; - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) { - this.minor++; - } - this.patch = 0; - this.prerelease = []; - break; - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) { - this.patch++; - } - this.prerelease = []; + var updateQueryString = function (uri, key, value) { + var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); + var separator = uri.indexOf("?") !== -1 ? "&" : "?"; + if (uri.match(re)) { + return uri.replace(re, "$1" + key + "=" + value + "$2"); + } else { + return uri + separator + key + "=" + value; + } + }; + + var sendRequest = function (location, options, callback) { + options = options || {}; + var queryString; + var requestOptions = options.requestOptions || {}; + var requestMethod = (requestOptions.requestMethod || "GET").toUpperCase(); + var requestParams = requestOptions.requestParams || null; + var formData = requestOptions.formData || null; + var requestPayload = null; + var request = new XMLHttpRequest(); + var timeout = options.timeout || 0; + request.onreadystatechange = function () { + if (request.readyState === 4) { + if (request.status === 200) { + callback(request.responseText, request, location, options); + } else if (request.status !== 0) { + callback(null, request, location, options); + } + } + }; + request.onerror = function (e) { + console.log(e); + callback(null, request, location, options); + }; + request.ontimeout = function () { + callback(null, request, location, options); + }; + + // Prepare the request payload for forms, if available + if (requestParams && requestParams.length) { + // Build query string + queryString = requestParams.map(function (param) { + return param.name + "=" + param.value; + }).join("&"); + switch (requestMethod) { + case "GET": + // Reset query string to avoid an issue with repeat submissions where checkboxes that were + // previously checked are incorrectly preserved + location = location.split("?")[0]; + + // Append new query string + location += "?" + queryString; break; - // This probably shouldn't be used publicly. - // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) { - this.prerelease = [0]; - } else { - let i = this.prerelease.length; - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++; - i = -2; - } - } - if (i === -1) { - // didn't increment anything - this.prerelease.push(0); - } - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (compareIdentifiers(this.prerelease[0], identifier) === 0) { - if (isNaN(this.prerelease[1])) { - this.prerelease = [identifier, 0]; - } - } else { - this.prerelease = [identifier, 0]; - } - } + case "POST": + // Send query string as request payload + requestPayload = queryString; break; - default: - throw new Error(`invalid increment argument: ${release}`); } - this.format(); - this.raw = this.version; - return this; + } else if (formData) { + requestPayload = formData; } - } - var semver = SemVer; - - const { - MAX_LENGTH - } = constants; - const { - re, - t - } = re_1; + // Add a timestamp as part of the query string if cache busting is enabled + if (options.cacheBust) { + location = updateQueryString(location, "t", Date.now()); + } + request.open(requestMethod, location, true); + request.timeout = timeout; + request.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + request.setRequestHeader("X-PJAX", "true"); + request.setRequestHeader("X-PJAX-Selectors", JSON.stringify(options.selectors)); - const parse = (version, options) => { - options = parseOptions_1(options); - if (version instanceof semver) { - return version; + // Send the proper header information for POST forms + if (requestPayload && requestMethod === "POST" && !formData) { + request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } - if (typeof version !== 'string') { - return null; + request.send(requestPayload); + return request; + }; + + var handleResponse = function (responseText, request, href, options) { + options = clone(options || this.options); + options.request = request; + + // Fail if unable to load HTML via AJAX + if (responseText === false) { + trigger(document, "pjax:complete pjax:error", options); + return; } - if (version.length > MAX_LENGTH) { - return null; + + // push scroll position to history + var currentState = window.history.state || {}; + window.history.replaceState({ + url: currentState.url || window.location.href, + title: currentState.title || document.title, + uid: currentState.uid || uniqueid(), + scrollPos: [document.documentElement.scrollLeft || document.body.scrollLeft, document.documentElement.scrollTop || document.body.scrollTop] + }, document.title, window.location.href); + + // Check for redirects + var oldHref = href; + if (request.responseURL) { + if (href !== request.responseURL) { + href = request.responseURL; + } + } else if (request.getResponseHeader("X-PJAX-URL")) { + href = request.getResponseHeader("X-PJAX-URL"); + } else if (request.getResponseHeader("X-XHR-Redirected-To")) { + href = request.getResponseHeader("X-XHR-Redirected-To"); } - const r = options.loose ? re[t.LOOSE] : re[t.FULL]; - if (!r.test(version)) { - return null; + + // Add back the hash if it was removed + var a = document.createElement("a"); + a.href = oldHref; + var oldHash = a.hash; + a.href = href; + if (oldHash && !a.hash) { + a.hash = oldHash; + href = a.href; } + this.state.href = href; + this.state.options = options; try { - return new semver(version, options); - } catch (er) { - return null; + this.loadContent(responseText, options); + } catch (e) { + trigger(document, "pjax:error", options); + if (!this.options.debug) { + if (console && console.error) { + console.error("Pjax switch fail: ", e); + } + return this.latestChance(href); + } else { + throw e; + } } }; - var parse_1 = parse; - const valid = (version, options) => { - const v = parse_1(version, options); - return v ? v.version : null; + var isSupported = function () { + // Borrowed wholesale from https://github.com/defunkt/jquery-pjax + return window.history && window.history.pushState && window.history.replaceState && + // pushState isn’t reliable on iOS until 5. + !navigator.userAgent.match(/((iPod|iPhone|iPad).+\bOS\s+[1-4]\D|WebApps\/.+CFNetwork)/); }; - var valid_1 = valid; - const major = (a, loose) => new semver(a, loose).major; - var major_1 = major; + var pjax$1 = createCommonjsModule(function (module) { + var Pjax = function (options) { + this.state = { + numPendingSwitches: 0, + href: null, + options: null + }; + this.options = parseOptions(options); + this.log("Pjax options", this.options); + if (this.options.scrollRestoration && "scrollRestoration" in history) { + history.scrollRestoration = "manual"; + } + this.maxUid = this.lastUid = uniqueid(); + this.parseDOM(document); + on(window, "popstate", function (st) { + if (st.state) { + var opt = clone(this.options); + opt.url = st.state.url; + opt.title = st.state.title; + // Since state already exists, prevent it from being pushed again + opt.history = false; + opt.scrollPos = st.state.scrollPos; + if (st.state.uid < this.lastUid) { + opt.backward = true; + } else { + opt.forward = true; + } + this.lastUid = st.state.uid; - class ProxyBus { - bus; - constructor(bus) { - if (typeof bus.getVersion !== 'function' || !valid_1(bus.getVersion())) { - console.warn('Proxying an event bus with an unknown or invalid version'); - } else if (major_1(bus.getVersion()) !== major_1(this.getVersion())) { - console.warn('Proxying an event bus of version ' + bus.getVersion() + ' with ' + this.getVersion()); + // @todo implement history cache here, based on uid + this.loadUrl(st.state.url, opt); } - this.bus = bus; - } - getVersion() { - return "3.0.2"; - } - subscribe(name, handler) { - this.bus.subscribe(name, handler); - } - unsubscribe(name, handler) { - this.bus.unsubscribe(name, handler); - } - emit(name, event) { - this.bus.emit(name, event); - } - } - class SimpleBus { - handlers = new Map(); - getVersion() { - return "3.0.2"; - } - subscribe(name, handler) { - this.handlers.set(name, (this.handlers.get(name) || []).concat(handler)); - } - unsubscribe(name, handler) { - this.handlers.set(name, (this.handlers.get(name) || []).filter(h => h != handler)); - } - emit(name, event) { - (this.handlers.get(name) || []).forEach(h => { - try { - h(event); - } catch (e) { - console.error('could not invoke event listener', e); + }.bind(this)); + }; + Pjax.switches = switches; + Pjax.prototype = { + log: log, + getElements: function (el) { + return el.querySelectorAll(this.options.elements); + }, + parseDOM: function (el) { + var parseElement$1 = parseElement; + foreachEls(this.getElements(el), parseElement$1, this); + }, + refresh: function (el) { + this.parseDOM(el || document); + }, + reload: function () { + window.location.reload(); + }, + attachLink: attachLink, + attachForm: attachForm, + forEachSelectors: function (cb, context, DOMcontext) { + return foreachSelectors.bind(this)(this.options.selectors, cb, context, DOMcontext); + }, + switchSelectors: function (selectors, fromEl, toEl, options) { + return switchesSelectors.bind(this)(this.options.switches, this.options.switchesOptions, selectors, fromEl, toEl, options); + }, + latestChance: function (href) { + window.location = href; + }, + onSwitch: function () { + trigger(window, "resize scroll"); + this.state.numPendingSwitches--; + + // debounce calls, so we only run this once after all switches are finished. + if (this.state.numPendingSwitches === 0) { + this.afterAllSwitches(); + } + }, + loadContent: function (html, options) { + if (typeof html !== "string") { + trigger(document, "pjax:complete pjax:error", options); + return; + } + var tmpEl = document.implementation.createHTMLDocument("pjax"); + + // parse HTML attributes to copy them + // since we are forced to use documentElement.innerHTML (outerHTML can't be used for ) + var htmlRegex = /]+>/gi; + var htmlAttribsRegex = /\s?[a-z:]+(?:=['"][^'">]+['"])*/gi; + var matches = html.match(htmlRegex); + if (matches && matches.length) { + matches = matches[0].match(htmlAttribsRegex); + if (matches.length) { + matches.shift(); + matches.forEach(function (htmlAttrib) { + var attr = htmlAttrib.trim().split("="); + if (attr.length === 1) { + tmpEl.documentElement.setAttribute(attr[0], true); + } else { + tmpEl.documentElement.setAttribute(attr[0], attr[1].slice(1, -1)); + } + }); } + } + tmpEl.documentElement.innerHTML = html; + this.log("load content", tmpEl.documentElement.attributes, tmpEl.documentElement.innerHTML.length); + + // Clear out any focused controls before inserting new page contents. + if (document.activeElement && contains(document, this.options.selectors, document.activeElement)) { + try { + document.activeElement.blur(); + } catch (e) {} // eslint-disable-line no-empty + } + + this.switchSelectors(this.options.selectors, tmpEl, document, options); + }, + abortRequest: abortRequest, + doRequest: sendRequest, + handleResponse: handleResponse, + loadUrl: function (href, options) { + options = typeof options === "object" ? extend({}, this.options, options) : clone(this.options); + this.log("load href", href, options); + + // Abort any previous request + this.abortRequest(this.request); + trigger(document, "pjax:send", options); + + // Do the request + this.request = this.doRequest(href, options, this.handleResponse.bind(this)); + }, + afterAllSwitches: function () { + // FF bug: Won’t autofocus fields that are inserted via JS. + // This behavior is incorrect. So if theres no current focus, autofocus + // the last field. + // + // http://www.w3.org/html/wg/drafts/html/master/forms.html + var autofocusEl = Array.prototype.slice.call(document.querySelectorAll("[autofocus]")).pop(); + if (autofocusEl && document.activeElement !== autofocusEl) { + autofocusEl.focus(); + } + + // execute scripts when DOM have been completely updated + this.options.selectors.forEach(function (selector) { + foreachEls(document.querySelectorAll(selector), function (el) { + executeScripts(el); + }); }); + var state = this.state; + if (state.options.history) { + if (!window.history.state) { + this.lastUid = this.maxUid = uniqueid(); + window.history.replaceState({ + url: window.location.href, + title: document.title, + uid: this.maxUid, + scrollPos: [0, 0] + }, document.title); + } + + // Update browser history + this.lastUid = this.maxUid = uniqueid(); + window.history.pushState({ + url: state.href, + title: state.options.title, + uid: this.maxUid, + scrollPos: [0, 0] + }, state.options.title, state.href); + } + this.forEachSelectors(function (el) { + this.parseDOM(el); + }, this); + + // Fire Events + trigger(document, "pjax:complete pjax:success", state.options); + if (typeof state.options.analytics === "function") { + state.options.analytics(); + } + if (state.options.history) { + // First parse url and check for hash to override scroll + var a = document.createElement("a"); + a.href = this.state.href; + if (a.hash) { + var name = a.hash.slice(1); + name = decodeURIComponent(name); + var curtop = 0; + var target = document.getElementById(name) || document.getElementsByName(name)[0]; + if (target) { + // http://stackoverflow.com/questions/8111094/cross-browser-javascript-function-to-find-actual-position-of-an-element-in-page + if (target.offsetParent) { + do { + curtop += target.offsetTop; + target = target.offsetParent; + } while (target); + } + } + window.scrollTo(0, curtop); + } else if (state.options.scrollTo !== false) { + // Scroll page to top on new page load + if (state.options.scrollTo.length > 1) { + window.scrollTo(state.options.scrollTo[0], state.options.scrollTo[1]); + } else { + window.scrollTo(0, state.options.scrollTo); + } + } + } else if (state.options.scrollRestoration && state.options.scrollPos) { + window.scrollTo(state.options.scrollPos[0], state.options.scrollPos[1]); + } + this.state = { + numPendingSwitches: 0, + href: null, + options: null + }; } + }; + Pjax.isSupported = isSupported; + + // arguably could do `if( require("./lib/is-supported")()) {` but that might be a little to simple + if (Pjax.isSupported()) { + module.exports = Pjax; } - function getBus() { - if (typeof window.OC !== 'undefined' && window.OC._eventBus && typeof window._nc_event_bus === 'undefined') { - console.warn('found old event bus instance at OC._eventBus. Update your version!'); - window._nc_event_bus = window.OC._eventBus; - } - // Either use an existing event bus instance or create one - if (typeof window._nc_event_bus !== 'undefined') { - return new ProxyBus(window._nc_event_bus); - } else { - return window._nc_event_bus = new SimpleBus(); + // if there isn’t required browser functions, returning stupid api + else { + var stupidPjax = noop; + for (var key in Pjax.prototype) { + if (Pjax.prototype.hasOwnProperty(key) && typeof Pjax.prototype[key] === "function") { + stupidPjax[key] = noop; + } } + module.exports = stupidPjax; } - const bus = getBus(); - /** - * Register an event listener - * - * @param name name of the event - * @param handler callback invoked for every matching event emitted on the bus - */ - function subscribe(name, handler) { - bus.subscribe(name, handler); - } + }); - const tokenElement = document.getElementsByTagName('head')[0]; - let token$1 = tokenElement ? tokenElement.getAttribute('data-requesttoken') : null; - const observers = []; - function getRequestToken() { - return token$1; - } - // Listen to server event and keep token in sync - subscribe('csrf-token-update', e => { - token$1 = e.token; - observers.forEach(observer => { - try { - observer(e.token); - } catch (e) { - console.error('error updating CSRF token observer', e); + var PagePjax = /*#__PURE__*/_createClass$1(function PagePjax(reload) { + _classCallCheck$1(this, PagePjax); + /** + * Enable seamless page navigation with pjax. + */ + this.pjaxInstance = new pjax$1({ + elements: [".timemanager-pjax-link"], + selectors: [".app-timemanager #app-navigation ul", ".app-timemanager #app-content .container"], + cacheBust: false, + scrollTo: true + }); + document.addEventListener("pjax:send", function () { + document.body.classList.add("loading"); + document.body.classList.remove("loading-error"); + document.body.classList.remove("tm_ready"); + }); + document.addEventListener("pjax:success", function () { + setTimeout(function () { + document.body.classList.remove("loading"); + reload(); + }, 300); + }); + document.addEventListener("pjax:error", function (error) { + // Catch session timeout and redirect to login + if (error && error.request && error.request.status === 401) { + document.location.href = "".concat(dist_4("login"), "?redirect_url=").concat(dist_4("timemanager", "index")); } + document.body.classList.remove("loading"); + document.body.classList.add("loading-error"); }); }); - /// - const getAttribute = (el, attribute) => { - if (el) { - return el.getAttribute(attribute); - } - return null; - }; - const head = document.getElementsByTagName('head')[0]; - getAttribute(head, 'data-user'); - getAttribute(head, 'data-user-displayname'); - typeof OC === 'undefined' ? false : OC.isUserAdmin(); - var token = getRequestToken(); var components = []; var pjax = []; @@ -38933,17 +38961,6 @@ requestToken: token }) })); - - // components.push( - // new Settings({ - // target: Helpers.replaceNode(document.querySelector("#content.app-timemanager [data-svelte='Settings.svelte']")), - // props: { - // ...store, - // requestToken: token, - // }, - // }) - // ); - var dateTimeElements = document.querySelectorAll("[data-datetime]"); if (dateTimeElements && dateTimeElements.length > 0) { dateTimeElements.forEach(function (element) { diff --git a/js/main.js b/js/main.js index 915ac22..c0765fd 100644 --- a/js/main.js +++ b/js/main.js @@ -14,7 +14,6 @@ import Timerange from "./views/Timerange.svelte"; import PrintButton from "./views/PrintButton.svelte"; import Import from "./views/Import.svelte"; import UserFilterButton from "./views/UserFilterButton.svelte"; -// import Settings from "./views/Settings.svelte"; import { Helpers } from "./lib/helpers"; import { PagePjax } from "./lib/pjax"; import { translate } from "@nextcloud/l10n"; @@ -265,16 +264,6 @@ const init = () => { }) ); - // components.push( - // new Settings({ - // target: Helpers.replaceNode(document.querySelector("#content.app-timemanager [data-svelte='Settings.svelte']")), - // props: { - // ...store, - // requestToken: token, - // }, - // }) - // ); - const dateTimeElements = document.querySelectorAll("[data-datetime]"); if (dateTimeElements && dateTimeElements.length > 0) { dateTimeElements.forEach((element) => { diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..6df32b1 --- /dev/null +++ b/js/settings.js @@ -0,0 +1,38 @@ +async function tmSaveSettings(body) { + try { + const response = await fetch(OC.generateUrl("/apps/timemanager/api/settings"), { + method: "POST", + headers: { + requesttoken: OC.requestToken, + "content-type": "application/json" + }, + body: JSON.stringify(body) + }); + + const result = await response.json(); + } catch (error) { + console.error(`Cannot save setting: ${error.message}`); + } +} + +document.getElementById("tm-reporter").addEventListener("change", async (e) => { + let value = e.target.value; + const loading = document.getElementById("tm-reporter-loading"); + + loading.classList.add("icon-loading"); + await tmSaveSettings({ + reporter: value + }); + loading.classList.remove("icon-loading"); +}); + +document.getElementById("tm-handle-conflicts").addEventListener("change", async (e) => { + let value = e.target.checked; + const loading = document.getElementById("tm-handle-conflicts-loading"); + + loading.classList.add("icon-loading"); + await tmSaveSettings({ + handle_conflicts: value + }); + loading.classList.remove("icon-loading"); +}); diff --git a/js/views/Settings.svelte b/js/views/Settings.svelte deleted file mode 100644 index e0b7834..0000000 --- a/js/views/Settings.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - - - { - handleConflicts = e.target.checked; - save(); - }} /> - - {translate('timemanager', "My (mobile) apps can handle conflicts (leave unchecked if you're unsure)")} - - - diff --git a/js/views/ShareDialog.svelte b/js/views/ShareDialog.svelte index 6e7976a..0e12f33 100644 --- a/js/views/ShareDialog.svelte +++ b/js/views/ShareDialog.svelte @@ -59,12 +59,14 @@ if (response.ok) { const { users, exact, groups } = (await response.json()).ocs.data; - const existing_users = sharees.filter(s => s.recipient_type == 'user').map((share) => share.recipient_id); - const existing_groups = sharees.filter(s => s.recipient_type == 'group').map((share) => share.recipient_id); + const existing_users = sharees.filter(s => s.recipient_type === 'user').map((share) => share.recipient_id); + const existing_groups = sharees.filter(s => s.recipient_type === 'group').map((share) => share.recipient_id); return [...users, ...exact.users, ...groups, ...exact.groups].filter( (user) => !existing_users.includes(user.value.shareWith) && user.value.shareWith !== userId ).filter( (group) => !existing_groups.includes(group.value.shareWith) + ).sort( + (a, b) => a.label.localeCompare(b.label) ); } }; diff --git a/js/views/Statistics.svelte b/js/views/Statistics.svelte index 90a412a..a17dd6d 100644 --- a/js/views/Statistics.svelte +++ b/js/views/Statistics.svelte @@ -3,6 +3,7 @@ export let requestToken; export let controls = true; export let includeShared = false; + export let includeReporter = false; import { onMount } from "svelte"; import { @@ -136,7 +137,7 @@ const loadStats = async () => { const start = format(Helpers.toUTC(startOfDay(startCursor)), apiDateFormat); const end = format(Helpers.toUTC(endOfDay(endCursor)), apiDateFormat); - let statUrl = `${statsApiUrl}?start=${start}&end=${end}&group_by=${scale}&shared=${includeShared ? 1 : 0}`; + let statUrl = `${statsApiUrl}?start=${start}&end=${end}&group_by=${scale}&shared=${includeShared ? 1 : 0}&reporter=${includeReporter ? 1 : 0}`; // Parse current URL for filters const urlParts = document.location.href.split("?"); diff --git a/js/views/UserFilterSelect.svelte b/js/views/UserFilterSelect.svelte index 7306c9a..283da21 100644 --- a/js/views/UserFilterSelect.svelte +++ b/js/views/UserFilterSelect.svelte @@ -3,6 +3,7 @@ export let isVisible = true; import Select from "svelte-select"; + import { getCurrentUser } from "@nextcloud/auth" import { translate } from "@nextcloud/l10n"; import { generateOcsUrl } from "@nextcloud/router"; import { onMount } from "svelte"; @@ -51,11 +52,17 @@ } ); + const current = getCurrentUser(); + let currentUser = []; + if (current.uid.toLowerCase().includes(query.toLowerCase()) || current.displayName.toLowerCase().includes(query.toLowerCase())) { + currentUser = [{ value: { shareWith: current.uid }, label: current.displayName }]; + } + loading = false; if (response.ok) { const { users, exact } = (await response.json()).ocs.data; - return [...users, ...exact.users]; + return [...users, ...exact.users, ...currentUser].sort((a, b) => a.label.localeCompare(b.label)); } }; diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php new file mode 100644 index 0000000..073b3d8 --- /dev/null +++ b/lib/AppInfo/Application.php @@ -0,0 +1,26 @@ + $this->urlGenerator->linkToRoute("timemanager.page.times"), "statsApiUrl" => $this->urlGenerator->linkToRoute("timemanager.t_api.getHoursInPeriodStats"), - "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - "settings" => [ - "handle_conflicts" => - $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === - "handle_conflicts", - "fullDateFormat" => $this->fullDateFormat, - ], "requestToken" => $this->requestToken, "isServer" => true, "latestSearchEntries" => array_map(function ($latestSearchEntry) { @@ -215,14 +208,23 @@ function reports( $end = $end_of_month->format("Y-m-d"); } - $all_clients = $this->clientMapper->findActiveForCurrentUser("name", true); - $all_projects = $this->projectMapper->findActiveForCurrentUser("name", true); - $all_tasks = $this->taskMapper->findActiveForCurrentUser("name", true); + // check if user is reporter or admin + $reporterGroup = $this->config->getAppValue('timemanager', 'reporter_group'); + $isReporterOrAdmin = $this->groupManager->isAdmin($this->userId) || $this->groupManager->isInGroup($this->userId, $reporterGroup); + if ($isReporterOrAdmin) { + $all_clients = $this->clientMapper->findActiveForReporter("name"); + $all_projects = $this->projectMapper->findActiveForReporter("name"); + $all_tasks = $this->taskMapper->findActiveForReporter("name"); + } else { + $all_clients = $this->clientMapper->findActiveForCurrentUser("name", true); + $all_projects = $this->projectMapper->findActiveForCurrentUser("name", true); + $all_tasks = $this->taskMapper->findActiveForCurrentUser("name", true); + } // Get possible task ids to filters for - $filter_tasks = $this->storageHelper->getTaskListFromFilters($clients, $projects, $tasks, true); + $filter_tasks = $this->storageHelper->getTaskListFromFilters($clients, $projects, $tasks, true, $isReporterOrAdmin); - $times = $this->timeMapper->findForReport($start, $end, $status, $filter_tasks, true); + $times = $this->timeMapper->findForReport($start, $end, $status, $filter_tasks, true, $isReporterOrAdmin); $includedAuthors = $userFilter && strlen($userFilter) > 0 ? explode(",", $userFilter) : []; @@ -234,15 +236,15 @@ function reports( $times = $this->storageHelper->resolveAuthorDisplayNamesForTimes($times, $this->userManager); foreach ($times as $time) { // Find details for parents of time entry - $task = $this->taskMapper->getActiveObjectById($time->getTaskUuid(), true); + $task = $this->taskMapper->getActiveObjectById($time->getTaskUuid(), true, $isReporterOrAdmin); if (!$task) { continue; } - $project = $this->projectMapper->getActiveObjectById($task->getProjectUuid(), true); + $project = $this->projectMapper->getActiveObjectById($task->getProjectUuid(), true, $isReporterOrAdmin); if (!$project) { continue; } - $client = $this->clientMapper->getActiveObjectById($project->getClientUuid(), true); + $client = $this->clientMapper->getActiveObjectById($project->getClientUuid(), true, $isReporterOrAdmin); if (!$client) { continue; } @@ -321,14 +323,8 @@ function reports( "start" => $start, "end" => $end, "controls" => false, - "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - "settings" => [ - "handle_conflicts" => - $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === - "handle_conflicts", - "fullDateFormat" => $this->fullDateFormat, - ], "includeShared" => true, + "includeReporter" => true, ]; return new TemplateResponse("timemanager", "reports", [ @@ -383,12 +379,6 @@ function clients() { $form_props = [ "action" => $this->urlGenerator->linkToRoute("timemanager.page.clients"), - "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - "settings" => [ - "handle_conflicts" => - $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === "handle_conflicts", - "fullDateFormat" => $this->fullDateFormat, - ], "clientEditorButtonCaption" => $this->l->t("Add client"), "clientEditorCaption" => $this->l->t("New client"), ]; @@ -557,8 +547,10 @@ function projects($client = null) { } } - $client_uuid = isset($client_data) && count($client_data) > 0 ? $client_data[0]->getUuid() : ""; - $client_name = isset($client_data) && count($client_data) > 0 ? $client_data[0]->getName() : ""; + + $client_uuid = $client_data[0]?->getUuid() ?? ""; + $client_name = $client_data[0]?->getName() ?? ""; + $canEdit = ($client_data[0]?->getUserId() ?? "") === $this->userId; $sharees = array_map(function ($share) { $shareArray = $share->toArray($this->userManager, $this->groupManager); @@ -575,12 +567,6 @@ function projects($client = null) { $form_props = [ "action" => $this->urlGenerator->linkToRoute("timemanager.page.projects") . "?client=" . $client_uuid, "editAction" => $this->urlGenerator->linkToRoute("timemanager.page.clients"), - "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - "settings" => [ - "handle_conflicts" => - $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === "handle_conflicts", - "fullDateFormat" => $this->fullDateFormat, - ], "requestToken" => $this->requestToken, "clientName" => $client_name, "clientEditorButtonCaption" => $this->l->t("Edit client"), @@ -603,7 +589,7 @@ function projects($client = null) { "deleteShareAction" => $this->urlGenerator->linkToRoute("timemanager.page.clients") . "/share/delete", "sharees" => $sharees, "sharedBy" => $sharedBy, - "canEdit" => $sharedBy === null, + "canEdit" => $canEdit, "userId" => $this->userId, "isServer" => true, ]; @@ -621,7 +607,7 @@ function projects($client = null) { "ShareStatus.svelte" => PHP_Svelte::render_template("ShareStatus.svelte", $form_props), ], "page" => "projects", - "canEdit" => $sharedBy === null, + "canEdit" => $canEdit, ]); } @@ -766,18 +752,13 @@ function tasks($project) { } } - $project_uuid = isset($project_data) && count($project_data) > 0 ? $project_data[0]->getUuid() : ""; - $project_name = isset($project_data) && count($project_data) > 0 ? $project_data[0]->getName() : ""; + $project_uuid = $project_data[0]?->getUuid() ?? ""; + $project_name = $project_data[0]?->getName() ?? ""; + $canEdit = ($project_data[0]?->getUserId() ?? "") === $this->userId; $form_props = [ "action" => $this->urlGenerator->linkToRoute("timemanager.page.tasks") . "?project=" . $project_uuid, "editAction" => $this->urlGenerator->linkToRoute("timemanager.page.projects"), - "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - "settings" => [ - "handle_conflicts" => - $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === "handle_conflicts", - "fullDateFormat" => $this->fullDateFormat, - ], "requestToken" => $this->requestToken, "clientName" => isset($client_data) && count($client_data) > 0 ? $client_data[0]->getName() : "", "projectName" => $project_name, @@ -797,7 +778,7 @@ function tasks($project) { ], "sharedBy" => $sharedBy, "sharees" => $sharees, - "canEdit" => $sharedBy === null, + "canEdit" => $canEdit, "isServer" => true, ]; @@ -815,7 +796,7 @@ function tasks($project) { "ShareStatus.svelte" => PHP_Svelte::render_template("ShareStatus.svelte", $form_props), ], "page" => "tasks", - "canEdit" => $sharedBy === null, + "canEdit" => $canEdit, ]); } @@ -943,18 +924,13 @@ function times($task, string $userFilter = "") { }); $hasSharedTimeEntries = count($sharedTimeEntries) > 0; - $task_uuid = isset($task_data) && count($task_data) > 0 ? $task_data[0]->getUuid() : ""; - $task_name = isset($task_data) && count($task_data) > 0 ? $task_data[0]->getName() : ""; + $task_uuid = $task_data[0]?->getUuid() ?? ""; + $task_name = $task_data[0]?->getName() ?? ""; + $canEdit = ($task_data[0]?->getUserId() ?? "") === $this->userId; $form_props = [ "action" => $this->urlGenerator->linkToRoute("timemanager.page.times") . "?task=" . $task_uuid, "editAction" => $this->urlGenerator->linkToRoute("timemanager.page.tasks"), - "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - "settings" => [ - "handle_conflicts" => - $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === "handle_conflicts", - "fullDateFormat" => $this->fullDateFormat, - ], "requestToken" => $this->requestToken, "clientName" => isset($client_data) && count($client_data) > 0 ? $client_data[0]->getName() : "", "projectName" => isset($project_data) && count($project_data) > 0 ? $project_data[0]->getName() : "", @@ -975,7 +951,7 @@ function times($task, string $userFilter = "") { "editTimeEntryAction" => $this->urlGenerator->linkToRoute("timemanager.page.times") . "?task=" . $task_uuid, "sharedBy" => $sharedBy, "sharees" => $sharees, - "canEdit" => $sharedBy === null, + "canEdit" => $canEdit, "isServer" => true, ]; @@ -995,7 +971,7 @@ function times($task, string $userFilter = "") { "ShareStatus.svelte" => PHP_Svelte::render_template("ShareStatus.svelte", $form_props), ], "page" => "times", - "canEdit" => $sharedBy === null, + "canEdit" => $canEdit, "latestEntries" => $latestEntries, "hasSharedTimeEntries" => $hasSharedTimeEntries, ]); @@ -1133,18 +1109,6 @@ function unpayTime($uuid) { return new NotFoundException("Time entry could not be found"); } - /** - * @NoAdminRequired - */ - function updateSettings($handle_conflicts) { - $this->config->setAppValue( - "timemanager", - "sync_mode", - (bool) $handle_conflicts ? "handle_conflicts" : "force_skip_conflict_handling" - ); - return new RedirectResponse($this->urlGenerator->linkToRoute("timemanager.page.index")); - } - /** * @NoAdminRequired * @NoCSRFRequired @@ -1164,12 +1128,6 @@ function tools() { "syncApiUrl" => $this->urlGenerator->linkToRoute("timemanager.t_api.updateObjectsFromWeb"), "requestToken" => $this->requestToken, "isServer" => true, - // "settingsAction" => $this->urlGenerator->linkToRoute("timemanager.page.updateSettings"), - // "settings" => [ - // "handle_conflicts" => - // $this->config->getAppValue("timemanager", "sync_mode", "force_skip_conflict_handling") === - // "handle_conflicts", - // ], ]; return new TemplateResponse("timemanager", "tools", [ diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php new file mode 100644 index 0000000..3f9a54f --- /dev/null +++ b/lib/Controller/SettingsController.php @@ -0,0 +1,40 @@ +config->setAppValue('timemanager', 'reporter_group', $reporter); + } + + if ($handle_conflicts !== null) { + $this->config->setAppValue( + "timemanager", + "sync_mode", + $handle_conflicts ? "handle_conflicts" : "force_skip_conflict_handling" + ); + } + + return new JSONResponse([]); + } +} diff --git a/lib/Controller/TApiController.php b/lib/Controller/TApiController.php index e2f85b6..6d3f69b 100644 --- a/lib/Controller/TApiController.php +++ b/lib/Controller/TApiController.php @@ -15,6 +15,7 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http; +use OCP\IGroupManager; use OCP\IRequest; use OCP\IConfig; use Psr\Log\LoggerInterface; @@ -40,6 +41,8 @@ class TApiController extends ApiController { private $config; /** @var LoggerInterface logger */ private $logger; + /** @var IGroupManager */ + private $groupManager; /** * constructor of the controller @@ -64,6 +67,7 @@ function __construct( ShareMapper $shareMapper, IConfig $config, LoggerInterface $logger, + IGroupManager $groupManager, ) { parent::__construct($appName, $request); $this->clientMapper = $clientMapper; @@ -75,6 +79,7 @@ function __construct( $this->config = $config; $this->userId = $userId; $this->logger = $logger; + $this->groupManager = $groupManager; $this->storageHelper = new StorageHelper( $this->clientMapper, $this->projectMapper, @@ -238,19 +243,24 @@ function getHoursInPeriodStats( string $projects = null, string $tasks = null, string $status = null, - $shared = false, - string $userFilter = "" + string $userFilter = "", + bool $shared = false, + bool $reporter = false, ) { + // check if user is reporter or admin + $reporterGroup = $this->config->getAppValue('timemanager', 'reporter_group'); + $isReporterOrAdmin = $reporter && ($this->groupManager->isAdmin($this->userId) || $this->groupManager->isInGroup($this->userId, $reporterGroup)); + // Get possible task ids to filters for - $filter_tasks = $this->storageHelper->getTaskListFromFilters($clients, $projects, $tasks, $shared); + $filter_tasks = $this->storageHelper->getTaskListFromFilters($clients, $projects, $tasks, $shared, $isReporterOrAdmin); $includedAuthors = $userFilter && strlen($userFilter) > 0 ? explode(",", $userFilter) : []; // Get all time entries for time period - $times = $this->timeMapper->findForReport($start, $end, $status, $filter_tasks, $shared); - $times = array_filter($times, function ($time) use ($includedAuthors) { + $times = $this->timeMapper->findForReport($start, $end, $status, $filter_tasks, $shared, $isReporterOrAdmin); + $times = array_filter($times, function ($time) use ($includedAuthors, $isReporterOrAdmin) { // Find details for parents of time entry. If it doesn't exist, we should filter out time entry - $task = $this->taskMapper->getActiveObjectById($time->getTaskUuid(), true); + $task = $this->taskMapper->getActiveObjectById($time->getTaskUuid(), true, $isReporterOrAdmin); if (!$task) { return false; diff --git a/lib/Db/ClientMapper.php b/lib/Db/ClientMapper.php index 90a7255..b1c6c3d 100644 --- a/lib/Db/ClientMapper.php +++ b/lib/Db/ClientMapper.php @@ -2,7 +2,10 @@ namespace OCA\TimeManager\Db; +use OCP\IConfig; use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; /** * Class ItemMapper @@ -11,24 +14,25 @@ * @method Client insert(Client $entity) */ class ClientMapper extends ObjectMapper { - protected $projectMapper; + protected ProjectMapper $projectMapper; - public function __construct(IDBConnection $db, CommitMapper $commitMapper, ProjectMapper $projectMapper) { - parent::__construct($db, $commitMapper, "timemanager_client"); + public function __construct(IDBConnection $db, IConfig $config, IUserManager $userManager, IGroupManager $groupManager, CommitMapper $commitMapper, ProjectMapper $projectMapper) { + parent::__construct($db, $config, $userManager, $groupManager, $commitMapper, "timemanager_client"); $this->projectMapper = $projectMapper; } - public function deleteChildrenForEntityById($uuid, $commit) { + public function deleteChildrenForEntityById(string $uuid, string $commit): void { $this->projectMapper->deleteWithChildrenByClientId($uuid, $commit); } - /** - * Gets the number of projects for a given object. - * - * @param string $userId the user id to filter - * @return Client[] list if matching items - */ - public function countProjects($uuid) { + /** + * Gets the number of projects for a given object. + * + * @param string $uuid + * @return int number of matching items + */ + public function countProjects(string $uuid): int + { $projects = $this->projectMapper->getActiveObjectsByAttributeValue("client_uuid", $uuid, "created", true); return count($projects); } diff --git a/lib/Db/CommitMapper.php b/lib/Db/CommitMapper.php index abd78e3..167219c 100644 --- a/lib/Db/CommitMapper.php +++ b/lib/Db/CommitMapper.php @@ -11,21 +11,20 @@ * @package OCA\TimeManager\Db * @method Commit insert(Commit $entity) */ -class CommitMapper extends QBMapper { - protected $userId; - protected IDBConnection $connection; +class CommitMapper extends QBMapper implements ICurrentUser { + protected string $userId; - public function __construct(IDBConnection $connection) { - $this->connection = $connection; - parent::__construct($connection, "timemanager_commit"); + public function __construct(IDBConnection $db) { + parent::__construct($db, "timemanager_commit"); } - function setCurrentUser($userId) { + function setCurrentUser(string $userId): void + { $this->userId = $userId; } function getLatestCommit() { - $sql = $this->connection->getQueryBuilder(); + $sql = $this->db->getQueryBuilder(); $sql ->select("*") ->from($this->tableName) @@ -44,7 +43,7 @@ function getLatestCommit() { function getCommitsAfter($commit) { // Find the given commit first, see if we have it. - $sql = $this->connection->getQueryBuilder(); + $sql = $this->db->getQueryBuilder(); $sql ->select("commit") ->from($this->tableName) @@ -54,7 +53,7 @@ function getCommitsAfter($commit) { $sql->setParameters([$this->userId, $commit]); $givenCommit = $this->findEntities($sql); - $sql = $this->connection->getQueryBuilder(); + $sql = $this->db->getQueryBuilder(); // The given commit is found, all fine. if (count($givenCommit) > 0) { diff --git a/lib/Db/ICurrentUser.php b/lib/Db/ICurrentUser.php new file mode 100644 index 0000000..0c6811e --- /dev/null +++ b/lib/Db/ICurrentUser.php @@ -0,0 +1,8 @@ +db = $db; - $this->commitMapper = $commitMapper; +abstract class ObjectMapper extends QBMapper implements ICurrentUser { + protected string $userId; + + abstract public function deleteChildrenForEntityById(string $uuid, string $commit): void; + + public function __construct( + IDBConnection $db, + protected IConfig $config, + protected IUserManager $userManager, + protected IGroupManager $groupManager, + protected CommitMapper $commitMapper, + $dbname) { parent::__construct($db, $dbname); } - function setCurrentUser($userId) { + function setCurrentUser(string $userId): void + { $this->userId = $userId; $this->commitMapper->setCurrentUser($this->userId); } - function getActiveObjects($orderby = "created", $sort = "ASC"): array { + /** + * Get all groups of user + * + * @return string[] + */ + function getUserGroups(): array + { + $user = $this->userManager->get($this->userId); + return array_unique($this->groupManager->getUserGroupIds($user)); + } + + function userIsAdminOrReporter(): bool + { + return $this->groupManager->isAdmin($this->userId) + || $this->groupManager->isInGroup($this->userId, $this->config->getAppValue(Application::APP_ID, 'reporter_group')); + } + + /** + * Fetch all items that are associated to the current user + * with a given attribute-value-combination and not deleted + * + * @param string $attr the attribute name + * @param string $value the attribute value + * @return Object[] list if matching items + * @throws Exception + */ + function getActiveObjectsByAttributeValue( + string $attr, + string $value, + string $orderBy = "created", + bool $shared = false, + bool $isReporterOrAdmin = false, + ): array { $sql = $this->db->getQueryBuilder(); - $sql - ->select("*") - ->from($this->tableName) - ->where("`user_id` = ?") - ->andWhere("`status` != ?") - ->orderBy(\strtolower($orderby), $sort); - - $sql->setParameters([$this->userId, "deleted"]); - - return $this->findEntities($sql); - } - - /** - * Fetch all items that are associated to the current user - * with a given attribute-value-combination and not deleted - * - * @param string $attr the attribute name - * @param string $value the attribute value - * @return Object[] list if matching items - */ - function getActiveObjectsByAttributeValue(string $attr, string $value, $orderby = "created", $shared = false): array { - $sql = $this->db->getQueryBuilder(); - if ($shared && strpos($this->tableName, "_client") > -1) { - $sql - ->selectDistinct("client.*") - ->from($this->tableName, "client") - ->leftJoin("client", "*PREFIX*timemanager_share", "share", "client.`uuid` = share.`object_uuid`") - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX( + if ($isReporterOrAdmin) { + $shared = false; + } + + $sql->selectDistinct("base.*") + ->from($this->tableName, "base") + ->where("base.`status` != :status") + ->andWhere("base.`$attr` = :attr") + ->orderBy(\strtolower($orderBy), "ASC") + ->setParameters([ + "userid" => $this->userId, + "status" => "deleted", + "attr" => $value, + "reporter_or_admin" => $isReporterOrAdmin, + ]); + + if ($shared) { + $expr = [ "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", - "group_user.`uid` = :userid AND share.`recipient_type` = 'group'", - "client.`user_id` = :userid", - ); - - $sql - ->where($expr) - ->andWhere("client.`status` != :status") - ->andWhere("client.`$attr` = :attr") - ->orderBy(\strtolower($orderby), "ASC"); - - $sql->setParameters(["userid" => $this->userId, "status" => "deleted", "attr" => $value]); - - } elseif ($shared && strpos($this->tableName, "_project") > -1) { - $sql - ->selectDistinct("project.*") - ->from($this->tableName, "project") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.`client_uuid` = share.`object_uuid` AND share.`author_user_id` != :userid" - ) - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX( - "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", - "group_user.`uid` = :userid AND share.`recipient_type` = 'group'", - "project.`user_id` = :userid" - ); - - $sql - ->where($expr) - ->andWhere("project.`status` != :status") - ->andWhere("project.`$attr` = :attr") - ->orderBy(\strtolower($orderby), "ASC"); - - $sql->setParameters(["userid" => $this->userId, "status" => "deleted", "attr" => $value]); - - } elseif ($shared && strpos($this->tableName, "_task") > -1) { - $sql - ->selectDistinct("task.*") - ->from($this->tableName, "task") - ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.`project_uuid` = project.`uuid`") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.`client_uuid` = share.`object_uuid` AND share.`author_user_id` != :userid" - ) - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX( - "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", - "group_user.`uid` = :userid AND share.`recipient_type` = 'group'", - "task.`user_id` = :userid" - ); - - $sql - ->where($expr) - ->andWhere("task.`status` != :status") - ->andWhere("task.`$attr` = :attr") - ->orderBy(\strtolower($orderby), "ASC"); - - $sql->setParameters(["userid" => $this->userId, "status" => "deleted", "attr" => $value]); - - } elseif ($shared && strpos($this->tableName, "_time") > -1) { - $sql - ->selectDistinct("time.*") - ->from($this->tableName, "time") - ->innerJoin("time", "*PREFIX*timemanager_task", "task", "time.`task_uuid` = task.`uuid`") - ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.`project_uuid` = project.`uuid`") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.`client_uuid` = share.`object_uuid` AND share.`author_user_id` = :userid" - ); - - $expr = $sql->expr()->orX("share.`author_user_id` = :userid", "time.`user_id` = :userid"); - - $sql - ->where($expr) - ->andWhere("time.`status` != :status") - ->andWhere("time.`$attr` = :attr") - ->orderBy(\strtolower($orderby), "ASC"); - - $sql->setParameters(["userid" => $this->userId, "status" => "deleted", "attr" => $value]); - - } else { - $sql = $this->db->getQueryBuilder(); - $sql - ->select("*") - ->from($this->tableName) - ->where("`user_id` = ?") - ->andWhere("`status` != ?") - ->andWhere("`$attr` = ?") - ->orderBy(\strtolower($orderby), "ASC"); - - $sql->setParameters([$this->userId, "deleted", $value]); - - } - - return $this->findEntities($sql); - } - - /** - * Fetch all items that are associated to the current user - * within a given timerange, not deleted and with applied filters - * - * @param string $date_start the range start - * @param string $date_end the range end - * @param ?string $status the status - * @return Object[] list if matching items - */ - function getActiveObjectsByDateRangeAndFilters( - string $date_start, - string $date_end, - string $status = null, - array $filter_tasks = [], - string $orderby = "start", - $shared = false - ): array { - $params = [ - "userid" => $this->userId, - "deleted" => "deleted", - "date_start" =>$date_start, - "date_end" => $date_end, - ]; - $sql = $this->db->getQueryBuilder(); - // Range can be one day as well - if ($date_start === $date_end) { - array_pop($params); - if ($shared) { - $sql - ->selectDistinct("current.*") - ->from($this->tableName, "current") - ->innerJoin("current", "*PREFIX*timemanager_task", "task", "current.`task_uuid` = task.`uuid`") - ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.`project_uuid` = project.`uuid`") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.`client_uuid` = share.`object_uuid` AND share.`author_user_id` = :userid" - ) - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX("share.`author_user_id` = :userid", "current.`user_id` = :userid"); - - $sql - ->where($expr) - ->andWhere("current.`status` != :deleted") - ->andWhere("date(current.`start`) = :date_start"); - } else { - $sql - ->select("*") - ->from($this->tableName) - ->where("`user_id` = :userid") - ->andWhere("`status` != :deleted") - ->andWhere("date(start) = :date_start"); - } - } else { - if ($shared) { - $sql - ->selectDistinct("current.*") - ->from($this->tableName, "current") - ->innerJoin("current", "*PREFIX*timemanager_task", "task", "current.`task_uuid` = task.`uuid`") - ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.`project_uuid` = project.`uuid`") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.`client_uuid` = share.`object_uuid` AND share.`author_user_id` = :userid" - ) - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX("share.`author_user_id` = :userid", "current.`user_id` = :userid"); - - $sql - ->where($expr) - ->andWhere("current.`status` != :deleted") - ->andWhere("date(current.`start`) >= :date_start") - ->andWhere("date(current.`start`) <= :date_end"); - } else { - $sql - ->select("*") - ->from($this->tableName) - ->where("`user_id` = :userid") - ->andWhere("`status` != :deleted") - ->andWhere("date(start) >= :date_start") - ->andWhere("date(start) <= :date_end"); - } - } - if (isset($status) && $status) { - if ($status === "paid") { - $sql->andWhere("LOWER(`payment_status`) = :status"); - $params["status"] = strtolower($status); - } else { - $expr = $sql->expr()->orX("`payment_status` IS NULL", "LOWER(`payment_status`) <> :status"); - $sql->andWhere($expr); - $params["status"] = "paid"; - } - } - if (count($filter_tasks) > 0) { - $sql->andWhere("`task_uuid` IN ('" . implode("','", $filter_tasks) . "')"); + "share.`recipient_id` IN ('".implode("','", $this->getUserGroups())."') AND share.`recipient_type` = 'group'", + "base.`user_id` = :userid", + ":admin_or_reporter", + ]; + + $sql->setParameter("admin_or_reporter", $this->userIsAdminOrReporter()); + + if (strpos($this->tableName, "_client") > -1) { + $sql->leftJoin("base", "*PREFIX*timemanager_share", "share", "base.`uuid` = share.`object_uuid`"); + } elseif (strpos($this->tableName, "_project") > -1) { + $sql->leftJoin("base", "*PREFIX*timemanager_share", "share", "base.`client_uuid` = share.`object_uuid`"); + } elseif (strpos($this->tableName, "_task") > -1) { + $sql->innerJoin("base", "*PREFIX*timemanager_project", "project", "base.`project_uuid` = project.`uuid`") + ->leftJoin("project", "*PREFIX*timemanager_share", "share", "project.`client_uuid` = share.`object_uuid`"); + } elseif (strpos($this->tableName, "_time") > -1) { + $sql->innerJoin("base", "*PREFIX*timemanager_task", "task", "base.`task_uuid` = task.`uuid`") + ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.`project_uuid` = project.`uuid`") + ->leftJoin("project", "*PREFIX*timemanager_share", "share", "project.`client_uuid` = share.`object_uuid`"); + + $expr = [ + "share.`author_user_id` = :userid", + "base.`user_id` = :userid", + ":admin_or_reporter", + ]; + } + + $sql->andWhere($sql->expr()->orX(...$expr)); + } else { + if (!$isReporterOrAdmin) { + $sql->where("base.`user_id` = :userid"); + } } - $sql->orderBy(\strtolower($orderby), "ASC"); - $sql->setParameters($params); - return $this->findEntities($sql); } @@ -289,8 +143,8 @@ function getObjectById(string $uuid): ?\OCP\AppFramework\Db\Entity { } } - function getActiveObjectById(string $uuid, $shared = false): ?\OCP\AppFramework\Db\Entity { - $objects = $this->getActiveObjectsByAttributeValue("uuid", $uuid, "created", $shared); + function getActiveObjectById(string $uuid, bool $shared = false, bool $isReporterOrAdmin = false): ?\OCP\AppFramework\Db\Entity { + $objects = $this->getActiveObjectsByAttributeValue("uuid", $uuid, "created", $shared, $isReporterOrAdmin); if (count($objects) > 0) { return $objects[0]; } else { @@ -357,97 +211,78 @@ function getDeletedObjectsAfterCommit($sql) { * * @return Object[] list if matching items */ - function findActiveForCurrentUser($orderby = "created", $shared = false, $sort = "ASC") { + function findActiveForCurrentUser($orderBy = "created", $shared = false, $sort = "ASC"): array + { $sql = $this->db->getQueryBuilder(); - if ($shared && strpos($this->tableName, "_client") > -1) { - $sql - ->selectDistinct("client.*") - ->from($this->tableName, "client") - ->leftJoin("client", "*PREFIX*timemanager_share", "share", "client.uuid = share.object_uuid") - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); + $sql->selectDistinct("base.*") + ->from($this->tableName, "base"); - $expr = $sql->expr()->orX( - "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", - "group_user.`uid` = :userid AND share.`recipient_type` = 'group'", - "client.user_id = :userid", - ); - $sql->where($expr)->andWhere("client.status != :status"); - - $sql->orderBy(\strtolower($orderby), $sort); - $sql->setParameters(["userid" => $this->userId, "status" => "deleted"]); - - } elseif ($shared && strpos($this->tableName, "_project") > -1) { - $sql - ->selectDistinct("project.*") - ->from($this->tableName, "project") - ->leftJoin("project", "*PREFIX*timemanager_share", "share", "project.client_uuid = share.object_uuid") - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX( - "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", - "group_user.`uid` = :userid AND share.`recipient_type` = 'group'", - "project.user_id = :userid", - ); - $sql->where($expr)->andWhere("project.status != :status"); + $sql->where("base.status != :status") + ->orderBy(\strtolower($orderBy), $sort);; - $sql->orderBy(\strtolower($orderby), $sort); - $sql->setParameters(["userid" => $this->userId, "status" => "deleted"]); + $sql->setParameters([ + "userid" => $this->userId, + "status" => "deleted", + ]); - } elseif ($shared && strpos($this->tableName, "_task") > -1) { - $sql - ->selectDistinct("task.*") - ->from($this->tableName, "task") - ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.project_uuid = project.uuid") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.client_uuid = share.object_uuid AND share.author_user_id != :userid" - ) - ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); - - $expr = $sql->expr()->orX( + if ($shared) { + $expr = [ "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", - "group_user.`uid` = :userid AND share.`recipient_type` = 'group'", - "task.user_id = :userid", - ); - $sql->where($expr)->andWhere("task.status != :status"); - - $sql->orderBy(\strtolower($orderby), $sort); - $sql->setParameters(["userid" => $this->userId, "status" => "deleted"]); - - } elseif ($shared && strpos($this->tableName, "_time") > -1) { - $sql - ->selectDistinct("time.*") - ->from($this->tableName, "time") - ->innerJoin("time", "*PREFIX*timemanager_task", "task", "time.task_uuid = task.uuid") - ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.project_uuid = project.uuid") - ->leftJoin( - "project", - "*PREFIX*timemanager_share", - "share", - "project.client_uuid = share.object_uuid AND share.author_user_id = :userid" - ); - - $expr = $sql->expr()->orX("share.author_user_id = :userid", "time.user_id = :userid"); - $sql->where($expr)->andWhere("time.status != :status"); - - $sql->orderBy(\strtolower($orderby), $sort); - $sql->setParameters(["userid" => $this->userId, "status" => "deleted"]); - + "share.`recipient_id` IN ('".implode("','", $this->getUserGroups())."') AND share.`recipient_type` = 'group'", + "base.user_id = :userid", + ":admin_or_reporter" + ]; + + $sql->setParameter("admin_or_reporter", $this->userIsAdminOrReporter()); + + if (strpos($this->tableName, "_client") > -1) { + $sql->leftJoin("base", "*PREFIX*timemanager_share", "share", "base.uuid = share.object_uuid"); + } elseif (strpos($this->tableName, "_project") > -1) { + $sql->leftJoin("base", "*PREFIX*timemanager_share", "share", "base.client_uuid = share.object_uuid"); + } elseif (strpos($this->tableName, "_task") > -1) { + $sql->innerJoin("base", "*PREFIX*timemanager_project", "project", "base.project_uuid = project.uuid") + ->leftJoin("project", "*PREFIX*timemanager_share","share","project.client_uuid = share.object_uuid"); + } elseif (strpos($this->tableName, "_time") > -1) { + $sql->innerJoin("base", "*PREFIX*timemanager_task", "task", "base.task_uuid = task.uuid") + ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.project_uuid = project.uuid") + ->leftJoin( + "project", + "*PREFIX*timemanager_share", + "share", + "project.client_uuid = share.object_uuid" + ); + + $expr = [ + "share.author_user_id = :userid", + "base.user_id = :userid", + ":admin_or_reporter", + ]; + } + + $sql->andWhere($sql->expr()->orX(...$expr)); } else { - $sql = $this->db->getQueryBuilder(); - $sql - ->select("*") - ->from($this->tableName) - ->where("user_id = ?") - ->andWhere("status != ?") - ->orderBy(\strtolower($orderby), $sort); - - $sql->setParameters([$this->userId, "deleted"]); - + $sql->andWhere("base.user_id = :userid"); } return $this->findEntities($sql); } + + /** + * Fetch all items that are not deleted + * + * @return Object[] list if matching items + */ + function findActiveForReporter($orderby = "created", $sort = "ASC"): array + { + $sql = $this->db->getQueryBuilder(); + + $sql + ->selectDistinct("base.*") + ->from($this->tableName, "base") + ->andWhere("base.status != :status") + ->orderBy(\strtolower($orderby), $sort) + ->setParameter("status", "deleted"); + + return $this->findEntities($sql); + } } diff --git a/lib/Db/ProjectMapper.php b/lib/Db/ProjectMapper.php index b2426e8..0648324 100644 --- a/lib/Db/ProjectMapper.php +++ b/lib/Db/ProjectMapper.php @@ -2,7 +2,10 @@ namespace OCA\TimeManager\Db; +use OCP\IConfig; use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; /** * Class ProjectMapper @@ -11,14 +14,14 @@ * @method Project insert(Project $entity) */ class ProjectMapper extends ObjectMapper { - private $taskMapper; + private TaskMapper $taskMapper; - public function __construct(IDBConnection $db, CommitMapper $commitMapper, TaskMapper $taskMapper) { - parent::__construct($db, $commitMapper, "timemanager_project"); + public function __construct(IDBConnection $db, IConfig $config, IUserManager $userManager, IGroupManager $groupManager, CommitMapper $commitMapper, TaskMapper $taskMapper) { + parent::__construct($db, $config, $userManager, $groupManager, $commitMapper, "timemanager_project"); $this->taskMapper = $taskMapper; } - public function deleteWithChildrenByClientId($uuid, $commit) { + public function deleteWithChildrenByClientId(string $uuid, string $commit): void { $projects = $this->getActiveObjectsByAttributeValue("client_uuid", $uuid); foreach ($projects as $project) { $project->setCommit($commit); @@ -29,17 +32,19 @@ public function deleteWithChildrenByClientId($uuid, $commit) { } } - public function deleteChildrenForEntityById($uuid, $commit) { + public function deleteChildrenForEntityById(string $uuid, string $commit): void + { $this->taskMapper->deleteWithChildrenByProjectId($uuid, $commit); } - /** - * Gets the number of tasks for a given object. - * - * @param string $userId the user id to filter - * @return Client[] list if matching items - */ - public function countTasks($uuid) { + /** + * Gets the number of tasks for a given object. + * + * @param string $uuid + * @return int list if matching items + */ + public function countTasks(string $uuid): int + { $tasks = $this->taskMapper->getActiveObjectsByAttributeValue("project_uuid", $uuid, "created", true); return count($tasks); } diff --git a/lib/Db/ShareMapper.php b/lib/Db/ShareMapper.php index 351f3da..eed5aa7 100644 --- a/lib/Db/ShareMapper.php +++ b/lib/Db/ShareMapper.php @@ -2,7 +2,10 @@ namespace OCA\TimeManager\Db; +use OCP\IConfig; use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; /** * Class ItemMapper @@ -10,15 +13,14 @@ * @package OCA\TimeManager\Db */ class ShareMapper extends ObjectMapper { - protected IDBConnection $connection; - public function __construct(IDBConnection $connection, CommitMapper $commitMapper) { - $this->connection = $connection; - parent::__construct($connection, $commitMapper, "timemanager_share"); + public function __construct(IDBConnection $db, IConfig $config, IUserManager $userManager, IGroupManager $groupManager, CommitMapper $commitMapper) { + parent::__construct($db, $config, $userManager, $groupManager, $commitMapper, "timemanager_share"); } public function findShareesForClient($client_uuid): array { - $sql = $this->connection->getQueryBuilder(); + $sql = $this->db->getQueryBuilder(); + $sql ->select("*") ->from($this->tableName) @@ -26,11 +28,12 @@ public function findShareesForClient($client_uuid): array { ->andWhere("`object_uuid` = ?") ->andWhere("`entity_type` = 'client'"); $sql->setParameters([$this->userId, $client_uuid]); + return $this->findEntities($sql); } public function findSharerForClient($client_uuid): array { - $sql = $this->connection->getQueryBuilder(); + $sql = $this->db->getQueryBuilder(); $expr = $sql->expr()->orX( "share.`recipient_id` = :userid AND share.`recipient_type` = 'user'", @@ -45,11 +48,12 @@ public function findSharerForClient($client_uuid): array { ->andWhere("`object_uuid` = :client_uuid") ->andWhere("`entity_type` = 'client'"); $sql->setParameters(["userid" => $this->userId, "client_uuid" => $client_uuid]); + return $this->findEntities($sql); } public function findByUuid($uuid): array { - $sql = $this->connection->getQueryBuilder(); + $sql = $this->db->getQueryBuilder(); $sql ->select("*") ->from($this->tableName) @@ -57,6 +61,12 @@ public function findByUuid($uuid): array { ->andWhere("`uuid` = ?") ->andWhere("`entity_type` = 'client'"); $sql->setParameters([$this->userId, $uuid]); + return $this->findEntities($sql); } + + public function deleteChildrenForEntityById(string $uuid, string $commit): void + { + // Do nothing here, because shares have no children. + } } diff --git a/lib/Db/StorageHelper.php b/lib/Db/StorageHelper.php index 8946772..ce89a39 100644 --- a/lib/Db/StorageHelper.php +++ b/lib/Db/StorageHelper.php @@ -309,20 +309,27 @@ function prepareObjectForInsert(array $object): array { * @param string|null $clients A comma-separated list of client uuids * @param string|null $projects A comma-separated list of project uuids * @param string|null $tasks A comma-separated list of task uuids - * @return array A comma-separated list of task uuids + * @return ?array A comma-separated list of task uuids */ function getTaskListFromFilters( string $clients = null, string $projects = null, string $tasks = null, - $shared = false - ): array { - $all_projects = $this->projectMapper->findActiveForCurrentUser("name", $shared); - $all_tasks = $this->taskMapper->findActiveForCurrentUser("name", $shared); + bool $shared = false, + bool $isReporterOrAdmin = false, + ): ?array { + if ($isReporterOrAdmin) { + $all_projects = $this->projectMapper->findActiveForReporter("name"); + $all_tasks = $this->taskMapper->findActiveForReporter("name"); + } else { + $all_projects = $this->projectMapper->findActiveForCurrentUser("name", $shared); + $all_tasks = $this->taskMapper->findActiveForCurrentUser("name", $shared); + } // Get task uuids related to filters. // Filters are exclusive from finer to coarse. // That is, finer filters override more coarse filters. + // If this filter_tasks array stays empty, there is no entry shown // Example: If a task filter is set, project and client filters will be ignored $filter_tasks = []; if (isset($tasks) && strlen($tasks) > 0) { @@ -352,10 +359,12 @@ function getTaskListFromFilters( } } } - } - $filter_tasks = array_unique($filter_tasks); + } else { + // in case there is no task filtered use null + return null; + } - return $filter_tasks; + return array_unique($filter_tasks); } /** diff --git a/lib/Db/TaskMapper.php b/lib/Db/TaskMapper.php index 6928319..9913257 100644 --- a/lib/Db/TaskMapper.php +++ b/lib/Db/TaskMapper.php @@ -2,7 +2,10 @@ namespace OCA\TimeManager\Db; +use OCP\IConfig; use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; /** * Class TaskMapper @@ -11,14 +14,15 @@ * @method Task insert(Task $entity) */ class TaskMapper extends ObjectMapper { - private $timeMapper; + private TimeMapper $timeMapper; - public function __construct(IDBConnection $db, CommitMapper $commitMapper, TimeMapper $timeMapper) { - parent::__construct($db, $commitMapper, "timemanager_task"); + public function __construct(IDBConnection $db, IConfig $config, IUserManager $userManager, IGroupManager $groupManager, CommitMapper $commitMapper, TimeMapper $timeMapper) { + parent::__construct($db, $config, $userManager, $groupManager, $commitMapper, "timemanager_task"); $this->timeMapper = $timeMapper; } - public function deleteWithChildrenByProjectId($uuid, $commit) { + public function deleteWithChildrenByProjectId($uuid, $commit): void + { $tasks = $this->getActiveObjectsByAttributeValue("project_uuid", $uuid); foreach ($tasks as $task) { $task->setChanged(date("Y-m-d H:i:s")); @@ -29,7 +33,8 @@ public function deleteWithChildrenByProjectId($uuid, $commit) { } } - public function deleteChildrenForEntityById($uuid, $commit) { + public function deleteChildrenForEntityById(string $uuid, string $commit): void + { $this->timeMapper->deleteByTaskId($uuid, $commit); } diff --git a/lib/Db/TimeMapper.php b/lib/Db/TimeMapper.php index abe6589..942be1f 100644 --- a/lib/Db/TimeMapper.php +++ b/lib/Db/TimeMapper.php @@ -2,7 +2,10 @@ namespace OCA\TimeManager\Db; +use OCP\IConfig; use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; /** * Class TimeMapper @@ -10,32 +13,110 @@ * @package OCA\TimeManager\Db * @method Time insert(Time $entity) */ -class TimeMapper extends ObjectMapper { - public function __construct(IDBConnection $db, CommitMapper $commitMapper) { - parent::__construct($db, $commitMapper, "timemanager_time"); - } - - public function deleteByTaskId($uuid, $commit) { - $times = $this->getActiveObjectsByAttributeValue("task_uuid", $uuid); - foreach ($times as $time) { - $time->setCommit($commit); - $time->setChanged(date("Y-m-d H:i:s")); - $time->setStatus("deleted"); - $this->update($time); - } - } - - public function deleteChildrenForEntityById($uuid, $commit) { - // Do nothing here, because times have no children. - } - - public function findForReport( - string $start, - string $end, - string $status = null, - array $filter_tasks = [], - $shared = false - ) { - return $this->getActiveObjectsByDateRangeAndFilters($start, $end, $status, $filter_tasks, "start", $shared); - } +class TimeMapper extends ObjectMapper +{ + public function __construct(IDBConnection $db, IConfig $config, IUserManager $userManager, IGroupManager $groupManager, CommitMapper $commitMapper) + { + parent::__construct($db, $config, $userManager, $groupManager, $commitMapper, "timemanager_time"); + } + + public function deleteByTaskId($uuid, $commit): void + { + $times = $this->getActiveObjectsByAttributeValue("task_uuid", $uuid); + foreach ($times as $time) { + $time->setCommit($commit); + $time->setChanged(date("Y-m-d H:i:s")); + $time->setStatus("deleted"); + $this->update($time); + } + } + + public function deleteChildrenForEntityById($uuid, $commit): void + { + // Do nothing here, because times have no children. + } + + /** + * + * Fetch all time items that are associated to the current user + * within a given timerange, not deleted and with applied filters + * + * @param string $date_start + * @param string $date_end + * @param string|null $status + * @param array|null $filter_tasks + * @param bool $shared + * @param bool $isReporterOrAdmin + * @return array + * @throws \OCP\DB\Exception + */ + public function findForReport( + string $date_start, + string $date_end, + string $status = null, + ?array $filter_tasks = [], + bool $shared = false, + bool $isReporterOrAdmin = false, + ): array { + $params = [ + "userid" => $this->userId, + "deleted" => "deleted", + "date_start" => $date_start, + "date_end" => $date_end, + ]; + $sql = $this->db->getQueryBuilder(); + if ($isReporterOrAdmin) { + $shared = false; + } + + $sql->selectDistinct("current.*") + ->from($this->tableName, "current") + ->where("current.`status` != :deleted"); + + if ($shared) { + $sql->innerJoin("current", "*PREFIX*timemanager_task", "task", "current.`task_uuid` = task.`uuid`") + ->innerJoin("task", "*PREFIX*timemanager_project", "project", "task.`project_uuid` = project.`uuid`") + ->leftJoin( + "project", + "*PREFIX*timemanager_share", + "share", + "project.`client_uuid` = share.`object_uuid` AND share.`author_user_id` = :userid" + ) + ->leftJoin("share", "*PREFIX*group_user", "group_user", "share.recipient_id = group_user.gid"); + + $expr = $sql->expr() + ->orX("share.`author_user_id` = :userid", "current.`user_id` = :userid"); + + $sql->andWhere($expr); + } elseif (!$isReporterOrAdmin) { + $sql->andWhere("current.`user_id` = :userid"); + } + + // Range can be one day as well + if ($date_start === $date_end) { + $sql->andWhere("date(current.`start`) = :date_start"); + } else { + $sql->andWhere("date(current.`start`) >= :date_start") + ->andWhere("date(current.`start`) <= :date_end"); + } + if (isset($status) && $status) { + if ($status === "paid") { + $sql->andWhere("LOWER(`payment_status`) = :status"); + $params["status"] = strtolower($status); + } else { + $expr = $sql->expr() + ->orX("`payment_status` IS NULL", "LOWER(`payment_status`) <> :status"); + $sql->andWhere($expr); + $params["status"] = "paid"; + } + } + if (is_array($filter_tasks)) { + $sql->andWhere("`task_uuid` IN ('" . implode("','", $filter_tasks) . "')"); + } + + $sql->orderBy("start", "ASC"); + $sql->setParameters($params); + + return $this->findEntities($sql); + } } diff --git a/lib/Sections/TimeManagerAdmin.php b/lib/Sections/TimeManagerAdmin.php new file mode 100644 index 0000000..a33e7c1 --- /dev/null +++ b/lib/Sections/TimeManagerAdmin.php @@ -0,0 +1,38 @@ +l->t("TimeManager"); + } + + public function getPriority(): int + { + return 98; + } + + public function getIcon(): string + { + return $this->urlGenerator->imagePath(Application::APP_ID, 'timemanager-dark.svg'); + } +} diff --git a/lib/Settings/TimeManagerAdmin.php b/lib/Settings/TimeManagerAdmin.php new file mode 100644 index 0000000..b93ff2d --- /dev/null +++ b/lib/Settings/TimeManagerAdmin.php @@ -0,0 +1,48 @@ +groupManager->getBackends() as $backend) { + $groups = array_merge($groups, $backend->getGroups()); + } + + $parameters = [ + 'groups' => array_unique($groups), + 'reporter_group' => $this->config->getAppValue(Application::APP_ID, 'reporter_group'), + 'sync_mode' => $this->config->getAppValue(Application::APP_ID, 'sync_mode', 'force_skip_conflict_handling'), + ]; + + Util::addScript(Application::APP_ID, 'settings'); + + return new TemplateResponse(Application::APP_ID, 'settings/admin', $parameters); + } + + public function getSection(): string + { + return Application::APP_ID; + } + + public function getPriority(): int + { + return 10; + } +} diff --git a/package-lock.json b/package-lock.json index eacefb3..7ad7455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@nextcloud/browserslist-config": "^2.0.0", + "@nextcloud/browserslist-config": "^2.3.0", "@nextcloud/l10n": "^2.1.0", "@popperjs/core": "^2.11.8", "csv-parse": "^5.4.0", @@ -28,7 +28,7 @@ "@babel/preset-env": "^7.22.6", "@nextcloud/auth": "^2.0.0", "@nextcloud/router": "^2.0.1", - "@prettier/plugin-php": "^0.19.6", + "@prettier/plugin-php": "^0.20.1", "babel-eslint": "^10.1.0", "core-js": "^3.31.0", "eslint": "^8.44.0", @@ -1839,9 +1839,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.0.tgz", + "integrity": "sha512-zJmuCWj2VLBt4c25CfBIbMZLGLyhkvs7LznyVX5HfpzeocThgIj5XQK4L+g3U36mMcx8bPMhGyPpwCATamC4jQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2744,9 +2744,13 @@ } }, "node_modules/@nextcloud/browserslist-config": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-2.0.0.tgz", - "integrity": "sha512-tWs+OGgA1Oaq/LKUQxFUpPwnLW5Q2ZsjqeHT/v1ABG39bTlXakyg7lFRLtQ3Zj8ErjXl73WyTsF20d9o5BhmEg==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-2.3.0.tgz", + "integrity": "sha512-1Tpkof2e9Q0UicHWahQnXXrubJoqyiaqsH9G52v3cjGeVeH3BCfa1FOa41eBwBSFe2/Jxj/wCH2YVLgIXpWbBg==", + "engines": { + "node": "^16.0.0", + "npm": "^7.0.0 || ^8.0.0" + } }, "node_modules/@nextcloud/event-bus": { "version": "3.0.2", @@ -2762,9 +2766,9 @@ } }, "node_modules/@nextcloud/event-bus/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2858,9 +2862,9 @@ } }, "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -2895,17 +2899,17 @@ } }, "node_modules/@prettier/plugin-php": { - "version": "0.19.6", - "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.19.6.tgz", - "integrity": "sha512-MFaBfdxfTbGNwClJ/EZd2ROTa/Bd7EV+ff2KG769I9RsmNcVWcHARIYlSlJqFGgSbumOm+Ylm6RIXdxb5kG+OA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.20.1.tgz", + "integrity": "sha512-CFn+NH44jb0buvg8D/DvLjeetadmNBMRq3yrgL8I+tXioDZlhQ+eBLLRILPEBIQ6jBmOjzm/Q7Qin8KD7raiFw==", "dev": true, "dependencies": { "linguist-languages": "^7.21.0", - "mem": "^8.0.0", + "mem": "^9.0.2", "php-parser": "^3.1.5" }, "peerDependencies": { - "prettier": "^1.15.0 || ^2.0.0" + "prettier": "^3.0.0" } }, "node_modules/@rollup/pluginutils": { @@ -4492,9 +4496,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.2.0.tgz", - "integrity": "sha512-AQER2jEyQOt1LG6JkGJCCIFotzmlcCZFur2wdKrp1JX2cNotC7Ae0BcD/4lLv3lUAArM9uNS8z/fsvXTd0L71g==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.1.0.tgz", + "integrity": "sha512-3wv/TooBst0N4ND+pnvffHuz9gNPmk/NkLwAxOt2JykTl/hcuECe6yhTtLJcZjIxtZwN+GX92ACp/QTLpHA3Hg==", "dev": true, "peer": true, "dependencies": { @@ -7814,16 +7818,16 @@ } }, "node_modules/mem": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", - "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz", + "integrity": "sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==", "dev": true, "dependencies": { "map-age-cleaner": "^0.1.3", - "mimic-fn": "^3.1.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sindresorhus/mem?sponsor=1" @@ -7896,12 +7900,15 @@ } }, "node_modules/mimic-fn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", - "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/min-indent": { @@ -8154,9 +8161,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8440,9 +8447,9 @@ } }, "node_modules/node-sass/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8559,9 +8566,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -8700,7 +8707,7 @@ "node_modules/p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", "dev": true, "engines": { "node": ">=4" @@ -9197,9 +9204,9 @@ } }, "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -9350,9 +9357,9 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -9653,9 +9660,9 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -11820,9 +11827,9 @@ } }, "@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.0.tgz", + "integrity": "sha512-zJmuCWj2VLBt4c25CfBIbMZLGLyhkvs7LznyVX5HfpzeocThgIj5XQK4L+g3U36mMcx8bPMhGyPpwCATamC4jQ==", "dev": true }, "@eslint/eslintrc": { @@ -12506,9 +12513,9 @@ } }, "@nextcloud/browserslist-config": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-2.0.0.tgz", - "integrity": "sha512-tWs+OGgA1Oaq/LKUQxFUpPwnLW5Q2ZsjqeHT/v1ABG39bTlXakyg7lFRLtQ3Zj8ErjXl73WyTsF20d9o5BhmEg==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-2.3.0.tgz", + "integrity": "sha512-1Tpkof2e9Q0UicHWahQnXXrubJoqyiaqsH9G52v3cjGeVeH3BCfa1FOa41eBwBSFe2/Jxj/wCH2YVLgIXpWbBg==" }, "@nextcloud/event-bus": { "version": "3.0.2", @@ -12520,9 +12527,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -12592,9 +12599,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -12618,13 +12625,13 @@ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@prettier/plugin-php": { - "version": "0.19.6", - "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.19.6.tgz", - "integrity": "sha512-MFaBfdxfTbGNwClJ/EZd2ROTa/Bd7EV+ff2KG769I9RsmNcVWcHARIYlSlJqFGgSbumOm+Ylm6RIXdxb5kG+OA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@prettier/plugin-php/-/plugin-php-0.20.1.tgz", + "integrity": "sha512-CFn+NH44jb0buvg8D/DvLjeetadmNBMRq3yrgL8I+tXioDZlhQ+eBLLRILPEBIQ6jBmOjzm/Q7Qin8KD7raiFw==", "dev": true, "requires": { "linguist-languages": "^7.21.0", - "mem": "^8.0.0", + "mem": "^9.0.2", "php-parser": "^3.1.5" } }, @@ -13921,9 +13928,9 @@ } }, "eslint-plugin-n": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.2.0.tgz", - "integrity": "sha512-AQER2jEyQOt1LG6JkGJCCIFotzmlcCZFur2wdKrp1JX2cNotC7Ae0BcD/4lLv3lUAArM9uNS8z/fsvXTd0L71g==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.1.0.tgz", + "integrity": "sha512-3wv/TooBst0N4ND+pnvffHuz9gNPmk/NkLwAxOt2JykTl/hcuECe6yhTtLJcZjIxtZwN+GX92ACp/QTLpHA3Hg==", "dev": true, "peer": true, "requires": { @@ -16266,13 +16273,13 @@ "dev": true }, "mem": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", - "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz", + "integrity": "sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==", "dev": true, "requires": { "map-age-cleaner": "^0.1.3", - "mimic-fn": "^3.1.0" + "mimic-fn": "^4.0.0" } }, "meow": { @@ -16326,9 +16333,9 @@ } }, "mimic-fn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", - "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true }, "min-indent": { @@ -16525,9 +16532,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -16748,9 +16755,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -16838,9 +16845,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -16941,7 +16948,7 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", "dev": true }, "p-limit": { @@ -17232,9 +17239,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "type-fest": { @@ -17406,9 +17413,9 @@ "dev": true }, "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "dev": true, "requires": { "is-core-module": "^2.13.0", @@ -17624,9 +17631,9 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "set-blocking": { diff --git a/package.json b/package.json index e337794..92a0bd6 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@babel/preset-env": "^7.22.6", "@nextcloud/auth": "^2.0.0", "@nextcloud/router": "^2.0.1", - "@prettier/plugin-php": "^0.19.6", + "@prettier/plugin-php": "^0.20.1", "babel-eslint": "^10.1.0", "core-js": "^3.31.0", "eslint": "^8.44.0", @@ -49,7 +49,7 @@ "svelte": "^3.58.0" }, "dependencies": { - "@nextcloud/browserslist-config": "^2.0.0", + "@nextcloud/browserslist-config": "^2.3.0", "@nextcloud/l10n": "^2.1.0", "@popperjs/core": "^2.11.8", "csv-parse": "^5.4.0", diff --git a/templates/partials/navigation.php b/templates/partials/navigation.php index 86c6036..a0d7165 100644 --- a/templates/partials/navigation.php +++ b/templates/partials/navigation.php @@ -72,15 +72,4 @@ class="timemanager-pjax-link - - - \ No newline at end of file + diff --git a/templates/settings/admin.php b/templates/settings/admin.php new file mode 100644 index 0000000..ec798b9 --- /dev/null +++ b/templates/settings/admin.php @@ -0,0 +1,32 @@ + + t('Reporter')) ?> + + t('Assign a role which is allowed to view all Entries.')) ?> + + + disabled>t("Select...")) ?> + + > + + + + + + + t('Sync Mode')) ?> + + t('How confilicts are handled.')) ?> + + + + /> + + t("My (mobile) apps can handle conflicts (leave unchecked if you're unsure)")) ?> + + + +
+ t('Assign a role which is allowed to view all Entries.')) ?> +
+ t('How confilicts are handled.')) ?> +