utiller 1.0.394 → 1.0.396
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.
- package/lib/utiller/index.js +224 -28
- package/package.json +1 -1
- package/template/sample.package.json +1 -1
package/lib/utiller/index.js
CHANGED
|
@@ -699,6 +699,156 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
699
699
|
var factor = Math.pow(10, precision);
|
|
700
700
|
return Math.ceil(a * b * factor) / factor;
|
|
701
701
|
});
|
|
702
|
+
/**
|
|
703
|
+
* 根據課程資訊生成 Google Calendar 的行事曆新增連結。
|
|
704
|
+
* @param {object} event - 行程物件。
|
|
705
|
+
* @param {string} event.title - 行程主題 (e.g., '吉他課')。
|
|
706
|
+
* @param {string} event.startDate - 開始日期 (e.g., '2025/11/10')。
|
|
707
|
+
* @param {string} event.startTime - 開始時間 (e.g., '14:00')。
|
|
708
|
+
* @param {string} event.endDate - 結束日期 (e.g., '2025/11/10')。
|
|
709
|
+
* @param {string} event.endTime - 結束時間 (e.g., '16:00')。
|
|
710
|
+
* @param {string} event.location - 地點/地址 (e.g., '台北市大安區信義路三段 162 號')。
|
|
711
|
+
* @param {string} event.details - 描述/備註 (e.g., '請攜帶吉他,並預習第5頁。')。
|
|
712
|
+
* @returns {string} Google Calendar 的新增連結。
|
|
713
|
+
*/
|
|
714
|
+
(0, _defineProperty2["default"])(this, "generateGoogleCalendarLink", function (_ref4) {
|
|
715
|
+
var title = _ref4.title,
|
|
716
|
+
startDate = _ref4.startDate,
|
|
717
|
+
startTime = _ref4.startTime,
|
|
718
|
+
endDate = _ref4.endDate,
|
|
719
|
+
endTime = _ref4.endTime,
|
|
720
|
+
location = _ref4.location,
|
|
721
|
+
details = _ref4.details;
|
|
722
|
+
// 使用 moment 結合日期和時間,並格式化為 YYYYMMDDTHHMMSS
|
|
723
|
+
// 注意: 日期格式已從 YYYY-MM-DD 變更為 YYYY/MM/DD
|
|
724
|
+
var startDateTime = (0, _momentTimezone["default"])("".concat(startDate, " ").concat(startTime), "YYYY/MM/DD HH:mm").format("YYYYMMDDTHHMMSS");
|
|
725
|
+
var endDateTime = (0, _momentTimezone["default"])("".concat(endDate, " ").concat(endTime), "YYYY/MM/DD HH:mm").format("YYYYMMDDTHHMMSS");
|
|
726
|
+
|
|
727
|
+
// 進行 URL 編碼
|
|
728
|
+
var encodedTitle = encodeURIComponent(title);
|
|
729
|
+
var encodedLocation = encodeURIComponent(location);
|
|
730
|
+
var encodedDetails = encodeURIComponent(details);
|
|
731
|
+
|
|
732
|
+
// --- Google Calendar 連結 (使用 https://calendar.google.com/calendar/r/eventedit) ---
|
|
733
|
+
var googleCalendarLink = "https://calendar.google.com/calendar/r/eventedit?" + "text=".concat(encodedTitle) + "&dates=".concat(startDateTime, "/").concat(endDateTime) + "&details=".concat(encodedDetails) + "&location=".concat(encodedLocation) + "&ctz=Asia/Taipei" + // 台灣時區
|
|
734
|
+
"&trp=true"; // 設為忙碌 (Busy)
|
|
735
|
+
|
|
736
|
+
return googleCalendarLink;
|
|
737
|
+
});
|
|
738
|
+
/**
|
|
739
|
+
* 根據課程資訊生成 TimeTree 的行事曆新增連結。
|
|
740
|
+
* (此函式不需要 moment.js,但維持其結構)
|
|
741
|
+
* @param {object} event - 行程物件。
|
|
742
|
+
* @param {string} event.title - 行程主題 (e.g., '吉他課')。
|
|
743
|
+
* @param {string} event.startDate - 開始日期 (YYYY/MM/DD, e.g., '2025/11/10')。
|
|
744
|
+
* @param {string} event.startTime - 開始時間 (HH:MM, e.g., '14:00')。
|
|
745
|
+
* @param {string} event.endDate - 結束日期 (YYYY/MM/DD, e.g., '2025/11/10')。
|
|
746
|
+
* @param {string} event.endTime - 結束時間 (HH:MM, e.g., '16:00')。
|
|
747
|
+
* @param {string} [event.location=''] - 地點/地址 (選填)。
|
|
748
|
+
* @param {string} [event.memo=''] - 描述/備註 (選填,TimeTree 使用 'memo')。
|
|
749
|
+
* @returns {string} TimeTree 行事曆新增連結。
|
|
750
|
+
*/
|
|
751
|
+
(0, _defineProperty2["default"])(this, "generateTimeTreeLink", function (_ref5) {
|
|
752
|
+
var title = _ref5.title,
|
|
753
|
+
startDate = _ref5.startDate,
|
|
754
|
+
startTime = _ref5.startTime,
|
|
755
|
+
endDate = _ref5.endDate,
|
|
756
|
+
endTime = _ref5.endTime,
|
|
757
|
+
_ref5$location = _ref5.location,
|
|
758
|
+
location = _ref5$location === void 0 ? "" : _ref5$location,
|
|
759
|
+
_ref5$memo = _ref5.memo,
|
|
760
|
+
memo = _ref5$memo === void 0 ? "" : _ref5$memo;
|
|
761
|
+
// 進行 URL 編碼
|
|
762
|
+
var encodedTitle = encodeURIComponent(title);
|
|
763
|
+
var encodedLocation = encodeURIComponent(location);
|
|
764
|
+
var encodedMemo = encodeURIComponent(memo);
|
|
765
|
+
|
|
766
|
+
// TimeTree 網址結構相對簡單,直接使用模板字串
|
|
767
|
+
// 注意: TimeTree 連結直接使用 YYYY-MM-DD 格式,故此函式仍傳遞原始 YYYY/MM/DD 字串
|
|
768
|
+
// 在實際應用中,如果 TimeTree 不接受 YYYY/MM/DD,則需要在傳入前轉換 (但 TimeTree 連結通常較為寬鬆)
|
|
769
|
+
var timeTreeLink = "https://timetreeapp.com/plans/new?" + "title=".concat(encodedTitle) + "&start_date=".concat(startDate.replace(/\//g, "-")) + // 將 YYYY/MM/DD 轉換回 YYYY-MM-DD
|
|
770
|
+
"&start_time=".concat(startTime) + "&end_date=".concat(endDate.replace(/\//g, "-")) + // 將 YYYY/MM/DD 轉換回 YYYY-MM-DD
|
|
771
|
+
"&end_time=".concat(endTime) + "".concat(location ? "&location=".concat(encodedLocation) : "") + "".concat(memo ? "&memo=".concat(encodedMemo) : "");
|
|
772
|
+
return timeTreeLink;
|
|
773
|
+
});
|
|
774
|
+
/**
|
|
775
|
+
* 根據課程資訊生成 iCalendar (.ics) 檔案內容。
|
|
776
|
+
* @param {object} event - 行程物件。
|
|
777
|
+
* @param {string} event.title - 行程主題。
|
|
778
|
+
* @param {string} event.startDate - 開始日期 (YYYY/MM/DD)。
|
|
779
|
+
* @param {string} event.startTime - 開始時間 (HH:MM)。
|
|
780
|
+
* @param {string} event.endDate - 結束日期 (YYYY/MM/DD)。
|
|
781
|
+
* @param {string} event.endTime - 結束時間 (HH:MM)。
|
|
782
|
+
* @param {string} event.location - 地點/地址。
|
|
783
|
+
* @param {string} event.details - 描述/備註。
|
|
784
|
+
* @returns {string} 遵循 iCalendar 規範的字串內容。
|
|
785
|
+
*/
|
|
786
|
+
(0, _defineProperty2["default"])(this, "generateIcsContent", function (_ref6) {
|
|
787
|
+
var title = _ref6.title,
|
|
788
|
+
startDate = _ref6.startDate,
|
|
789
|
+
startTime = _ref6.startTime,
|
|
790
|
+
endDate = _ref6.endDate,
|
|
791
|
+
endTime = _ref6.endTime,
|
|
792
|
+
location = _ref6.location,
|
|
793
|
+
details = _ref6.details;
|
|
794
|
+
// 使用 moment-timezone 結合日期和時間,並格式化為 YYYYMMDDTHHMMSS
|
|
795
|
+
// 注意: 日期格式已從 YYYY-MM-DD 變更為 YYYY/MM/DD
|
|
796
|
+
var startDateTime = _momentTimezone["default"].tz("".concat(startDate, " ").concat(startTime), "YYYY/MM/DD HH:mm", "Asia/Taipei").format("YYYYMMDDTHHMMSS");
|
|
797
|
+
var endDateTime = _momentTimezone["default"].tz("".concat(endDate, " ").concat(endTime), "YYYY/MM/DD HH:mm", "Asia/Taipei").format("YYYYMMDDTHHMMSS");
|
|
798
|
+
|
|
799
|
+
// DTSTAMP 必須是 UTC 格式 (以 Z 結尾)
|
|
800
|
+
var stamp = (0, _momentTimezone["default"])().utc().format("YYYYMMDDTHHMMSS") + "Z";
|
|
801
|
+
|
|
802
|
+
// 唯一識別碼 (UID)
|
|
803
|
+
var uid = Date.now().toString(36) + Math.random().toString(36).substring(2, 5) + "@gemini-app.com";
|
|
804
|
+
|
|
805
|
+
// 確保內容中的特殊字元(如換行、逗號、分號)被正確逸出
|
|
806
|
+
var escapeIcs = function escapeIcs(text) {
|
|
807
|
+
return text.replace(/\\/g, "\\\\").replace(/,/g, "\\,").replace(/;/g, "\\;").replace(/\n/g, "\\n");
|
|
808
|
+
};
|
|
809
|
+
var escapedTitle = escapeIcs(title);
|
|
810
|
+
var escapedLocation = escapeIcs(location);
|
|
811
|
+
var escapedDetails = escapeIcs(details);
|
|
812
|
+
|
|
813
|
+
// 產生 ICS 內容
|
|
814
|
+
var icsContent = "BEGIN:VCALENDAR\n" + "VERSION:2.0\n" + "PRODID:-//Gemini AI//NONSGML v1.0//EN\n" + "BEGIN:VEVENT\n" + "UID:".concat(uid, "\n") + "DTSTAMP:".concat(stamp, "\n") + // 採用 Asia/Taipei 時區
|
|
815
|
+
"DTSTART;TZID=Asia/Taipei:".concat(startDateTime, "\n") + "DTEND;TZID=Asia/Taipei:".concat(endDateTime, "\n") + "SUMMARY:".concat(escapedTitle, "\n") + "LOCATION:".concat(escapedLocation, "\n") + "DESCRIPTION:".concat(escapedDetails, "\n") + "END:VEVENT\n" + "END:VCALENDAR";
|
|
816
|
+
|
|
817
|
+
// 確保使用 CRLF (\r\n) 換行符號,這是 ICS 規範的要求
|
|
818
|
+
return icsContent.replace(/\n/g, "\r\n");
|
|
819
|
+
});
|
|
820
|
+
/**
|
|
821
|
+
* 根據行程物件,生成所有主要的行事曆新增連結 (Google, TimeTree, iCalendar/ICS)。
|
|
822
|
+
* @param {object} event - 行程物件。
|
|
823
|
+
* @param {string} event.title - 行程主題。
|
|
824
|
+
* @param {string} event.startDate - 開始日期 (YYYY/MM/DD)。
|
|
825
|
+
* @param {string} event.startTime - 開始時間 (HH:MM)。
|
|
826
|
+
* @param {string} event.endDate - 結束日期 (YYYY/MM/DD)。
|
|
827
|
+
* @param {string} event.endTime - 結束時間 (HH:MM)。
|
|
828
|
+
* @param {string} event.location - 地點/地址。
|
|
829
|
+
* @param {string} event.details - 描述/備註。
|
|
830
|
+
* @returns {{google: string, timeTree: string, ics: string}} 包含所有連結的物件。
|
|
831
|
+
*/
|
|
832
|
+
(0, _defineProperty2["default"])(this, "generateAllCalendarLinks", function (event) {
|
|
833
|
+
// 1. Google Calendar Link
|
|
834
|
+
var googleLink = _this.generateGoogleCalendarLink(event);
|
|
835
|
+
|
|
836
|
+
// 2. TimeTree Link
|
|
837
|
+
// TimeTree 使用 'memo' 參數,因此將 event.details 映射到 memo
|
|
838
|
+
var timeTreeEvent = _objectSpread(_objectSpread({}, event), {}, {
|
|
839
|
+
memo: event.details
|
|
840
|
+
});
|
|
841
|
+
var timeTreeLink = _this.generateTimeTreeLink(timeTreeEvent);
|
|
842
|
+
|
|
843
|
+
// 3. iCalendar (ICS) Link (Data URI 格式,用於 iOS/Outlook/下載)
|
|
844
|
+
var icsContent = _this.generateIcsContent(event);
|
|
845
|
+
var icsLink = "data:text/calendar;charset=utf8,".concat(encodeURIComponent(icsContent));
|
|
846
|
+
return {
|
|
847
|
+
google: googleLink,
|
|
848
|
+
timeTree: timeTreeLink,
|
|
849
|
+
ics: icsLink // iCalendar 連結
|
|
850
|
+
};
|
|
851
|
+
});
|
|
702
852
|
/**
|
|
703
853
|
* @param {Array<Object>} current - 要更新的目標陣列
|
|
704
854
|
* @param {Array<Object>} reference - 用來替換屬性的參照陣列
|
|
@@ -3987,9 +4137,9 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
3987
4137
|
key: "mutateRemoveKeys",
|
|
3988
4138
|
value: function mutateRemoveKeys(array, keysToRemove) {
|
|
3989
4139
|
_lodash["default"].forEach(array, function (obj, index) {
|
|
3990
|
-
var filtered = Object.fromEntries(Object.entries(obj).filter(function (
|
|
3991
|
-
var
|
|
3992
|
-
key =
|
|
4140
|
+
var filtered = Object.fromEntries(Object.entries(obj).filter(function (_ref8) {
|
|
4141
|
+
var _ref9 = (0, _slicedToArray2["default"])(_ref8, 1),
|
|
4142
|
+
key = _ref9[0];
|
|
3993
4143
|
return !keysToRemove.includes(key);
|
|
3994
4144
|
}));
|
|
3995
4145
|
// 原地替換每個 object 的 key
|
|
@@ -4016,9 +4166,9 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4016
4166
|
key: "removeKeysFromArrayObjects",
|
|
4017
4167
|
value: function removeKeysFromArrayObjects(array, keysToRemove) {
|
|
4018
4168
|
return _lodash["default"].map(array, function (obj) {
|
|
4019
|
-
return Object.fromEntries(Object.entries(obj).filter(function (
|
|
4020
|
-
var
|
|
4021
|
-
key =
|
|
4169
|
+
return Object.fromEntries(Object.entries(obj).filter(function (_ref10) {
|
|
4170
|
+
var _ref11 = (0, _slicedToArray2["default"])(_ref10, 1),
|
|
4171
|
+
key = _ref11[0];
|
|
4022
4172
|
return !keysToRemove.includes(key);
|
|
4023
4173
|
}));
|
|
4024
4174
|
});
|
|
@@ -4077,10 +4227,10 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4077
4227
|
var predict = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (attr) {
|
|
4078
4228
|
return attr.checked !== true;
|
|
4079
4229
|
};
|
|
4080
|
-
return _lodash["default"].fromPairs(_lodash["default"].toPairs(obj).filter(function (
|
|
4081
|
-
var
|
|
4082
|
-
_ =
|
|
4083
|
-
value =
|
|
4230
|
+
return _lodash["default"].fromPairs(_lodash["default"].toPairs(obj).filter(function (_ref12) {
|
|
4231
|
+
var _ref13 = (0, _slicedToArray2["default"])(_ref12, 2),
|
|
4232
|
+
_ = _ref13[0],
|
|
4233
|
+
value = _ref13[1];
|
|
4084
4234
|
return predict(value);
|
|
4085
4235
|
}));
|
|
4086
4236
|
}
|
|
@@ -4243,6 +4393,11 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4243
4393
|
value: function isFirestoreAutoId(id) {
|
|
4244
4394
|
return _lodash["default"].isString(id) && id.length === 20 && /^[A-Za-z0-9]{20}$/.test(id);
|
|
4245
4395
|
}
|
|
4396
|
+
}, {
|
|
4397
|
+
key: "getAutoIdOfFirestore",
|
|
4398
|
+
value: function getAutoIdOfFirestore() {
|
|
4399
|
+
return this.getRandomHashV2(20);
|
|
4400
|
+
}
|
|
4246
4401
|
}, {
|
|
4247
4402
|
key: "getStringOfConvertTimeRange",
|
|
4248
4403
|
value:
|
|
@@ -4271,9 +4426,9 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4271
4426
|
}, {
|
|
4272
4427
|
key: "getTSOfSpecificDate",
|
|
4273
4428
|
value: function getTSOfSpecificDate(dateStr) {
|
|
4274
|
-
var
|
|
4275
|
-
|
|
4276
|
-
end =
|
|
4429
|
+
var _ref14 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
4430
|
+
_ref14$end = _ref14.end,
|
|
4431
|
+
end = _ref14$end === void 0 ? false : _ref14$end;
|
|
4277
4432
|
return Number((0, _lodash["default"])(dateStr).thru(function (str) {
|
|
4278
4433
|
return (0, _momentTimezone["default"])(str.split("(")[0], "YYYY/MM/DD");
|
|
4279
4434
|
}).thru(function (m) {
|
|
@@ -4389,15 +4544,14 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4389
4544
|
* 🧩 範例:
|
|
4390
4545
|
* ```js
|
|
4391
4546
|
* const array = [
|
|
4392
|
-
* { a: { c:
|
|
4393
|
-
* { b: { d:
|
|
4394
|
-
* { a: { f: 3 } }
|
|
4547
|
+
* { a: { b: { c: 2 } } },
|
|
4548
|
+
* { b: { d: { g: 1 } }, a: { b: { y: 1 }, h: { e: 1 } } }
|
|
4395
4549
|
* ];
|
|
4396
4550
|
*
|
|
4397
4551
|
* ArrayHelper.mergeArrayByKey(array);
|
|
4398
4552
|
*
|
|
4399
4553
|
* console.log(array);
|
|
4400
|
-
* // 👉 [ { a: { c: 1,
|
|
4554
|
+
* // 👉 [ { a: { b: { c: 2, y: 1 }, h: { e: 1 } } }, { b: { d: { g: 1 } } } ]
|
|
4401
4555
|
* ```
|
|
4402
4556
|
*
|
|
4403
4557
|
* @param {Array<Object>} array - 需被合併的物件陣列
|
|
@@ -4406,21 +4560,65 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4406
4560
|
function mergeArrayByKey(array) {
|
|
4407
4561
|
if (!Array.isArray(array)) return array;
|
|
4408
4562
|
|
|
4409
|
-
//
|
|
4410
|
-
var
|
|
4563
|
+
// 收集所有 key 的合併結果
|
|
4564
|
+
var resultMap = {};
|
|
4565
|
+
var _iterator20 = _createForOfIteratorHelper(array),
|
|
4566
|
+
_step20;
|
|
4567
|
+
try {
|
|
4568
|
+
for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
|
|
4569
|
+
var obj = _step20.value;
|
|
4570
|
+
if (!_lodash["default"].isPlainObject(obj)) continue;
|
|
4571
|
+
for (var _i16 = 0, _Object$entries = Object.entries(obj); _i16 < _Object$entries.length; _i16++) {
|
|
4572
|
+
var _Object$entries$_i = (0, _slicedToArray2["default"])(_Object$entries[_i16], 2),
|
|
4573
|
+
_key45 = _Object$entries$_i[0],
|
|
4574
|
+
value = _Object$entries$_i[1];
|
|
4575
|
+
if (!resultMap[_key45]) {
|
|
4576
|
+
resultMap[_key45] = _lodash["default"].cloneDeep(value);
|
|
4577
|
+
} else {
|
|
4578
|
+
_lodash["default"].merge(resultMap[_key45], value);
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4411
4582
|
|
|
4412
|
-
|
|
4583
|
+
// 清空原陣列(mutate)
|
|
4584
|
+
} catch (err) {
|
|
4585
|
+
_iterator20.e(err);
|
|
4586
|
+
} finally {
|
|
4587
|
+
_iterator20.f();
|
|
4588
|
+
}
|
|
4413
4589
|
array.length = 0;
|
|
4414
4590
|
|
|
4415
|
-
//
|
|
4416
|
-
Object.entries(
|
|
4417
|
-
var
|
|
4418
|
-
key =
|
|
4419
|
-
value =
|
|
4591
|
+
// 重建結構
|
|
4592
|
+
Object.entries(resultMap).forEach(function (_ref15) {
|
|
4593
|
+
var _ref16 = (0, _slicedToArray2["default"])(_ref15, 2),
|
|
4594
|
+
key = _ref16[0],
|
|
4595
|
+
value = _ref16[1];
|
|
4420
4596
|
array.push((0, _defineProperty2["default"])({}, key, value));
|
|
4421
4597
|
});
|
|
4422
4598
|
return array;
|
|
4423
4599
|
}
|
|
4600
|
+
}, {
|
|
4601
|
+
key: "getFilteredHeraPeriods",
|
|
4602
|
+
value:
|
|
4603
|
+
// 範例用法 (請確保 moment.js 已載入)
|
|
4604
|
+
/*
|
|
4605
|
+
const myEvent = {
|
|
4606
|
+
title: '吉他課 (Moment.js)',
|
|
4607
|
+
startDate: '2026/01/15', // 使用新的 YYYY/MM/DD 格式
|
|
4608
|
+
startTime: '10:30',
|
|
4609
|
+
endDate: '2026/01/15',
|
|
4610
|
+
endTime: '12:00',
|
|
4611
|
+
location: '新北市板橋區縣民大道一段 1 號',
|
|
4612
|
+
details: 'Moment.js 測試:請記得帶譜架。'
|
|
4613
|
+
};
|
|
4614
|
+
if (typeof moment !== 'undefined') {
|
|
4615
|
+
const links = generateAllCalendarLinks(myEvent);
|
|
4616
|
+
console.log('--- 使用 Moment.js 生成的連結 ---');
|
|
4617
|
+
console.log(links);
|
|
4618
|
+
} else {
|
|
4619
|
+
console.warn('moment.js 函式庫未載入,無法執行。');
|
|
4620
|
+
}
|
|
4621
|
+
*/
|
|
4424
4622
|
|
|
4425
4623
|
/** ============== 排課系統公式 開始 ============== */
|
|
4426
4624
|
|
|
@@ -4439,9 +4637,7 @@ var Utiller = /*#__PURE__*/function () {
|
|
|
4439
4637
|
* 2. idOfBooze和idOfVariant為PK, 重複的只保留一組
|
|
4440
4638
|
* 3. 回傳filter array,(反查出哪些課程重複會用到其他資訊)
|
|
4441
4639
|
* */
|
|
4442
|
-
|
|
4443
|
-
key: "getFilteredHeraPeriods",
|
|
4444
|
-
value: function getFilteredHeraPeriods(arr, idOfCurrentBooze) {
|
|
4640
|
+
function getFilteredHeraPeriods(arr, idOfCurrentBooze) {
|
|
4445
4641
|
return _lodash["default"].chain(arr)
|
|
4446
4642
|
// 1️⃣ 刪掉 idOfBooze 等於目標值的項目
|
|
4447
4643
|
.filter(function (item) {
|
package/package.json
CHANGED