Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
1ebbb10
fix(scrollbar): consistent thin scrollbar across Firefox and Edge/Chr…
reniko May 18, 2026
36acd2c
fix(kanban): restore modal scroll panes after upstream Modal wrapper …
reniko May 18, 2026
030362a
fix(kanban): fix scrollbar visibility in modal panes for Edge and Fir…
reniko May 19, 2026
4b1c8f1
fix(kanban): use real CSS class to fix Modal wrapper flex chain
reniko May 19, 2026
cee596c
fix(kanban): use size=fullscreen to restore modal scroll layout
reniko May 19, 2026
f40f149
fix(scrollbar): remove redundant tailwind-scrollbar plugin classes
reniko May 19, 2026
1a6d7f2
fix(scrollbar): include overflow-x-auto in Firefox scrollbar-width rule
reniko May 19, 2026
fc85822
feat(kanban): add archive all for completed columns
reniko May 22, 2026
4d6abbd
fix(kanban): clarify archive all failure handling
reniko May 22, 2026
307cd66
fix(checklist): respect drop position when reordering subitems
reniko May 22, 2026
dc0d2de
Merge pull request #518 from reniko/fix/subitem-reorder-position
fccview May 27, 2026
e5a3702
Merge pull request #517 from reniko/feature/kanban-archive-all
fccview May 27, 2026
ee96699
fix small translation issue
fccview May 27, 2026
b3e4610
Merge pull request #514 from reniko/fix/scrollbar-edge-chromium
fccview May 27, 2026
1cbfdf3
Add remember me toggle to sign in and try gix atomic json read on ses…
fccview May 27, 2026
426685a
fix tests
fccview May 27, 2026
99dcff5
Add status field to card detail modal
reniko May 27, 2026
5d4095a
Make card detail properties collapsible
reniko May 27, 2026
3b92aaf
Add mobile status dropdown preference
reniko May 27, 2026
a6b9feb
Add mobile status action sheet
reniko May 27, 2026
fdbfeac
Refine card detail status interactions
reniko May 27, 2026
0057d39
Show card status action on all viewports
reniko May 27, 2026
2dbabf3
Added portuguese translation file
ajam13 May 27, 2026
1300bbc
Merge pull request #527 from ajam13/feature/add-portuguese-translation
fccview May 28, 2026
df144d5
fix: assign unique nested item IDs in client parser (#501)
reniko May 29, 2026
27c45d4
Merge pull request #534 from reniko/fix/duplicate-item-ids-501
fccview May 29, 2026
cb7a272
fix warning on leave and autosave exalidraw
fccview May 29, 2026
a11e079
Merge branch 'develop' of github-fccview:fccview/jotty into develop
fccview May 29, 2026
d18a783
fix sankey
fccview May 29, 2026
8532a9d
fix bidirectional dropdown bug
fccview May 29, 2026
8965991
add new api endpoints
fccview May 29, 2026
c96d4ae
fix scrollable modal for tasks
fccview May 29, 2026
ed017c3
Merge branch 'develop' into feature/n2-mobile-status-actions
fccview Jun 2, 2026
06edf56
Merge pull request #524 from reniko/feature/n2-mobile-status-actions
fccview Jun 2, 2026
3c99988
Add proxyClientMaxBodySize to next.config.mjs to fix bodySizeLimit bug
dmca-glasgow Jun 7, 2026
314db5d
Merge pull request #537 from dmca-glasgow/develop
fccview Jun 11, 2026
a200ed2
remove body size limit patch as not needed anymore
fccview Jun 11, 2026
f90db48
Merge branch 'develop' of github-fccview:fccview/jotty into develop
fccview Jun 11, 2026
47fec09
Start refactoring for kanban and dnd in general
fccview Jun 12, 2026
fed8077
fix styling around card detail
fccview Jun 12, 2026
9364926
improve modal for kanban item and make it all a bit better
fccview Jun 12, 2026
b512b83
fix calendar to show start/end date and improve visibility
fccview Jun 12, 2026
edc0ae8
neural network finally deployed, shit has been sitting in a branch fo…
fccview Jun 13, 2026
96c7701
make dev origins an env var so people can develop with their own loca…
fccview Jun 13, 2026
15ac5c9
Merge pull request #541 from fccview/feature/neural-network
fccview Jun 13, 2026
999a421
Merge pull request #540 from fccview/feature/kanban-dnd-refactoring
fccview Jun 13, 2026
59823e1
fix rabbit nitpicks
fccview Jun 13, 2026
2d05c95
fix dependency vulnerabilities
fccview Jun 13, 2026
0f08f4a
continue fixing nitpicks
fccview Jun 13, 2026
ad55b04
last two nitpicks
fccview Jun 13, 2026
e732198
add more endpoints
fccview Jun 13, 2026
3307091
latest nitpick, the bunny is a savage
fccview Jun 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 2 additions & 47 deletions app/(loggedInRoutes)/settings/connections/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { LinksTab } from "@/app/_components/FeatureComponents/Profile/Parts/LinksTab";
import { readLinkIndex } from "@/app/_server/actions/link";
import { LinkIndex } from "@/app/_types";
import { getUsername } from "@/app/_server/actions/users";
import { getArchivedItems } from "@/app/_server/actions/archived";
import { getUserNotes } from "@/app/_server/actions/note";
import { getUserChecklists } from "@/app/_server/actions/checklist";
import { filterArchivedLinkIndex } from "@/app/_components/FeatureComponents/Profile/Parts/ConnectionsGraph/graph-data";

export default async function ConnectionsPage() {
const username = await getUsername();
Expand All @@ -16,52 +16,7 @@ export default async function ConnectionsPage() {
]);

const archivedItems = archivedResult.success ? archivedResult.data : [];

const filterArchivedItems = (linkIndex: LinkIndex, archivedItems: any[]): LinkIndex => {
const archivedIds = new Set(archivedItems.map(item => `${item.category || 'Uncategorized'}/${item.id}`));

const filteredNotes = Object.fromEntries(
Object.entries(linkIndex.notes).filter(([key]) => !archivedIds.has(key))
);

const filteredChecklists = Object.fromEntries(
Object.entries(linkIndex.checklists).filter(([key]) => !archivedIds.has(key))
);

Object.keys(filteredNotes).forEach(noteKey => {
filteredNotes[noteKey].isLinkedTo.notes = filteredNotes[noteKey].isLinkedTo.notes.filter(
linkedKey => !archivedIds.has(linkedKey)
);
filteredNotes[noteKey].isLinkedTo.checklists = filteredNotes[noteKey].isLinkedTo.checklists.filter(
linkedKey => !archivedIds.has(linkedKey)
);
filteredNotes[noteKey].isReferencedIn.notes = filteredNotes[noteKey].isReferencedIn.notes.filter(
refKey => !archivedIds.has(refKey)
);
filteredNotes[noteKey].isReferencedIn.checklists = filteredNotes[noteKey].isReferencedIn.checklists.filter(
refKey => !archivedIds.has(refKey)
);
});

Object.keys(filteredChecklists).forEach(checklistKey => {
filteredChecklists[checklistKey].isLinkedTo.notes = filteredChecklists[checklistKey].isLinkedTo.notes.filter(
linkedKey => !archivedIds.has(linkedKey)
);
filteredChecklists[checklistKey].isLinkedTo.checklists = filteredChecklists[checklistKey].isLinkedTo.checklists.filter(
linkedKey => !archivedIds.has(linkedKey)
);
filteredChecklists[checklistKey].isReferencedIn.notes = filteredChecklists[checklistKey].isReferencedIn.notes.filter(
refKey => !archivedIds.has(refKey)
);
filteredChecklists[checklistKey].isReferencedIn.checklists = filteredChecklists[checklistKey].isReferencedIn.checklists.filter(
refKey => !archivedIds.has(refKey)
);
});

return { notes: filteredNotes, checklists: filteredChecklists };
};

const filteredLinkIndex = filterArchivedItems(linkIndex, archivedItems || []);
const filteredLinkIndex = filterArchivedLinkIndex(linkIndex, archivedItems || []);
const notes = notesResult.success ? notesResult.data || [] : [];
const checklists = checklistsResult.success ? checklistsResult.data || [] : [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const ChecklistHome = ({
}

return (
<div className="h-full overflow-y-auto hide-scrollbar bg-background pb-16 lg:pb-0 jotty-scrollable-content">
<div className="h-full overflow-y-auto bg-background pb-16 lg:pb-0 jotty-scrollable-content">
<div className="max-w-full pt-6 pb-4 px-4 lg:pt-8 lg:pb-8 lg:px-8">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 lg:mb-8">
<div className="flex items-center gap-3">
Expand Down
2 changes: 1 addition & 1 deletion app/_components/FeatureComponents/Home/Parts/NotesHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export const NotesHome = ({
}

return (
<div className="flex-1 overflow-y-auto jotty-scrollable-content bg-background h-full hide-scrollbar">
<div className="flex-1 overflow-y-auto jotty-scrollable-content bg-background h-full">
<div className="max-w-full pt-6 pb-4 px-4 lg:pt-8 lg:pb-8 lg:px-8">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6 lg:mb-8">
<div className="flex items-center gap-3">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export const ArchivedItemsModal = ({
<p className="text-md lg:text-xs text-muted-foreground">
{t('common.archived')}{" "}
{formatDateString(item.archivedAt)}
{item.archivedBy && ` ${t('common.by')} ${item.archivedBy}`}
{item.archivedBy && ` ${t('common.by', { owner: item.archivedBy })}`}
</p>
)}
</div>
Expand Down
165 changes: 99 additions & 66 deletions app/_components/FeatureComponents/Kanban/CalendarView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import { KeyboardEvent } from "react";
import { Checklist, Item } from "@/app/_types";
import { useCalendarView } from "@/app/_hooks/kanban/useCalendarView";
import { Button } from "@/app/_components/GlobalComponents/Buttons/Button";
Expand All @@ -10,7 +11,11 @@ import {
Download04Icon,
} from "hugeicons-react";
import { cn } from "@/app/_utils/global-utils";
import { getPriorityDotColor } from "@/app/_utils/kanban/index";
import { getPriorityBarStyle } from "@/app/_utils/kanban/index";
import {
getMaxBarLanes,
getWeekBarSegments,
} from "@/app/_utils/kanban/calendar-utils";
import { useTranslations } from "next-intl";

interface CalendarViewProps {
Expand All @@ -33,16 +38,18 @@ const _toLocalDate = (d: Date): string => {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
};

const _BAR_HEIGHT = 18;

export const CalendarView = ({ checklist, onItemClick }: CalendarViewProps) => {
const t = useTranslations();
const {
currentDate,
calendarGrid,
events,
goToPreviousMonth,
goToNextMonth,
goToToday,
exportICS,
getEventsForDate,
unscheduledItems,
} = useCalendarView(checklist);

Expand All @@ -53,6 +60,11 @@ export const CalendarView = ({ checklist, onItemClick }: CalendarViewProps) => {
year: "numeric",
});

const _openItem = (itemId: string) => {
const item = checklist.items.find((entry) => entry.id === itemId);
if (item && onItemClick) onItemClick(item);
};

return (
<div className="space-y-4 min-w-0">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between min-w-0">
Expand Down Expand Up @@ -88,78 +100,99 @@ export const CalendarView = ({ checklist, onItemClick }: CalendarViewProps) => {
))}
</div>

{calendarGrid.map((week, weekIndex) => (
<div key={weekIndex} className="grid grid-cols-7">
{week.map((day, dayIndex) => {
if (!day) {
return (
<div
key={`empty-${dayIndex}`}
className="min-h-[100px] p-1 border-b border-r border-border bg-muted/20"
/>
);
}
{calendarGrid.map((week, weekIndex) => {
const segments = getWeekBarSegments(week, events);
const maxLanes = getMaxBarLanes(segments);
const barAreaHeight = maxLanes * _BAR_HEIGHT;

const dateStr = _toLocalDate(day);
const dayEvents = getEventsForDate(day);
const isToday = dateStr === today;
return (
<div key={weekIndex} className="relative grid grid-cols-7">
{week.map((day, dayIndex) => {
if (!day) {
return (
<div
key={`empty-${dayIndex}`}
className="border-b border-r border-border bg-muted/20"
style={{ minHeight: 72 + barAreaHeight }}
/>
);
}

return (
<div
key={dateStr}
className={cn(
"min-h-[100px] p-1 border-b border-r border-border transition-colors",
isToday && "bg-primary/5",
)}
>
const dateStr = _toLocalDate(day);
const isToday = dateStr === today;

return (
<div
key={dateStr}
className={cn(
"text-xs font-medium mb-1 w-6 h-6 flex items-center justify-center rounded-full",
isToday && "bg-primary text-primary-foreground",
"border-b border-r border-border p-1 transition-colors",
isToday && "bg-primary/5",
)}
style={{ minHeight: 72 + barAreaHeight }}
>
{day.getDate()}
</div>
<div className="space-y-0.5">
{dayEvents.slice(0, 3).map((event) => (
<div
key={event.id}
onClick={() => {
const item = checklist.items.find(
(i) => i.id === event.itemId,
);
if (item && onItemClick) onItemClick(item);
}}
className={cn(
"text-[10px] px-1 py-0.5 rounded truncate cursor-pointer hover:opacity-80 transition-opacity flex items-center gap-1 bg-muted text-muted-foreground border-l-2",
event.completed && "line-through opacity-60",
)}
style={{
borderLeftColor: event.completed
? "#22c55e"
: getPriorityDotColor(
event.priority as Parameters<
typeof getPriorityDotColor
>[0],
),
}}
>
{event.title}
</div>
))}
{dayEvents.length > 3 && (
<div className="text-[10px] text-muted-foreground px-1">
{t("kanban.moreEvents", {
count: dayEvents.length - 3,
})}
</div>
)}
<div
className={cn(
"text-xs font-medium mb-1 w-6 h-6 flex items-center justify-center rounded-full",
isToday && "bg-primary text-primary-foreground",
)}
>
{day.getDate()}
</div>
</div>
);
})}

{maxLanes > 0 && (
<div
className="absolute inset-x-0 top-7 grid grid-cols-7 gap-y-0.5 px-0.5 pointer-events-none"
style={{ gridTemplateRows: `repeat(${maxLanes}, ${_BAR_HEIGHT}px)` }}
>
{segments.map((segment) => {
const barStyle = getPriorityBarStyle(
segment.event.priority as Parameters<typeof getPriorityBarStyle>[0],
);

return (
<div
key={`${segment.event.id}-${segment.colStart}-${segment.lane}`}
{...(onItemClick
? {
role: "button" as const,
tabIndex: 0,
"aria-label": segment.event.title,
onClick: () => _openItem(segment.event.itemId),
onKeyDown: (e: KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
_openItem(segment.event.itemId);
}
},
}
: {})}
className={cn(
"pointer-events-auto h-4 text-[10px] font-medium leading-4 px-1.5 truncate transition-[filter,opacity]",
onItemClick &&
"cursor-pointer hover:brightness-95 dark:hover:brightness-110",
!barStyle.backgroundColor && "bg-muted/80 text-muted-foreground",
segment.continuesPrev ? "rounded-l-none ml-0" : "rounded-l-jotty ml-0.5",
segment.continuesNext ? "rounded-r-none mr-0" : "rounded-r-jotty mr-0.5",
segment.event.completed && "line-through opacity-70",
)}
style={{
...barStyle,
gridColumn: `${segment.colStart + 1} / span ${segment.colSpan}`,
gridRow: segment.lane + 1,
}}
>
{!segment.continuesPrev ? segment.event.title : "\u00a0"}
</div>
);
})}
</div>
);
})}
</div>
))}
)}
</div>
);
})}
</div>

{unscheduledItems.length > 0 && (
Expand Down
Loading
Loading