utiller 1.0.395 → 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.
@@ -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 (_ref5) {
3991
- var _ref6 = (0, _slicedToArray2["default"])(_ref5, 1),
3992
- key = _ref6[0];
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 (_ref7) {
4020
- var _ref8 = (0, _slicedToArray2["default"])(_ref7, 1),
4021
- key = _ref8[0];
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 (_ref9) {
4081
- var _ref10 = (0, _slicedToArray2["default"])(_ref9, 2),
4082
- _ = _ref10[0],
4083
- value = _ref10[1];
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
  }
@@ -4276,9 +4426,9 @@ var Utiller = /*#__PURE__*/function () {
4276
4426
  }, {
4277
4427
  key: "getTSOfSpecificDate",
4278
4428
  value: function getTSOfSpecificDate(dateStr) {
4279
- var _ref11 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
4280
- _ref11$end = _ref11.end,
4281
- end = _ref11$end === void 0 ? false : _ref11$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;
4282
4432
  return Number((0, _lodash["default"])(dateStr).thru(function (str) {
4283
4433
  return (0, _momentTimezone["default"])(str.split("(")[0], "YYYY/MM/DD");
4284
4434
  }).thru(function (m) {
@@ -4439,14 +4589,36 @@ var Utiller = /*#__PURE__*/function () {
4439
4589
  array.length = 0;
4440
4590
 
4441
4591
  // 重建結構
4442
- Object.entries(resultMap).forEach(function (_ref12) {
4443
- var _ref13 = (0, _slicedToArray2["default"])(_ref12, 2),
4444
- key = _ref13[0],
4445
- value = _ref13[1];
4592
+ Object.entries(resultMap).forEach(function (_ref15) {
4593
+ var _ref16 = (0, _slicedToArray2["default"])(_ref15, 2),
4594
+ key = _ref16[0],
4595
+ value = _ref16[1];
4446
4596
  array.push((0, _defineProperty2["default"])({}, key, value));
4447
4597
  });
4448
4598
  return array;
4449
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
+ */
4450
4622
 
4451
4623
  /** ============== 排課系統公式 開始 ============== */
4452
4624
 
@@ -4465,9 +4637,7 @@ var Utiller = /*#__PURE__*/function () {
4465
4637
  * 2. idOfBooze和idOfVariant為PK, 重複的只保留一組
4466
4638
  * 3. 回傳filter array,(反查出哪些課程重複會用到其他資訊)
4467
4639
  * */
4468
- }, {
4469
- key: "getFilteredHeraPeriods",
4470
- value: function getFilteredHeraPeriods(arr, idOfCurrentBooze) {
4640
+ function getFilteredHeraPeriods(arr, idOfCurrentBooze) {
4471
4641
  return _lodash["default"].chain(arr)
4472
4642
  // 1️⃣ 刪掉 idOfBooze 等於目標值的項目
4473
4643
  .filter(function (item) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utiller",
3
- "version": "1.0.395",
3
+ "version": "1.0.396",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,7 +11,7 @@
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
13
  "configerer": "^1.0.23",
14
- "utiller": "^1.0.394",
14
+ "utiller": "^1.0.395",
15
15
  "linepayer": "^1.0.15",
16
16
  "databazer": "^1.0.17",
17
17
  "lodash": "^4.17.20",