From a23db529ca19ce47cc1978e00a354660f9c8a96b Mon Sep 17 00:00:00 2001 From: Andreas Molnar Date: Sat, 12 Sep 2026 13:35:32 +0200 Subject: [PATCH 1/2] feat(links): icons for lists, paste markdown links --- .../links/CreateLinksCollectionDialog.tsx | 65 ++++++++++++------- .../links/CreateLinksItemDialog.tsx | 24 ++++++- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/links/CreateLinksCollectionDialog.tsx b/apps/web/src/components/links/CreateLinksCollectionDialog.tsx index 192bf644..e363786b 100644 --- a/apps/web/src/components/links/CreateLinksCollectionDialog.tsx +++ b/apps/web/src/components/links/CreateLinksCollectionDialog.tsx @@ -1,9 +1,12 @@ "use client"; import { useEffect, useState } from "react"; +import AppIcon from "@dashwise/app-icon"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import IconPickerComponent, { type IconResult } from "@/components/settings/IconPicker"; import useAuth from "@/context/useAuth"; import { createLinksCollectionAction, updateLinksCollectionAction } from '@/lib/apiClient'; import { LinksFormAlert, type LinksFormAlertState } from "./LinksFormAlert"; @@ -21,6 +24,7 @@ export default function CreateLinksCollectionDialog({ open, onOpenChange, collec const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [icon, setIcon] = useState(""); + const [iconPickerOpen, setIconPickerOpen] = useState(false); const [alert, setAlert] = useState({ open: false, title: "", description: "", variant: "success" }); const isEditing = Boolean(collection?.id); @@ -30,6 +34,7 @@ export default function CreateLinksCollectionDialog({ open, onOpenChange, collec setName(collection?.name ?? ""); setDescription(collection?.description ?? ""); setIcon(collection?.icon ?? ""); + setIconPickerOpen(false); setAlert({ open: false, title: "", description: "", variant: "success" }); }, [collection, open]); @@ -69,6 +74,7 @@ export default function CreateLinksCollectionDialog({ open, onOpenChange, collec onOpenChange(false); setName(""); setDescription(""); + setIcon(""); } catch (error) { const message = error instanceof Error ? error.message : String(error); setAlert({ @@ -82,16 +88,42 @@ export default function CreateLinksCollectionDialog({ open, onOpenChange, collec >
- setName(event.target.value)} - className="w-full rounded-md border border-white/10 bg-white/5 px-3 py-2 text-foreground outline-none transition-colors placeholder:text-white/35 focus:border-primary" - placeholder="Design inspiration" - required - /> +
+ setName(event.target.value)} + className="min-w-0 flex-1 rounded-md border border-white/10 bg-white/5 px-3 py-2 text-foreground outline-none transition-colors placeholder:text-white/35 focus:border-primary" + placeholder="Design inspiration" + required + /> + {!renameOnly && ( + + + + + + { + setIcon(selectedIcon.url?.trim() ?? ""); + setIconPickerOpen(false); + }} + /> + + + )} +
{!renameOnly && ( @@ -108,19 +140,6 @@ export default function CreateLinksCollectionDialog({ open, onOpenChange, collec placeholder="Optional note about what belongs here" /> - -
- - setIcon(event.target.value)} - className="w-full rounded-md border border-white/10 bg-white/5 px-3 py-2 text-foreground outline-none transition-colors placeholder:text-white/35 focus:border-primary" - placeholder="fa6-solid:folder-open or url:https://..." - /> -
)} diff --git a/apps/web/src/components/links/CreateLinksItemDialog.tsx b/apps/web/src/components/links/CreateLinksItemDialog.tsx index d25bdb9a..7c3f15c7 100644 --- a/apps/web/src/components/links/CreateLinksItemDialog.tsx +++ b/apps/web/src/components/links/CreateLinksItemDialog.tsx @@ -128,6 +128,16 @@ function IconPickerButton({ ); } +function parseMarkdownLink(value: string) { + const match = value.trim().match(/^\[([^\]]+)\]\(\s*]+)>?\s*\)$/i); + if (!match) return null; + + return { + title: match[1].trim(), + url: match[2], + }; +} + function buildTargetGroups(collections: CollectionRecord[], folders: FolderRecord[]): TargetGroup[] { const foldersByCollection = new Map(); for (const folder of folders) { @@ -717,7 +727,19 @@ defaultFolderId, setUrl(event.target.value)} + onChange={(event) => { + const value = event.target.value; + const markdownLink = parseMarkdownLink(value); + + if (markdownLink) { + setUrl(markdownLink.url); + autoMetadataRef.current.title = ""; + setTitle(markdownLink.title); + return; + } + + setUrl(value); + }} placeholder="https://n8n.io" required={activeTab === "link"} /> From 9ace33235a9aca9703f91d7a845e3a73a2beed98 Mon Sep 17 00:00:00 2001 From: Andreas Molnar Date: Sat, 12 Sep 2026 13:42:58 +0200 Subject: [PATCH 2/2] fix(calendar widget): show end date for events spanning across multiple days --- .../static-widgets/CalendarWidgets.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/integrationskit/static-widgets/CalendarWidgets.tsx b/packages/integrationskit/static-widgets/CalendarWidgets.tsx index a6ae6a8c..b6aedbd1 100644 --- a/packages/integrationskit/static-widgets/CalendarWidgets.tsx +++ b/packages/integrationskit/static-widgets/CalendarWidgets.tsx @@ -124,6 +124,18 @@ export function CalendarUpcomingWidget({ items = [], maxItems = 5, className = " return date.toLocaleDateString([], { weekday: "short", month: "short", day: "numeric" }); }; + const formatEventTime = (item: CalendarUpcomingItem) => { + if (!item.isAllDay) return formatTime(item.start); + if (!item.end) return "All day"; + + const start = new Date(item.start); + const end = new Date(item.end); + const inclusiveEnd = new Date(end.getTime() - 1); + const spansMultipleDays = start.toDateString() !== inclusiveEnd.toDateString(); + + return spansMultipleDays ? `Until ${formatDate(inclusiveEnd.toISOString())}` : "All day"; + }; + const resolved = { header: { title: "Upcoming Events", @@ -133,7 +145,7 @@ export function CalendarUpcomingWidget({ items = [], maxItems = 5, className = " list: upcoming.map((item) => ({ title: item.title, subtitle: [ - item.isAllDay ? "All day" : formatTime(item.start), + formatEventTime(item), item.location, ].filter(Boolean) as string[], group: formatDate(item.start),