mysticals 0.8.0 → 0.9.0

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.
Files changed (2) hide show
  1. package/dist/cli.js +51 -7
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { createElement } from "react";
5
5
  import { render } from "ink";
6
6
 
7
7
  // package.json
8
- var version = "0.8.0";
8
+ var version = "0.9.0";
9
9
 
10
10
  // src/client.ts
11
11
  import { spawn } from "child_process";
@@ -277,6 +277,8 @@ var en = {
277
277
  "notify.changed": "Event changed",
278
278
  "notify.cancelled": "Event cancelled",
279
279
  "notify.many": { one: "{n} calendar update", other: "{n} calendar updates" },
280
+ "reminder.in": "in {n} min",
281
+ "reminder.now": "starting now",
280
282
  // ---- desktop: menus, dialogs, update ----
281
283
  "menu.file": "File",
282
284
  "menu.newEvent": "New Event",
@@ -345,12 +347,19 @@ var en = {
345
347
  "settings.tab.language": "Language",
346
348
  "settings.tab.sync": "Sync",
347
349
  "settings.tab.privacy": "Privacy",
350
+ "settings.tab.notifications": "Notifications",
351
+ "settings.reminders.label": "Remind me before events",
352
+ "settings.reminders.off": "Off",
353
+ "settings.reminders.min": "{n} min before",
354
+ "settings.reminders.note": "A notification pops up before each event while Mysticals is open, with a button to join the call when the event has a link. Declined, all-day and hidden-calendar events stay silent.",
348
355
  "settings.noAccounts": "No accounts yet.",
349
356
  "settings.addAccount": "Add account\u2026",
350
357
  "settings.theme": "Theme",
351
358
  "theme.amber": "Phosphor amber",
352
359
  "theme.mono": "Black & White",
353
360
  "theme.toon": "Cartoon",
361
+ "theme.cloud": "Cloud",
362
+ "theme.light": "Light",
354
363
  "settings.language.auto": "Automatic",
355
364
  "settings.language.system": "system: {name}",
356
365
  "settings.sync.note": "Accounts sync automatically every 2 minutes and when the app regains focus.",
@@ -365,6 +374,7 @@ var en = {
365
374
  "settings.privacy.note": "Mysticals sends two anonymous events: when the app is first installed and when an account is added (provider only). Never emails, server addresses, calendars or events.",
366
375
  "settings.privacy.policy": "Privacy policy",
367
376
  "settings.privacy.share": "Share anonymous usage stats",
377
+ "settings.donate": "Support on Ko-fi",
368
378
  "settings.account.labelFor": "Label for {email}",
369
379
  "settings.account.colourFor": "Colour for {email}",
370
380
  "settings.account.remove": "Remove",
@@ -610,6 +620,8 @@ var uk = {
610
620
  "notify.changed": "\u041F\u043E\u0434\u0456\u044E \u0437\u043C\u0456\u043D\u0435\u043D\u043E",
611
621
  "notify.cancelled": "\u041F\u043E\u0434\u0456\u044E \u0441\u043A\u0430\u0441\u043E\u0432\u0430\u043D\u043E",
612
622
  "notify.many": { one: "{n} \u0437\u043C\u0456\u043D\u0430 \u0432 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0456", few: "{n} \u0437\u043C\u0456\u043D\u0438 \u0432 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0456", many: "{n} \u0437\u043C\u0456\u043D \u0443 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0456", other: "{n} \u0437\u043C\u0456\u043D\u0438 \u0432 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0456" },
623
+ "reminder.in": "\u0447\u0435\u0440\u0435\u0437 {n} \u0445\u0432",
624
+ "reminder.now": "\u043F\u043E\u0447\u0438\u043D\u0430\u0454\u0442\u044C\u0441\u044F \u0437\u0430\u0440\u0430\u0437",
613
625
  // ---- desktop: menus, dialogs, update ----
614
626
  "menu.file": "\u0424\u0430\u0439\u043B",
615
627
  "menu.newEvent": "\u041D\u043E\u0432\u0430 \u043F\u043E\u0434\u0456\u044F",
@@ -678,12 +690,19 @@ var uk = {
678
690
  "settings.tab.language": "\u041C\u043E\u0432\u0430",
679
691
  "settings.tab.sync": "\u0421\u0438\u043D\u0445\u0440\u043E\u043D\u0456\u0437\u0430\u0446\u0456\u044F",
680
692
  "settings.tab.privacy": "\u041A\u043E\u043D\u0444\u0456\u0434\u0435\u043D\u0446\u0456\u0439\u043D\u0456\u0441\u0442\u044C",
693
+ "settings.tab.notifications": "\u0421\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F",
694
+ "settings.reminders.label": "\u041D\u0430\u0433\u0430\u0434\u0443\u0432\u0430\u0442\u0438 \u043F\u0435\u0440\u0435\u0434 \u043F\u043E\u0434\u0456\u044F\u043C\u0438",
695
+ "settings.reminders.off": "\u0412\u0438\u043C\u043A\u043D\u0435\u043D\u043E",
696
+ "settings.reminders.min": "\u0437\u0430 {n} \u0445\u0432",
697
+ "settings.reminders.note": "\u041F\u043E\u043A\u0438 Mysticals \u0432\u0456\u0434\u043A\u0440\u0438\u0442\u0438\u0439, \u043F\u0435\u0440\u0435\u0434 \u043A\u043E\u0436\u043D\u043E\u044E \u043F\u043E\u0434\u0456\u0454\u044E \u0437\u2019\u044F\u0432\u043B\u044F\u0454\u0442\u044C\u0441\u044F \u0441\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F \u0437 \u043A\u043D\u043E\u043F\u043A\u043E\u044E, \u0449\u043E\u0431 \u043F\u0440\u0438\u0454\u0434\u043D\u0430\u0442\u0438\u0441\u044F \u0434\u043E \u0434\u0437\u0432\u0456\u043D\u043A\u0430, \u044F\u043A\u0449\u043E \u0432 \u043F\u043E\u0434\u0456\u0457 \u0454 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F. \u0412\u0456\u0434\u0445\u0438\u043B\u0435\u043D\u0456, \u0446\u0456\u043B\u043E\u0434\u0435\u043D\u043D\u0456 \u043F\u043E\u0434\u0456\u0457 \u0442\u0430 \u043F\u043E\u0434\u0456\u0457 \u0437 \u043F\u0440\u0438\u0445\u043E\u0432\u0430\u043D\u0438\u0445 \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0456\u0432 \u0431\u0435\u0437 \u0441\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u044C.",
681
698
  "settings.noAccounts": "\u041E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0445 \u0437\u0430\u043F\u0438\u0441\u0456\u0432 \u0449\u0435 \u043D\u0435\u043C\u0430\u0454.",
682
699
  "settings.addAccount": "\u0414\u043E\u0434\u0430\u0442\u0438 \u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439 \u0437\u0430\u043F\u0438\u0441\u2026",
683
700
  "settings.theme": "\u0422\u0435\u043C\u0430",
684
701
  "theme.amber": "\u0411\u0443\u0440\u0448\u0442\u0438\u043D\u043E\u0432\u0438\u0439 \u043B\u044E\u043C\u0456\u043D\u043E\u0444\u043E\u0440",
685
702
  "theme.mono": "\u0427\u043E\u0440\u043D\u043E-\u0431\u0456\u043B\u0430",
686
703
  "theme.toon": "\u041C\u0443\u043B\u044C\u0442\u044F\u0448\u043D\u0430",
704
+ "theme.cloud": "\u0425\u043C\u0430\u0440\u0438\u043D\u043A\u0430",
705
+ "theme.light": "\u0421\u0432\u0456\u0442\u043B\u0430",
687
706
  "settings.language.auto": "\u0410\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u043D\u043E",
688
707
  "settings.language.system": "\u0441\u0438\u0441\u0442\u0435\u043C\u043D\u0430: {name}",
689
708
  "settings.sync.note": "\u041E\u0431\u043B\u0456\u043A\u043E\u0432\u0456 \u0437\u0430\u043F\u0438\u0441\u0438 \u0441\u0438\u043D\u0445\u0440\u043E\u043D\u0456\u0437\u0443\u044E\u0442\u044C\u0441\u044F \u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0447\u043D\u043E \u043A\u043E\u0436\u043D\u0456 2 \u0445\u0432\u0438\u043B\u0438\u043D\u0438 \u0442\u0430 \u043A\u043E\u043B\u0438 \u0437\u0430\u0441\u0442\u043E\u0441\u0443\u043D\u043E\u043A \u0437\u043D\u043E\u0432\u0443 \u0441\u0442\u0430\u0454 \u0430\u043A\u0442\u0438\u0432\u043D\u0438\u043C.",
@@ -698,6 +717,7 @@ var uk = {
698
717
  "settings.privacy.note": "Mysticals \u043D\u0430\u0434\u0441\u0438\u043B\u0430\u0454 \u0434\u0432\u0456 \u0430\u043D\u043E\u043D\u0456\u043C\u043D\u0456 \u043F\u043E\u0434\u0456\u0457: \u043A\u043E\u043B\u0438 \u0437\u0430\u0441\u0442\u043E\u0441\u0443\u043D\u043E\u043A \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u0432\u043F\u0435\u0440\u0448\u0435 \u0456 \u043A\u043E\u043B\u0438 \u0434\u043E\u0434\u0430\u043D\u043E \u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439 \u0437\u0430\u043F\u0438\u0441 (\u043B\u0438\u0448\u0435 \u0442\u0438\u043F \u0441\u0435\u0440\u0432\u0456\u0441\u0443). \u041D\u0456\u043A\u043E\u043B\u0438 \u043D\u0435 \u043D\u0430\u0434\u0441\u0438\u043B\u0430\u0454 \u0430\u0434\u0440\u0435\u0441\u0438 \u043F\u043E\u0448\u0442\u0438 \u0447\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0456\u0432, \u043A\u0430\u043B\u0435\u043D\u0434\u0430\u0440\u0456 \u0430\u0431\u043E \u043F\u043E\u0434\u0456\u0457.",
699
718
  "settings.privacy.policy": "\u041F\u043E\u043B\u0456\u0442\u0438\u043A\u0430 \u043A\u043E\u043D\u0444\u0456\u0434\u0435\u043D\u0446\u0456\u0439\u043D\u043E\u0441\u0442\u0456",
700
719
  "settings.privacy.share": "\u041D\u0430\u0434\u0441\u0438\u043B\u0430\u0442\u0438 \u0430\u043D\u043E\u043D\u0456\u043C\u043D\u0443 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043A\u0443 \u0432\u0438\u043A\u043E\u0440\u0438\u0441\u0442\u0430\u043D\u043D\u044F",
720
+ "settings.donate": "\u041F\u0456\u0434\u0442\u0440\u0438\u043C\u0430\u0442\u0438 \u043D\u0430 Ko-fi",
701
721
  "settings.account.labelFor": "\u041D\u0430\u0437\u0432\u0430 \u0434\u043B\u044F {email}",
702
722
  "settings.account.colourFor": "\u041A\u043E\u043B\u0456\u0440 \u0434\u043B\u044F {email}",
703
723
  "settings.account.remove": "\u0412\u0438\u0434\u0430\u043B\u0438\u0442\u0438",
@@ -2489,6 +2509,10 @@ function mapPeople(vevent, email) {
2489
2509
  });
2490
2510
  return { organizer, attendees, myStatus: attendees.find((a) => a.self)?.status };
2491
2511
  }
2512
+ function conferenceOf(vevent) {
2513
+ const urls = [...vevent.getAllProperties("conference"), ...vevent.getAllProperties("x-google-conference")].map((p) => String(p.getFirstValue() ?? "").trim());
2514
+ return urls.find((u) => /^https?:\/\//i.test(u));
2515
+ }
2492
2516
  function parseEvents(ics, href, etag, ctx, range) {
2493
2517
  const root = parse(ics);
2494
2518
  const vevents = root.getAllSubcomponents("vevent");
@@ -2502,6 +2526,7 @@ function parseEvents(ics, href, etag, ctx, range) {
2502
2526
  const build = (item, start, end, recurrenceId) => {
2503
2527
  const rid = recurrenceId?.toString();
2504
2528
  const raw = { href, ics, ...rid ? { recurrenceId: rid } : {} };
2529
+ const conference = conferenceOf(item.component);
2505
2530
  return {
2506
2531
  id: rid ? `${href}#${rid}` : href,
2507
2532
  accountId: ctx.accountId,
@@ -2512,6 +2537,7 @@ function parseEvents(ics, href, etag, ctx, range) {
2512
2537
  allDay: start.isDate,
2513
2538
  location: item.location || void 0,
2514
2539
  description: item.description || void 0,
2540
+ ...conference ? { conferenceUrl: conference } : {},
2515
2541
  ...mapPeople(item.component, ctx.email),
2516
2542
  etag,
2517
2543
  raw,
@@ -3780,8 +3806,9 @@ import { addDays as addDays8, differenceInCalendarDays as differenceInCalendarDa
3780
3806
  // ../../packages/core/src/logic/details.ts
3781
3807
  import { addDays as addDays7, isSameDay as isSameDay3, parseISO as parseISO7 } from "date-fns";
3782
3808
  var same = (a, b) => !!a && !!b && a.trim().toLowerCase() === b.trim().toLowerCase();
3809
+ var extraEmail = (label, email) => same(email, label) ? void 0 : email;
3783
3810
  function ownerLine(calendar, label, email) {
3784
- return { calendar: same(calendar, label) || same(calendar, email) ? void 0 : calendar, label, email: same(email, label) ? void 0 : email };
3811
+ return { calendar: same(calendar, label) || same(calendar, email) ? void 0 : calendar, label, email: extraEmail(label, email) };
3785
3812
  }
3786
3813
  function canEdit(e, account, calendar) {
3787
3814
  if (!account || !calendar || calendar.readOnly) return false;
@@ -3846,8 +3873,15 @@ function htmlToText(html) {
3846
3873
  function meetingUrl(location) {
3847
3874
  return location?.match(/https?:\/\/[^\s<>"]+/i)?.[0].replace(/[.,;:!?)\]}'>]+$/, "");
3848
3875
  }
3876
+ var CALL_KINDS = ["meet", "zoom", "teams", "video"];
3877
+ function descriptionCallUrl(description) {
3878
+ if (!description) return void 0;
3879
+ const hrefs = [...description.matchAll(/href\s*=\s*["']([^"']+)["']/gi)].map((m) => m[1].replace(/&amp;/g, "&"));
3880
+ const urls = [...hrefs, ...linkify(description.replace(/<[^>]*>/g, " ")).flatMap((p) => p.href ? [p.href] : [])];
3881
+ return urls.find((u) => /^https?:\/\//i.test(u) && CALL_KINDS.includes(linkKind(u)));
3882
+ }
3849
3883
  function eventMeetingUrl(e) {
3850
- return e.conferenceUrl ?? meetingUrl(e.location);
3884
+ return e.conferenceUrl ?? meetingUrl(e.location) ?? descriptionCallUrl(e.description);
3851
3885
  }
3852
3886
  function locationText(location = "") {
3853
3887
  return location.replace(/https?:\/\/[^\s<>"]+/gi, "").replace(/^[\s/|,·-]+|[\s/|,·-]+$/g, "");
@@ -4795,9 +4829,11 @@ function EventEditor({ event, initialStart, onClose }) {
4795
4829
  const calendar = calendars.find((c) => c.accountId === form.accountId && c.id === form.calendarId);
4796
4830
  const value = (f) => {
4797
4831
  switch (f) {
4798
- case "account":
4799
- if (event) return { text: account ? `${account.label} \xB7 ${account.email}` : form.accountId };
4800
- return account ? { text: `${account.label} \xB7 ${account.email}` } : { text: "\u2039 choose account \u203A", dim: true };
4832
+ case "account": {
4833
+ const name = account && [account.label, extraEmail(account.label, account.email)].filter(Boolean).join(" \xB7 ");
4834
+ if (event) return { text: name || form.accountId };
4835
+ return name ? { text: name } : { text: "\u2039 choose account \u203A", dim: true };
4836
+ }
4801
4837
  case "calendar":
4802
4838
  if (event) return { text: calendar?.name ?? form.calendarId };
4803
4839
  if (!form.accountId) return { text: "choose an account first", dim: true };
@@ -5160,6 +5196,7 @@ function AddCaldav({ onBack, onDone }) {
5160
5196
 
5161
5197
  // src/ui/screens/Accounts.tsx
5162
5198
  import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
5199
+ var DONATE_URL = "https://ko-fi.com/a1exalexander";
5163
5200
  function Accounts({ onClose }) {
5164
5201
  const api = useApi();
5165
5202
  const { accounts, calendars, loaded, error: loadError } = useDirectory();
@@ -5403,7 +5440,14 @@ function Accounts({ onClose }) {
5403
5440
  button("a", "add CalDAV"),
5404
5441
  /* @__PURE__ */ jsx12(Button, { k: "esc", label: "close", onPress: onClose })
5405
5442
  ] }),
5406
- mode.kind === "list" && /* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text10, { color: C.muted, children: "Made in Ukraine" }) })
5443
+ mode.kind === "list" && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
5444
+ /* @__PURE__ */ jsx12(Text10, { color: C.muted, children: "Made in Ukraine \xB7 " }),
5445
+ /* @__PURE__ */ jsxs10(Clickable, { onClick: () => void openUrl(DONATE_URL).catch(() => {
5446
+ }), children: [
5447
+ /* @__PURE__ */ jsx12(Text10, { color: C.muted, children: "Support on Ko-fi: " }),
5448
+ /* @__PURE__ */ jsx12(Text10, { color: C.cyan, underline: true, children: DONATE_URL })
5449
+ ] })
5450
+ ] })
5407
5451
  ] });
5408
5452
  }
5409
5453
  var loopbackPort = (authUrl) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysticals",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Isolated multi-account calendar for the terminal on macOS, Linux and Windows",
5
5
  "keywords": [
6
6
  "calendar",
@@ -47,7 +47,7 @@
47
47
  "tsup": "^8.5.1",
48
48
  "typescript": "^5.9.3",
49
49
  "vitest": "^3.2.7",
50
- "@mysticals/core": "0.8.0"
50
+ "@mysticals/core": "0.9.0"
51
51
  },
52
52
  "scripts": {
53
53
  "dev": "tsup --watch",