dhtmlx-scheduler 7.2.10 → 7.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  })(this, function(exports2) {
4
4
  "use strict";/** @license
5
5
 
6
- dhtmlxScheduler v.7.2.10 Standard
6
+ dhtmlxScheduler v.7.2.12 Standard
7
7
 
8
8
  To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product), please obtain Commercial/Enterprise or Ultimate license on our site https://dhtmlx.com/docs/products/dhtmlxScheduler/#licensing or contact us at sales@dhtmlx.com
9
9
 
@@ -1301,7 +1301,7 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
1301
1301
  if (scheduler2._dp && scheduler2._dp._in_progress[event2.id]) {
1302
1302
  return;
1303
1303
  }
1304
- if (type === "add-event") {
1304
+ if (type === "add-event" && scheduler2._dp) {
1305
1305
  for (const id in scheduler2._dp._in_progress) {
1306
1306
  if (scheduler2._dp.getState(id) === "inserted") {
1307
1307
  scheduler2._dp.attachEvent("onFullSync", function() {
@@ -1340,9 +1340,13 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
1340
1340
  console.warn(`Event with ID ${eventData.id} already exists. Skipping add.`);
1341
1341
  return;
1342
1342
  }
1343
- eventData.start_date = scheduler2.templates.parse_date(eventData.start_date);
1344
- eventData.end_date = scheduler2.templates.parse_date(eventData.end_date);
1345
- if (eventData.original_start) {
1343
+ if (typeof eventData.start_date === "string") {
1344
+ eventData.start_date = scheduler2.templates.parse_date(eventData.start_date);
1345
+ }
1346
+ if (typeof eventData.end_date === "string") {
1347
+ eventData.end_date = scheduler2.templates.parse_date(eventData.end_date);
1348
+ }
1349
+ if (eventData.original_start && typeof eventData.original_start === "string") {
1346
1350
  eventData.original_start = scheduler2.templates.parse_date(eventData.original_start);
1347
1351
  }
1348
1352
  ignore(() => {
@@ -1362,9 +1366,13 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
1362
1366
  existingEvent[key] = eventData[key];
1363
1367
  }
1364
1368
  }
1365
- existingEvent.start_date = scheduler2.templates.parse_date(eventData.start_date);
1366
- existingEvent.end_date = scheduler2.templates.parse_date(eventData.end_date);
1367
- if (eventData.original_start) {
1369
+ if (typeof eventData.start_date === "string") {
1370
+ eventData.start_date = scheduler2.templates.parse_date(eventData.start_date);
1371
+ }
1372
+ if (typeof eventData.end_date === "string") {
1373
+ eventData.end_date = scheduler2.templates.parse_date(eventData.end_date);
1374
+ }
1375
+ if (eventData.original_start && typeof eventData.original_start === "string") {
1368
1376
  eventData.original_start = scheduler2.templates.parse_date(eventData.original_start);
1369
1377
  }
1370
1378
  scheduler2.callEvent("onEventChanged", [sid, existingEvent]);
@@ -2095,18 +2103,21 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
2095
2103
  });
2096
2104
  return views.concat(date).concat(nav);
2097
2105
  }
2106
+ scheduler2._getInitialState = function(provided) {
2107
+ const initialDate = provided.date || this._currentDate();
2108
+ const initialView = provided.mode || "week";
2109
+ return { date: initialDate, mode: initialView };
2110
+ };
2098
2111
  scheduler2.init = function(id, date, mode) {
2099
2112
  if (this.$destroyed) {
2100
2113
  return;
2101
2114
  }
2102
- date = date || scheduler2._currentDate();
2103
- mode = mode || "week";
2104
- if (this._obj) {
2105
- this.unset_actions();
2106
- }
2107
2115
  this._obj = typeof id == "string" ? document.getElementById(id) : id;
2108
2116
  this.$container = this._obj;
2109
2117
  this.$root = this._obj;
2118
+ if (this._obj) {
2119
+ this.unset_actions();
2120
+ }
2110
2121
  if (!this.$container.offsetHeight && this.$container.offsetWidth && this.$container.style.height === "100%") {
2111
2122
  window.console.error(scheduler2._commonErrorMessages.collapsedContainer(), this.$container);
2112
2123
  }
@@ -2144,9 +2155,12 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
2144
2155
  this._init_once();
2145
2156
  this._init_touch_events();
2146
2157
  this.set_sizes();
2158
+ const initialState = scheduler2._getInitialState({ date, mode });
2159
+ const initialDate = initialState.date;
2160
+ const initialMode = initialState.mode;
2147
2161
  scheduler2.callEvent("onSchedulerReady", []);
2148
2162
  scheduler2.$initialized = true;
2149
- this.setCurrentView(date, mode);
2163
+ this.setCurrentView(initialDate, initialMode);
2150
2164
  };
2151
2165
  scheduler2.xy = { min_event_height: 20, bar_height: 24, scale_width: 50, scroll_width: 18, scale_height: 20, month_scale_height: 20, menu_width: 25, margin_top: 0, margin_left: 0, editor_width: 140, month_head_height: 22, event_header_height: 14 };
2152
2166
  scheduler2.keys = { edit_save: 13, edit_cancel: 27 };
@@ -2169,7 +2183,25 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
2169
2183
  this.$container.insertBefore(materialScalePlaceholder, this._els["dhx_cal_header"][0]);
2170
2184
  }
2171
2185
  materialScalePlaceholder.style.display = "block";
2172
- this.set_xy(materialScalePlaceholder, w, this.xy.scale_height + 1, 0, this._els["dhx_cal_header"][0].offsetTop);
2186
+ const navElement = scheduler2.$root.querySelector(".dhx_cal_navline");
2187
+ let navHeight = 0;
2188
+ if (navElement) {
2189
+ navHeight += navElement.offsetHeight;
2190
+ }
2191
+ const headerElement = scheduler2.$root.querySelector(".dhx_cal_header");
2192
+ let headerHeight = 0;
2193
+ if (headerElement) {
2194
+ headerHeight += headerElement.offsetHeight;
2195
+ }
2196
+ let offsetTop, offsetHeight;
2197
+ if (!headerHeight) {
2198
+ offsetTop = navHeight - 4;
2199
+ offsetHeight = 5;
2200
+ } else {
2201
+ offsetTop = navHeight + 1;
2202
+ offsetHeight = headerHeight;
2203
+ }
2204
+ this.set_xy(materialScalePlaceholder, w, offsetHeight, 0, offsetTop);
2173
2205
  } else {
2174
2206
  if (materialScalePlaceholder) {
2175
2207
  materialScalePlaceholder.parentNode.removeChild(materialScalePlaceholder);
@@ -3061,11 +3093,8 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
3061
3093
  var container = this._obj;
3062
3094
  var oldClass = "dhx_scheduler_" + this._mode;
3063
3095
  var newClass = "dhx_scheduler_" + mode;
3064
- if (!this._mode || container.className.indexOf(oldClass) == -1) {
3065
- container.className += " " + newClass;
3066
- } else {
3067
- container.className = container.className.replace(oldClass, newClass);
3068
- }
3096
+ container.classList.remove(oldClass);
3097
+ container.classList.add(newClass);
3069
3098
  var dhx_multi_day = "dhx_multi_day";
3070
3099
  var prev_scroll = this._mode == mode && this.config.preserve_scroll ? this._els[dhx_cal_data][0].scrollTop : false;
3071
3100
  var multidayScroll;
@@ -4205,6 +4234,7 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
4205
4234
  this.clearAll();
4206
4235
  if (this.$container) {
4207
4236
  this.$container.innerHTML = "";
4237
+ this.$container.classList.remove(`dhx_scheduler_${this._mode}`);
4208
4238
  }
4209
4239
  if (this._eventRemoveAll) {
4210
4240
  this._eventRemoveAll();
@@ -9241,7 +9271,7 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
9241
9271
  }
9242
9272
  }
9243
9273
  function factoryMethod(extensionManager) {
9244
- const scheduler2 = { version: "7.2.10" };
9274
+ const scheduler2 = { version: "7.2.12" };
9245
9275
  scheduler2.$stateProvider = StateService();
9246
9276
  scheduler2.getState = scheduler2.$stateProvider.getState;
9247
9277
  extend$n(scheduler2);
@@ -9725,7 +9755,10 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
9725
9755
  scheduler2._colsS = null;
9726
9756
  scheduler2._table_view = true;
9727
9757
  const dateHeader = scheduler2._getNavDateElement();
9728
- dateHeader.innerHTML = scheduler2.templates.agenda_date(scheduler2._date);
9758
+ if (dateHeader) {
9759
+ dateHeader.innerHTML = scheduler2.templates.agenda_date(scheduler2._date);
9760
+ }
9761
+ scheduler2.set_sizes();
9729
9762
  fill_agenda_tab();
9730
9763
  } else {
9731
9764
  scheduler2._table_view = false;
@@ -10290,74 +10323,95 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
10290
10323
  }
10291
10324
  }
10292
10325
  function cookie(scheduler2) {
10293
- function setCookie(name, cookie_param, value) {
10294
- const str = `${name}=${value}${cookie_param ? `; ${cookie_param}` : ""}`;
10295
- document.cookie = `${str}; Secure`;
10296
- }
10297
- function getCookie(name) {
10298
- var search = name + "=";
10299
- if (document.cookie.length > 0) {
10300
- var offset = document.cookie.indexOf(search);
10301
- if (offset != -1) {
10302
- offset += search.length;
10303
- var end = document.cookie.indexOf(";", offset);
10304
- if (end == -1)
10305
- end = document.cookie.length;
10306
- return document.cookie.substring(offset, end);
10307
- }
10326
+ function getStorageKey() {
10327
+ return (scheduler2._obj.id || "scheduler") + "_settings";
10328
+ }
10329
+ const dateToString = scheduler2.date.date_to_str("%Y-%m-%d %H:%i");
10330
+ const stringToDate = scheduler2.date.str_to_date("%Y-%m-%d %H:%i");
10331
+ function safeLocalStorageGet(key) {
10332
+ try {
10333
+ return window.localStorage.getItem(key);
10334
+ } catch (e) {
10335
+ return null;
10308
10336
  }
10309
- return "";
10310
10337
  }
10311
- function getCookieName(scheduler3) {
10312
- return (scheduler3._obj.id || "scheduler") + "_settings";
10338
+ function safeLocalStorageSet(key, value) {
10339
+ try {
10340
+ window.localStorage.setItem(key, value);
10341
+ return true;
10342
+ } catch (e) {
10343
+ return false;
10344
+ }
10313
10345
  }
10314
- var first = true;
10315
- scheduler2.attachEvent("onBeforeViewChange", function(oldMode, oldDate, mode, date) {
10316
- if (first && scheduler2._get_url_nav) {
10317
- var urlNavigationPlugin = scheduler2._get_url_nav();
10318
- if (urlNavigationPlugin.date || urlNavigationPlugin.mode || urlNavigationPlugin.event) {
10319
- first = false;
10346
+ function readStoredState() {
10347
+ const storageKey = getStorageKey();
10348
+ const raw = safeLocalStorageGet(storageKey);
10349
+ if (!raw) {
10350
+ return { date: null, mode: null };
10351
+ }
10352
+ let parsed;
10353
+ try {
10354
+ parsed = JSON.parse(raw);
10355
+ } catch (e) {
10356
+ return { date: null, mode: null };
10357
+ }
10358
+ if (!parsed || typeof parsed !== "object") {
10359
+ return { date: null, mode: null };
10360
+ }
10361
+ let parsedDate = null;
10362
+ if (typeof parsed.date === "string" && parsed.date.length > 0) {
10363
+ try {
10364
+ parsedDate = stringToDate(parsed.date);
10365
+ if (isNaN(+parsedDate)) {
10366
+ parsedDate = null;
10367
+ }
10368
+ } catch (e) {
10369
+ parsedDate = null;
10320
10370
  }
10321
10371
  }
10322
- var cookie2 = getCookieName(scheduler2);
10323
- if (first) {
10324
- first = false;
10325
- var schedulerCookie = getCookie(cookie2);
10326
- if (schedulerCookie) {
10327
- if (!scheduler2._min_date) {
10328
- scheduler2._min_date = date;
10372
+ const parsedMode = typeof parsed.mode === "string" ? parsed.mode : null;
10373
+ return { date: parsedDate, mode: parsedMode };
10374
+ }
10375
+ function urlHasExplicitNavigation() {
10376
+ if (!scheduler2._get_url_nav) {
10377
+ return false;
10378
+ }
10379
+ const urlState = scheduler2._get_url_nav();
10380
+ if (!urlState) {
10381
+ return false;
10382
+ }
10383
+ if (urlState.date || urlState.mode || urlState.event) {
10384
+ return true;
10385
+ }
10386
+ return false;
10387
+ }
10388
+ const originalGetInitialState = scheduler2._getInitialState;
10389
+ scheduler2._getInitialState = function(provided) {
10390
+ const baseState = originalGetInitialState.call(this, provided);
10391
+ if (urlHasExplicitNavigation()) {
10392
+ return baseState;
10393
+ }
10394
+ const storedState = readStoredState();
10395
+ const nextState = { date: baseState.date, mode: baseState.mode };
10396
+ if (storedState.date) {
10397
+ nextState.date = storedState.date;
10398
+ }
10399
+ if (storedState.mode) {
10400
+ try {
10401
+ if (this.isViewExists(storedState.mode)) {
10402
+ nextState.mode = storedState.mode;
10329
10403
  }
10330
- schedulerCookie = unescape(schedulerCookie).split("@");
10331
- schedulerCookie[0] = this._helpers.parseDate(schedulerCookie[0]);
10332
- var view = this.isViewExists(schedulerCookie[1]) ? schedulerCookie[1] : mode, date = !isNaN(+schedulerCookie[0]) ? schedulerCookie[0] : date;
10333
- window.setTimeout(function() {
10334
- if (scheduler2.$destroyed) {
10335
- return;
10336
- }
10337
- scheduler2.setCurrentView(date, view);
10338
- }, 1);
10339
- return false;
10404
+ } catch (e) {
10340
10405
  }
10341
10406
  }
10342
- return true;
10343
- });
10407
+ return nextState;
10408
+ };
10344
10409
  scheduler2.attachEvent("onViewChange", function(newMode, newDate) {
10345
- var cookie2 = getCookieName(scheduler2);
10346
- var text = escape(this._helpers.formatDate(newDate) + "@" + newMode);
10347
- setCookie(cookie2, "expires=Sun, 31 Jan 9999 22:00:00 GMT", text);
10410
+ const storageKey = getStorageKey();
10411
+ const payload = { date: dateToString(newDate), mode: newMode };
10412
+ safeLocalStorageSet(storageKey, JSON.stringify(payload));
10413
+ return true;
10348
10414
  });
10349
- var old_load = scheduler2._load;
10350
- scheduler2._load = function() {
10351
- var args = arguments;
10352
- if (!scheduler2._date) {
10353
- var that = this;
10354
- window.setTimeout(function() {
10355
- old_load.apply(that, args);
10356
- }, 1);
10357
- } else {
10358
- old_load.apply(this, args);
10359
- }
10360
- };
10361
10415
  }
10362
10416
  const notImplemented = { alert: (extension, assert2) => {
10363
10417
  assert2(false, `The ${extension} extension is not included in this version of dhtmlxScheduler.<br>
@@ -15423,41 +15477,39 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
15423
15477
  scheduler2.hideQuickInfo();
15424
15478
  };
15425
15479
  scheduler2._init_quick_info = function() {
15426
- if (!this._quick_info_box) {
15427
- var qi = this._quick_info_box = document.createElement("div");
15428
- this._waiAria.quickInfoAttr(qi);
15429
- qi.className = "dhx_cal_quick_info";
15430
- if (scheduler2.$testmode)
15431
- qi.className += " dhx_no_animate";
15432
- if (scheduler2.config.rtl)
15433
- qi.className += " dhx_quick_info_rtl";
15434
- var ariaAttr = this._waiAria.quickInfoHeaderAttrString();
15435
- var html = `
15436
- <div class="dhx_cal_qi_tcontrols">
15437
- <a class="dhx_cal_qi_close_btn scheduler_icon close"></a>
15480
+ let qi = this._quick_info_box = document.createElement("div");
15481
+ this._waiAria.quickInfoAttr(qi);
15482
+ qi.className = "dhx_cal_quick_info";
15483
+ if (scheduler2.$testmode)
15484
+ qi.className += " dhx_no_animate";
15485
+ if (scheduler2.config.rtl)
15486
+ qi.className += " dhx_quick_info_rtl";
15487
+ let ariaAttr = this._waiAria.quickInfoHeaderAttrString();
15488
+ let html = `
15489
+ <div class="dhx_cal_qi_tcontrols">
15490
+ <a class="dhx_cal_qi_close_btn scheduler_icon close"></a>
15491
+ </div>
15492
+ <div class="dhx_cal_qi_title" ${ariaAttr}>
15493
+
15494
+ <div class="dhx_cal_qi_tcontent"></div>
15495
+ <div class="dhx_cal_qi_tdate"></div>
15438
15496
  </div>
15439
- <div class="dhx_cal_qi_title" ${ariaAttr}>
15440
-
15441
- <div class="dhx_cal_qi_tcontent"></div>
15442
- <div class="dhx_cal_qi_tdate"></div>
15443
- </div>
15444
- <div class="dhx_cal_qi_content"></div>`;
15445
- html += '<div class="dhx_cal_qi_controls">';
15446
- var buttons = scheduler2.config.icons_select;
15447
- for (var i = 0; i < buttons.length; i++) {
15448
- var ariaAttr = this._waiAria.quickInfoButtonAttrString(this.locale.labels[buttons[i]]);
15449
- html += `<div ${ariaAttr} class="dhx_qi_big_icon ${buttons[i]}" title="${scheduler2.locale.labels[buttons[i]]}">
15450
- <div class='dhx_menu_icon ${buttons[i]}'></div><div>${scheduler2.locale.labels[buttons[i]]}</div></div>`;
15451
- }
15452
- html += "</div>";
15453
- qi.innerHTML = html;
15454
- scheduler2.event(qi, "click", function(ev) {
15455
- scheduler2._qi_button_click(ev.target || ev.srcElement);
15456
- });
15457
- if (scheduler2.config.quick_info_detached) {
15458
- scheduler2._detachDomEvent(scheduler2._els["dhx_cal_data"][0], "scroll", scheduler2._quick_info_onscroll_handler);
15459
- scheduler2.event(scheduler2._els["dhx_cal_data"][0], "scroll", scheduler2._quick_info_onscroll_handler);
15460
- }
15497
+ <div class="dhx_cal_qi_content"></div>`;
15498
+ html += '<div class="dhx_cal_qi_controls">';
15499
+ let buttons = scheduler2.config.icons_select;
15500
+ for (let i = 0; i < buttons.length; i++) {
15501
+ let ariaAttr2 = this._waiAria.quickInfoButtonAttrString(this.locale.labels[buttons[i]]);
15502
+ html += `<div ${ariaAttr2} class="dhx_qi_big_icon ${buttons[i]}" title="${scheduler2.locale.labels[buttons[i]]}">
15503
+ <div class='dhx_menu_icon ${buttons[i]}'></div><div>${scheduler2.locale.labels[buttons[i]]}</div></div>`;
15504
+ }
15505
+ html += "</div>";
15506
+ qi.innerHTML = html;
15507
+ scheduler2.event(qi, "click", function(ev) {
15508
+ scheduler2._qi_button_click(ev.target || ev.srcElement);
15509
+ });
15510
+ if (scheduler2.config.quick_info_detached) {
15511
+ scheduler2._detachDomEvent(scheduler2._els["dhx_cal_data"][0], "scroll", scheduler2._quick_info_onscroll_handler);
15512
+ scheduler2.event(scheduler2._els["dhx_cal_data"][0], "scroll", scheduler2._quick_info_onscroll_handler);
15461
15513
  }
15462
15514
  return this._quick_info_box;
15463
15515
  };
@@ -18191,6 +18243,73 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18191
18243
  return "".concat(header).concat(dateString);
18192
18244
  }
18193
18245
  function recurring(scheduler2) {
18246
+ scheduler2.ext.recurring = { confirm: function(context) {
18247
+ }, confirmDefault: function showRecurringConfirmModal(context) {
18248
+ const labels = context.labels || {};
18249
+ const options = Array.isArray(context.options) ? context.options : [];
18250
+ if (options.length === 0) {
18251
+ return Promise.resolve(null);
18252
+ }
18253
+ if (options.length === 1) {
18254
+ return Promise.resolve(options[0]);
18255
+ }
18256
+ const modalOptions = options.map((decision, index) => ({ value: decision, label: labelForDecision(decision, labels), checked: index === 0 }));
18257
+ return new Promise((resolve) => {
18258
+ scheduler2.modalbox({ text: `<div class="dhx_edit_recurrence_options">
18259
+ ${modalOptions.map((option) => `<label class="dhx_styled_radio">
18260
+ <input type="radio" value="${option.value}" name="option" ${option.checked ? "checked" : ""}>
18261
+ ${option.label}
18262
+ </label>`).join("")}
18263
+ </div>`, type: "recurring_mode", title: labels.title || scheduler2.locale.labels.confirm_recurring, width: "auto", position: "middle", buttons: [{ label: labels.ok || scheduler2.locale.labels.message_ok, value: "ok", css: "rec_ok" }, { label: labels.cancel || scheduler2.locale.labels.message_cancel, value: "cancel" }], callback: function onModalClose(value, event2) {
18264
+ if (value === "cancel") {
18265
+ resolve(null);
18266
+ return;
18267
+ }
18268
+ const box = event2.target.closest(".scheduler_modal_box");
18269
+ const checked = box && box.querySelector("input[type='radio']:checked");
18270
+ const selected = checked ? checked.value : null;
18271
+ if (!selected) {
18272
+ resolve(null);
18273
+ return;
18274
+ }
18275
+ resolve(selected);
18276
+ } });
18277
+ });
18278
+ }, _getDecision: async function _getDecision(context) {
18279
+ const confirmHandler = scheduler2.ext.recurring.confirm;
18280
+ let decision;
18281
+ if (typeof confirmHandler === "function") {
18282
+ decision = await confirmHandler(context);
18283
+ } else {
18284
+ decision = void 0;
18285
+ }
18286
+ if (decision === void 0) {
18287
+ decision = await scheduler2.ext.recurring.confirmDefault(context);
18288
+ }
18289
+ if (decision === null) {
18290
+ return null;
18291
+ }
18292
+ if (context.options && context.options.length > 0) {
18293
+ if (context.options.indexOf(decision) === -1) {
18294
+ console.warning(`[recurring extension] - the custom confirm handler returned a value ("${decision}") which is not in the allowed options list. The allowed options are: [${context.options.join(", ")}]. The operation will be cancelled.`);
18295
+ return null;
18296
+ }
18297
+ }
18298
+ return decision;
18299
+ } };
18300
+ scheduler2.ext.recurring.confirm = scheduler2.ext.recurring.confirmDefault;
18301
+ function labelForDecision(decision, labels) {
18302
+ if (decision === "occurrence") {
18303
+ return labels.occurrence || scheduler2.locale.labels.button_edit_occurrence;
18304
+ }
18305
+ if (decision === "following") {
18306
+ return labels.following || scheduler2.locale.labels.button_edit_occurrence_and_following;
18307
+ }
18308
+ if (decision === "series") {
18309
+ return labels.series || scheduler2.locale.labels.button_edit_series;
18310
+ }
18311
+ return decision;
18312
+ }
18194
18313
  function clearMilliseconds(date) {
18195
18314
  return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0);
18196
18315
  }
@@ -18230,10 +18349,14 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18230
18349
  if (ev.rrule.includes(";UNTIL=")) {
18231
18350
  ev.rrule = ev.rrule.split(";UNTIL=")[0];
18232
18351
  }
18233
- let parsedRRule = rrulestr(`RRULE:${ev.rrule};UNTIL=${toIcalString(setUTCPartsToDate(ev._end_date || ev.end_date))}`, { dtstart: ev.start_date });
18234
- let newRRULE = new RRule(parsedRRule.origOptions).toString().replace("RRULE:", "");
18235
- newRRULE = newRRULE.split("\n")[1];
18236
- ev.rrule = newRRULE;
18352
+ let parsedRRule;
18353
+ if (ev.rrule.includes(";COUNT=")) {
18354
+ parsedRRule = rrulestr(`RRULE:${ev.rrule};UNTIL=${toIcalString(setUTCPartsToDate(ev._shorten_end_date))}`, { dtstart: ev.start_date });
18355
+ delete parsedRRule.origOptions.count;
18356
+ } else {
18357
+ parsedRRule = rrulestr(`RRULE:${ev.rrule};UNTIL=${toIcalString(setUTCPartsToDate(ev._end_date || ev.end_date))}`, { dtstart: ev.start_date });
18358
+ }
18359
+ ev.rrule = new RRule(parsedRRule.origOptions).toString().replace("RRULE:", "").split("\n")[1];
18237
18360
  }
18238
18361
  function updateFollowingRRULE(id, ev) {
18239
18362
  if (!ev) {
@@ -18241,6 +18364,7 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18241
18364
  }
18242
18365
  let rruleStringparts = ev.rrule.split(";");
18243
18366
  let updatedRRULE = [];
18367
+ let interval;
18244
18368
  for (let i = 0; i < rruleStringparts.length; i++) {
18245
18369
  let splited = rruleStringparts[i].split("=");
18246
18370
  let code = splited[0];
@@ -18257,6 +18381,17 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18257
18381
  ev._end_date = ev.end_date;
18258
18382
  }
18259
18383
  }
18384
+ if (code === "INTERVAL") {
18385
+ interval = Number(name);
18386
+ }
18387
+ if (code === "COUNT") {
18388
+ if (ev._shorten_end_date) {
18389
+ const start = scheduler2.date.date_part(new Date(ev._start_date));
18390
+ const end = scheduler2.date.date_part(new Date(ev._shorten_end_date));
18391
+ const days = Math.floor((end - start) / (1e3 * 60 * 60 * 24 * interval)) + 1;
18392
+ name = name - days;
18393
+ }
18394
+ }
18260
18395
  updatedRRULE.push(code);
18261
18396
  updatedRRULE.push("=");
18262
18397
  updatedRRULE.push(name);
@@ -18396,8 +18531,10 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18396
18531
  for (let i in scheduler2._events) {
18397
18532
  let tev = scheduler2._events[i];
18398
18533
  if (tev.recurring_event_id == id || scheduler2._is_virtual_event(tev.id) && tev.id.split("#")[0] == id) {
18399
- tev.text = data.text;
18400
- scheduler2.updateEvent(tev.id);
18534
+ if (tev.start_date.valueOf() >= data.start_date.valueOf()) {
18535
+ tev.text = data.text;
18536
+ scheduler2.updateEvent(tev.id);
18537
+ }
18401
18538
  }
18402
18539
  }
18403
18540
  }
@@ -18515,7 +18652,7 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18515
18652
  scheduler2.attachEvent("onRecurringEventSave", function(id, data, is_new_event) {
18516
18653
  let ev = this.getEvent(id);
18517
18654
  let tempEvent = scheduler2._lame_clone(ev);
18518
- let tempDataRRULE = data.rrule;
18655
+ let tempData = scheduler2._lame_clone(data);
18519
18656
  if (ev && isSeries(ev)) {
18520
18657
  if (!is_new_event && this._isFollowing(id)) {
18521
18658
  if (ev._removeFollowing) {
@@ -18571,10 +18708,8 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18571
18708
  ev._shorten = true;
18572
18709
  updateFollowingRRULEOnSave(ev);
18573
18710
  scheduler2.callEvent("onEventChanged", [ev.id, ev]);
18574
- let followingEv = { ...tempEvent };
18575
- followingEv.text = data.text;
18576
- followingEv.duration = data.duration;
18577
- followingEv.rrule = tempDataRRULE;
18711
+ let followingEv = { ...tempData };
18712
+ followingEv._end_date = tempEvent._end_date;
18578
18713
  followingEv._start_date = null;
18579
18714
  followingEv.id = scheduler2.uid();
18580
18715
  scheduler2.addEvent(followingEv.start_date, followingEv.end_date, followingEv.text, followingEv.id, followingEv);
@@ -18742,43 +18877,28 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18742
18877
  return this.showLightbox_rec(id);
18743
18878
  }
18744
18879
  if (formSetting === "ask") {
18745
- const locale2 = scheduler2.locale;
18746
- showModalbox([{ value: "Occurrence", label: locale2.labels.button_edit_occurrence, checked: true, callback: () => showRequiredLightbox(id, "Occurrence") }, { value: "Following", label: locale2.labels.button_edit_occurrence_and_following, callback: () => showRequiredLightbox(id, "Following") }, { value: "AllEvents", label: locale2.labels.button_edit_series, callback: () => showRequiredLightbox(id, "AllEvents") }]);
18880
+ showRecurringScopeConfirmForLightbox(id, pid);
18747
18881
  }
18748
- };
18749
- function showModalbox(options, callback) {
18750
- const locale = scheduler2.locale;
18751
- const haveChecked = options.find((o) => o.checked);
18752
- if (!haveChecked) {
18753
- options[0].checked = true;
18754
- }
18755
- const callbacks = options.reduce((result, o) => {
18756
- result[o.value] = o.callback;
18757
- return result;
18758
- }, {});
18759
- scheduler2.modalbox({ text: `<div class="dhx_edit_recurrence_options">
18760
- ${options.map((option) => `<label class="dhx_styled_radio">
18761
- <input type="radio" value="${option.value}" name="option" ${option.checked ? "checked" : ""}>
18762
- ${option.label}
18763
- </label>`).join("")}
18764
- </div>`, type: "recurring_mode", title: locale.labels.confirm_recurring, width: "auto", position: "middle", buttons: [{ label: locale.labels.message_ok, value: "ok", css: "rec_ok" }, { label: locale.labels.message_cancel, value: "cancel" }], callback: function(value, e) {
18765
- if (callback) {
18766
- callback(value, e);
18767
- }
18768
- if (value === "cancel") {
18882
+ async function showRecurringScopeConfirmForLightbox(id2, pid2) {
18883
+ const locale2 = scheduler2.locale;
18884
+ const occurrence = scheduler2.getEvent(id2);
18885
+ const seriesEvent = scheduler2.getEvent(pid2);
18886
+ const context = { origin: "lightbox", occurrence, series: seriesEvent, labels: { title: locale2.labels.confirm_recurring, ok: locale2.labels.message_ok, cancel: locale2.labels.message_cancel, occurrence: locale2.labels.button_edit_occurrence, following: locale2.labels.button_edit_occurrence_and_following, series: locale2.labels.button_edit_series }, options: ["occurrence", "following", "series"] };
18887
+ const decision = await scheduler2.ext.recurring._getDecision(context);
18888
+ if (decision === null) {
18769
18889
  return;
18770
18890
  }
18771
- const box = e.target.closest(".scheduler_modal_box");
18772
- const checked = box.querySelector("input[type='radio']:checked");
18773
- let selectedOption;
18774
- if (checked) {
18775
- selectedOption = checked.value;
18891
+ if (decision === "occurrence") {
18892
+ return showRequiredLightbox(id2, "Occurrence");
18776
18893
  }
18777
- if (selectedOption) {
18778
- callbacks[selectedOption]();
18894
+ if (decision === "following") {
18895
+ return showRequiredLightbox(id2, "Following");
18779
18896
  }
18780
- } });
18781
- }
18897
+ if (decision === "series") {
18898
+ return showRequiredLightbox(id2, "AllEvents");
18899
+ }
18900
+ }
18901
+ };
18782
18902
  scheduler2._showRequiredModalBox = function(id, type) {
18783
18903
  let buttons;
18784
18904
  const locale = scheduler2.locale;
@@ -18900,17 +19020,30 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
18900
19020
  }
18901
19021
  }
18902
19022
  };
18903
- const btnAll = { value: "AllEvents", label: locale.labels.button_edit_series, callback: () => handleAllEvents(occurrence) };
18904
- const btnFollowing = { value: "Following", label: locale.labels.button_edit_occurrence_and_following, callback: () => handleFollowing(occurrence) };
18905
- const btnOccurrence = { value: "Occurrence", label: locale.labels.button_edit_occurrence, callback: () => handleOccurrence(occurrence), checked: true };
18906
19023
  if (type === "ask") {
18907
- buttons = [btnOccurrence, btnFollowing, btnAll];
19024
+ buttons = ["occurrence", "following", "series"];
18908
19025
  } else {
18909
- buttons = [btnOccurrence, btnFollowing];
19026
+ buttons = ["occurrence", "following"];
18910
19027
  }
18911
- showModalbox(buttons, (result) => {
18912
- if (result === "cancel") {
19028
+ const context = { origin: "dnd", occurrence, series: event2, labels: { title: locale.labels.confirm_recurring, ok: locale.labels.message_ok, cancel: locale.labels.message_cancel, occurrence: locale.labels.button_edit_occurrence, following: locale.labels.button_edit_occurrence_and_following, series: locale.labels.button_edit_series }, options: buttons };
19029
+ Promise.resolve(scheduler2.ext.recurring._getDecision(context)).then((decision) => {
19030
+ if (!decision) {
18913
19031
  removeTempDraggedEvent();
19032
+ return;
19033
+ }
19034
+ switch (decision) {
19035
+ case "occurrence":
19036
+ handleOccurrence(occurrence);
19037
+ break;
19038
+ case "following":
19039
+ handleFollowing(occurrence);
19040
+ break;
19041
+ case "series":
19042
+ handleAllEvents(occurrence);
19043
+ break;
19044
+ default:
19045
+ removeTempDraggedEvent();
19046
+ return;
18914
19047
  }
18915
19048
  });
18916
19049
  };
@@ -19021,7 +19154,7 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
19021
19154
  copy.start_date = date;
19022
19155
  copy.id = ev.id + "#" + Math.ceil(date.valueOf());
19023
19156
  copy.end_date = new Date(date.valueOf() + eventDuration * 1e3);
19024
- if (copy.end_date.valueOf() < scheduler2._min_date.valueOf()) {
19157
+ if (copy.end_date.valueOf() <= scheduler2._min_date.valueOf()) {
19025
19158
  continue;
19026
19159
  }
19027
19160
  copy.end_date = scheduler2._fix_daylight_saving_date(copy.start_date, copy.end_date, ev, date, copy.end_date);
@@ -20962,73 +21095,103 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
20962
21095
  notImplemented.alert("Units", scheduler2.assert);
20963
21096
  }
20964
21097
  function url(scheduler2) {
20965
- scheduler2._get_url_nav = function() {
20966
- var p = {};
20967
- var data = (document.location.hash || "").replace("#", "").split(",");
20968
- for (var i = 0; i < data.length; i++) {
20969
- var s = data[i].split("=");
20970
- if (s.length == 2)
20971
- p[s[0]] = s[1];
21098
+ function parseHashParameters() {
21099
+ const parameters = {};
21100
+ const raw = (document.location.hash || "").replace("#", "");
21101
+ if (!raw) {
21102
+ return parameters;
21103
+ }
21104
+ const pairs = raw.split(",");
21105
+ for (let i = 0; i < pairs.length; i++) {
21106
+ const parts = pairs[i].split("=");
21107
+ if (parts.length === 2) {
21108
+ parameters[parts[0]] = parts[1];
21109
+ }
20972
21110
  }
20973
- return p;
21111
+ return parameters;
21112
+ }
21113
+ scheduler2._get_url_nav = function() {
21114
+ return parseHashParameters();
20974
21115
  };
20975
- scheduler2.attachEvent("onTemplatesReady", function() {
20976
- var first = true;
20977
- var s2d = scheduler2.date.str_to_date("%Y-%m-%d");
20978
- var d2s = scheduler2.date.date_to_str("%Y-%m-%d");
20979
- var select_event = scheduler2._get_url_nav().event || null;
20980
- scheduler2.attachEvent("onAfterEventDisplay", function(ev) {
20981
- select_event = null;
20982
- return true;
20983
- });
20984
- scheduler2.attachEvent("onBeforeViewChange", function(om, od, m, d) {
20985
- if (first) {
20986
- first = false;
20987
- var p = scheduler2._get_url_nav();
20988
- if (p.event) {
20989
- try {
20990
- if (scheduler2.getEvent(p.event)) {
20991
- setTimeout(function() {
20992
- showEvent(p.event);
20993
- });
20994
- return false;
20995
- } else {
20996
- var handler = scheduler2.attachEvent("onXLE", function() {
20997
- setTimeout(function() {
20998
- showEvent(p.event);
20999
- });
21000
- scheduler2.detachEvent(handler);
21001
- });
21002
- }
21003
- } catch (e) {
21004
- }
21005
- }
21006
- if (p.date || p.mode) {
21007
- try {
21008
- this.setCurrentView(p.date ? s2d(p.date) : null, p.mode || null);
21009
- } catch (e) {
21010
- this.setCurrentView(p.date ? s2d(p.date) : null, m);
21011
- }
21012
- return false;
21116
+ const dateToString = scheduler2.date.date_to_str("%Y-%m-%d");
21117
+ const originalGetInitialState = scheduler2._getInitialState;
21118
+ scheduler2._getInitialState = function(provided) {
21119
+ const baseState = originalGetInitialState.call(this, provided);
21120
+ const url2 = scheduler2._get_url_nav ? scheduler2._get_url_nav() : null;
21121
+ if (!url2) {
21122
+ return baseState;
21123
+ }
21124
+ const nextState = { date: baseState.date, mode: baseState.mode };
21125
+ if (url2.date) {
21126
+ const stringToDate = scheduler2.date.str_to_date("%Y-%m-%d");
21127
+ try {
21128
+ const parsed = stringToDate(url2.date);
21129
+ if (!isNaN(+parsed)) {
21130
+ nextState.date = parsed;
21013
21131
  }
21132
+ } catch (e) {
21014
21133
  }
21015
- var values = ["date=" + d2s(d || od), "mode=" + (m || om)];
21016
- if (select_event) {
21017
- values.push("event=" + select_event);
21134
+ }
21135
+ if (url2.mode) {
21136
+ try {
21137
+ if (this.isViewExists(url2.mode)) {
21138
+ nextState.mode = url2.mode;
21139
+ }
21140
+ } catch (e) {
21018
21141
  }
21019
- var text = "#" + values.join(",");
21020
- document.location.hash = text;
21021
- return true;
21022
- });
21023
- function showEvent(e) {
21142
+ }
21143
+ return nextState;
21144
+ };
21145
+ function updateHashFromState(optionalSelectedEventId) {
21146
+ const currentDate = scheduler2._date || scheduler2.getState().date;
21147
+ const currentMode = scheduler2.getState().mode;
21148
+ const values = ["date=" + dateToString(currentDate), "mode=" + currentMode];
21149
+ if (optionalSelectedEventId) {
21150
+ values.push("event=" + optionalSelectedEventId);
21151
+ }
21152
+ document.location.hash = "#" + values.join(",");
21153
+ }
21154
+ function showEventWhenPossible(eventId) {
21155
+ if (!eventId) {
21156
+ return;
21157
+ }
21158
+ const tryShowNow = function() {
21024
21159
  if (scheduler2.$destroyed) {
21025
- return true;
21160
+ return;
21026
21161
  }
21027
- select_event = e;
21028
- if (scheduler2.getEvent(e)) {
21029
- scheduler2.showEvent(e);
21162
+ if (scheduler2.getEvent(eventId)) {
21163
+ scheduler2.showEvent(eventId);
21164
+ return true;
21030
21165
  }
21166
+ return false;
21167
+ };
21168
+ if (tryShowNow()) {
21169
+ return;
21031
21170
  }
21171
+ const onLoadedId = scheduler2.attachEvent("onParse", function() {
21172
+ tryShowNow();
21173
+ scheduler2.detachEvent(onLoadedId);
21174
+ return true;
21175
+ });
21176
+ }
21177
+ let pendingSelectedEventId = null;
21178
+ scheduler2.attachEvent("onSchedulerReady", function() {
21179
+ const initialState = scheduler2._get_url_nav();
21180
+ if (initialState.event) {
21181
+ pendingSelectedEventId = initialState.event;
21182
+ showEventWhenPossible(initialState.event);
21183
+ }
21184
+ updateHashFromState(pendingSelectedEventId);
21185
+ return true;
21186
+ });
21187
+ scheduler2.attachEvent("onAfterEventDisplay", function() {
21188
+ pendingSelectedEventId = null;
21189
+ updateHashFromState(null);
21190
+ return true;
21191
+ });
21192
+ scheduler2.attachEvent("onViewChange", function() {
21193
+ updateHashFromState(pendingSelectedEventId);
21194
+ return true;
21032
21195
  });
21033
21196
  }
21034
21197
  function week_agenda_restricted(scheduler2) {
@@ -21285,6 +21448,9 @@ To use dhtmlxScheduler in non-GPL projects (and get Pro version of the product),
21285
21448
  var locateEvent = scheduler2._locate_event;
21286
21449
  scheduler2._locate_event = function(node) {
21287
21450
  var id = locateEvent.apply(scheduler2, arguments);
21451
+ if (!isYearMode()) {
21452
+ return id;
21453
+ }
21288
21454
  if (!id) {
21289
21455
  var date = getCellDate(node);
21290
21456
  if (!date)