floq 0.5.0 → 0.6.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.
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useMemo } from 'react';
3
3
  import { Box, Text, useInput } from 'ink';
4
4
  import { t } from '../../i18n/index.js';
@@ -6,32 +6,135 @@ import { useTheme } from '../theme/index.js';
6
6
  import { parseChangelog } from '../../changelog.js';
7
7
  import { loadConfig, isTursoEnabled, getTursoConfig, getDbPath } from '../../config.js';
8
8
  import { CONFIG_FILE, DATA_DIR } from '../../paths.js';
9
- const VISIBLE_LINES = 12;
9
+ const VISIBLE_LINES = 14;
10
10
  export function HelpModal({ onClose, isKanban = false }) {
11
11
  const [activeTab, setActiveTab] = useState('keybindings');
12
12
  const [scrollOffset, setScrollOffset] = useState(0);
13
13
  const i18n = t();
14
14
  const theme = useTheme();
15
15
  const help = i18n.tui.help;
16
- // Get changelog items for scroll calculation
17
- const changelogItems = useMemo(() => {
16
+ const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
17
+ // Build keybindings content
18
+ const keybindingsContent = useMemo(() => {
19
+ if (isKanban) {
20
+ const kHelp = i18n.tui.kanbanHelp;
21
+ return [
22
+ { type: 'header', value: kHelp.navigation },
23
+ { type: 'key', key: 'h/l ←/→', value: kHelp.columnSwitch },
24
+ { type: 'key', key: '1-3', value: kHelp.columnDirect },
25
+ { type: 'key', key: 'j/k ↑/↓', value: kHelp.taskSelect },
26
+ { type: 'header', value: kHelp.actions },
27
+ { type: 'key', key: 'a', value: kHelp.addTask },
28
+ { type: 'key', key: 'd', value: kHelp.completeTask },
29
+ { type: 'key', key: 'm', value: kHelp.moveRight },
30
+ { type: 'key', key: 'BS', value: kHelp.moveLeft },
31
+ { type: 'key', key: 'u', value: 'Undo' },
32
+ { type: 'key', key: 'Ctrl+r', value: 'Redo' },
33
+ { type: 'header', value: kHelp.settings },
34
+ { type: 'key', key: 'T', value: kHelp.changeTheme },
35
+ { type: 'key', key: 'V', value: kHelp.changeViewMode },
36
+ { type: 'key', key: 'L', value: kHelp.changeLanguage },
37
+ { type: 'header', value: kHelp.other },
38
+ { type: 'key', key: '/', value: kHelp.searchTasks },
39
+ { type: 'key', key: '?', value: kHelp.showHelp },
40
+ { type: 'key', key: 'q', value: kHelp.quit },
41
+ ];
42
+ }
43
+ return [
44
+ { type: 'header', value: help.navigation },
45
+ { type: 'key', key: '1-6', value: help.tabSwitch },
46
+ { type: 'key', key: 'h/l ←/→', value: help.prevNextTab },
47
+ { type: 'key', key: 'j/k ↑/↓', value: help.taskSelect },
48
+ { type: 'header', value: help.actions },
49
+ { type: 'key', key: 'a', value: help.addTask },
50
+ { type: 'key', key: 'd', value: help.completeTask },
51
+ { type: 'key', key: 'n', value: help.moveToNext },
52
+ { type: 'key', key: 's', value: help.moveToSomeday },
53
+ { type: 'key', key: 'w', value: help.moveToWaiting },
54
+ { type: 'key', key: 'i', value: help.moveToInbox },
55
+ { type: 'key', key: 'r', value: help.refresh },
56
+ { type: 'key', key: 'u', value: help.undo },
57
+ { type: 'key', key: 'Ctrl+r', value: help.redo },
58
+ { type: 'header', value: help.projects },
59
+ { type: 'key', key: 'p', value: help.makeProject },
60
+ { type: 'key', key: 'P', value: help.linkToProject },
61
+ { type: 'key', key: 'Enter', value: help.openProject },
62
+ { type: 'key', key: 'Esc/b', value: help.backFromProject },
63
+ { type: 'header', value: help.settings },
64
+ { type: 'key', key: 'T', value: help.changeTheme },
65
+ { type: 'key', key: 'V', value: help.changeViewMode },
66
+ { type: 'key', key: 'L', value: help.changeLanguage },
67
+ { type: 'header', value: help.other },
68
+ { type: 'key', key: '/', value: help.searchTasks },
69
+ { type: 'key', key: '?', value: help.showHelp },
70
+ { type: 'key', key: 'q', value: help.quit },
71
+ ];
72
+ }, [isKanban, help, i18n.tui.kanbanHelp]);
73
+ // Build info content
74
+ const infoContent = useMemo(() => {
75
+ const info = i18n.tui.info;
76
+ const config = loadConfig();
77
+ const tursoEnabled = isTursoEnabled();
78
+ const tursoConfig = tursoEnabled ? getTursoConfig() : undefined;
79
+ const dbPath = getDbPath();
80
+ const tursoUrl = tursoConfig ? (() => {
81
+ try {
82
+ return new URL(tursoConfig.url).host;
83
+ }
84
+ catch {
85
+ return tursoConfig.url;
86
+ }
87
+ })() : '';
88
+ const lines = [
89
+ { type: 'header', value: info.settings },
90
+ { type: 'key', key: info.theme, value: config.theme },
91
+ { type: 'key', key: info.language, value: config.locale },
92
+ { type: 'key', key: info.viewMode, value: config.viewMode },
93
+ { type: 'header', value: info.database },
94
+ { type: 'key', key: info.dbType, value: tursoEnabled ? info.turso : info.local },
95
+ { type: 'key', key: info.dbPath, value: dbPath },
96
+ ];
97
+ if (tursoEnabled && tursoUrl) {
98
+ lines.push({ type: 'key', key: info.tursoUrl, value: tursoUrl });
99
+ }
100
+ lines.push({ type: 'header', value: info.paths }, { type: 'key', key: info.configFile, value: CONFIG_FILE }, { type: 'key', key: info.dataDir, value: DATA_DIR });
101
+ return lines;
102
+ }, [i18n.tui.info]);
103
+ // Build changelog content
104
+ const changelogContent = useMemo(() => {
18
105
  const changelog = parseChangelog();
106
+ const whatsNew = i18n.tui.whatsNew;
19
107
  const items = [];
108
+ const getSectionLabel = (type) => {
109
+ const labels = {
110
+ Added: whatsNew.added,
111
+ Changed: whatsNew.changed,
112
+ Deprecated: whatsNew.deprecated,
113
+ Removed: whatsNew.removed,
114
+ Fixed: whatsNew.fixed,
115
+ Security: whatsNew.security,
116
+ };
117
+ return labels[type] || type;
118
+ };
20
119
  for (const entry of changelog.entries.slice(0, 5)) {
21
- items.push({ type: 'version', content: entry.version, date: entry.date });
120
+ items.push({ type: 'version', value: entry.version, date: entry.date });
22
121
  for (const section of entry.sections) {
23
- items.push({ type: 'section', content: section.type });
122
+ items.push({ type: 'section', value: getSectionLabel(section.type) });
24
123
  for (const item of section.items) {
25
- items.push({ type: 'item', content: item });
124
+ items.push({ type: 'item', value: item });
26
125
  }
27
126
  }
28
127
  }
29
128
  return items;
30
- }, []);
31
- const maxScroll = Math.max(0, changelogItems.length - VISIBLE_LINES);
129
+ }, [i18n.tui.whatsNew]);
130
+ // Get current content and max scroll
131
+ const currentContent = activeTab === 'keybindings' ? keybindingsContent
132
+ : activeTab === 'info' ? infoContent
133
+ : changelogContent;
134
+ const maxScroll = Math.max(0, currentContent.length - VISIBLE_LINES);
32
135
  const tabs = ['keybindings', 'info', 'whatsNew'];
33
136
  useInput((input, key) => {
34
- // Tab key detection (key.tab or raw tab character)
137
+ // Tab key detection
35
138
  if (key.tab || input === '\t') {
36
139
  setActiveTab(prev => {
37
140
  const currentIndex = tabs.indexOf(prev);
@@ -56,108 +159,39 @@ export function HelpModal({ onClose, isKanban = false }) {
56
159
  setScrollOffset(0);
57
160
  return;
58
161
  }
59
- // Arrow keys and h/l for tab switching
60
- if (input === 'h' || key.leftArrow) {
61
- setActiveTab(prev => {
62
- const currentIndex = tabs.indexOf(prev);
63
- return tabs[(currentIndex - 1 + tabs.length) % tabs.length];
64
- });
65
- setScrollOffset(0);
162
+ // Scroll with j/k or arrows
163
+ if (input === 'j' || key.downArrow) {
164
+ setScrollOffset(prev => Math.min(prev + 1, maxScroll));
66
165
  return;
67
166
  }
68
- if (input === 'l' || key.rightArrow) {
69
- setActiveTab(prev => {
70
- const currentIndex = tabs.indexOf(prev);
71
- return tabs[(currentIndex + 1) % tabs.length];
72
- });
73
- setScrollOffset(0);
167
+ if (input === 'k' || key.upArrow) {
168
+ setScrollOffset(prev => Math.max(prev - 1, 0));
74
169
  return;
75
170
  }
76
- // Scroll in What's New tab
77
- if (activeTab === 'whatsNew') {
78
- if (input === 'j' || key.downArrow) {
79
- setScrollOffset(prev => Math.min(prev + 1, maxScroll));
80
- return;
81
- }
82
- if (input === 'k' || key.upArrow) {
83
- setScrollOffset(prev => Math.max(prev - 1, 0));
84
- return;
85
- }
86
- }
87
171
  // Close modal
88
172
  if (key.escape || key.return || input === 'q' || input === ' ') {
89
173
  onClose();
90
174
  return;
91
175
  }
92
176
  });
93
- const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
94
- return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.modal, borderColor: theme.colors.borderActive, paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsxs(Box, { children: [_jsxs(Text, { bold: activeTab === 'keybindings', color: activeTab === 'keybindings' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'keybindings', children: [' ', formatTitle(help.keybindingsTab), ' '] }), _jsx(Text, { color: theme.colors.textMuted, children: " \u2502 " }), _jsxs(Text, { bold: activeTab === 'info', color: activeTab === 'info' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'info', children: [' ', formatTitle(help.infoTab), ' '] }), _jsx(Text, { color: theme.colors.textMuted, children: " \u2502 " }), _jsxs(Text, { bold: activeTab === 'whatsNew', color: activeTab === 'whatsNew' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'whatsNew', children: [' ', formatTitle(help.whatsNewTab), ' '] })] }) }), activeTab === 'keybindings' ? (isKanban ? (_jsx(KanbanKeybindingsContent, {})) : (_jsx(GTDKeybindingsContent, {}))) : activeTab === 'info' ? (_jsx(InfoContent, {})) : (_jsx(WhatsNewContent, { items: changelogItems, scrollOffset: scrollOffset, visibleLines: VISIBLE_LINES, maxScroll: maxScroll })), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: activeTab === 'whatsNew' && maxScroll > 0
95
- ? `j/k: scroll | ${help.tabHint} | ${help.closeHint}`
96
- : `${help.tabHint} | ${help.closeHint}` }) })] }));
97
- }
98
- function GTDKeybindingsContent() {
99
- const i18n = t();
100
- const help = i18n.tui.help;
101
- const theme = useTheme();
102
- const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
103
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-6" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "w" }), " ", help.moveToWaiting] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "u" }), " ", help.undo] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Ctrl+r" }), " ", help.redo] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
104
- }
105
- function KanbanKeybindingsContent() {
106
- const i18n = t();
107
- const help = i18n.tui.kanbanHelp;
108
- const theme = useTheme();
109
- const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
110
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.columnSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-3" }), " ", help.columnDirect] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.moveRight] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "BS" }), " ", help.moveLeft] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "u" }), " Undo"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Ctrl+r" }), " Redo"] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
111
- }
112
- function InfoContent() {
113
- const i18n = t();
114
- const info = i18n.tui.info;
115
- const theme = useTheme();
116
- const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
117
- const config = loadConfig();
118
- const tursoEnabled = isTursoEnabled();
119
- const tursoConfig = tursoEnabled ? getTursoConfig() : undefined;
120
- const dbPath = getDbPath();
121
- // Get Turso host for display
122
- const tursoUrl = tursoConfig ? (() => {
123
- try {
124
- return new URL(tursoConfig.url).host;
125
- }
126
- catch {
127
- return tursoConfig.url;
128
- }
129
- })() : '';
130
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(info.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.theme, ":"] }), " ", config.theme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.language, ":"] }), " ", config.locale] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.viewMode, ":"] }), " ", config.viewMode] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(info.database) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.dbType, ":"] }), " ", tursoEnabled ? info.turso : info.local] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.dbPath, ":"] }), " ", dbPath] }), tursoEnabled && tursoUrl && (_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.tursoUrl, ":"] }), " ", tursoUrl] }))] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(info.paths) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.configFile, ":"] }), " ", CONFIG_FILE] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.dataDir, ":"] }), " ", DATA_DIR] })] })] })] }));
131
- }
132
- function WhatsNewContent({ items, scrollOffset, visibleLines, maxScroll }) {
133
- const i18n = t();
134
- const whatsNew = i18n.tui.whatsNew;
135
- const theme = useTheme();
136
- const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
137
- const getSectionLabel = (type) => {
138
- const labels = {
139
- Added: whatsNew.added,
140
- Changed: whatsNew.changed,
141
- Deprecated: whatsNew.deprecated,
142
- Removed: whatsNew.removed,
143
- Fixed: whatsNew.fixed,
144
- Security: whatsNew.security,
145
- };
146
- return labels[type] || type;
147
- };
148
- if (items.length === 0) {
149
- return (_jsx(Box, { justifyContent: "center", paddingY: 2, children: _jsx(Text, { color: theme.colors.textMuted, children: whatsNew.noChanges }) }));
150
- }
151
- const visibleItems = items.slice(scrollOffset, scrollOffset + visibleLines);
177
+ const visibleContent = currentContent.slice(scrollOffset, scrollOffset + VISIBLE_LINES);
152
178
  const showScrollUp = scrollOffset > 0;
153
179
  const showScrollDown = scrollOffset < maxScroll;
154
- return (_jsxs(Box, { flexDirection: "column", children: [showScrollUp && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25B2 scroll up" })), visibleItems.map((item, index) => {
155
- if (item.type === 'version') {
156
- return (_jsxs(Text, { bold: true, color: theme.colors.accent, children: ["v", item.content, " ", item.date && _jsxs(Text, { color: theme.colors.textMuted, children: ["(", item.date, ")"] })] }, `${item.content}-${index}`));
157
- }
158
- if (item.type === 'section') {
159
- return (_jsxs(Text, { color: theme.colors.secondary, children: [' ', formatTitle(getSectionLabel(item.content)), ":"] }, `${item.content}-${index}`));
160
- }
161
- return (_jsxs(Text, { color: theme.colors.text, children: [' ', "\u2022 ", item.content] }, `${item.content}-${index}`));
162
- }), showScrollDown && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25BC scroll down" }))] }));
180
+ const renderLine = (line, index) => {
181
+ switch (line.type) {
182
+ case 'header':
183
+ return (_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(line.value) }, index));
184
+ case 'key':
185
+ return (_jsxs(Text, { color: theme.colors.text, children: [' ', _jsx(Text, { color: theme.colors.textHighlight, children: (line.key || '').padEnd(10) }), " ", line.value] }, index));
186
+ case 'version':
187
+ return (_jsxs(Text, { bold: true, color: theme.colors.accent, children: ["v", line.value, " ", line.date && _jsxs(Text, { color: theme.colors.textMuted, children: ["(", line.date, ")"] })] }, index));
188
+ case 'section':
189
+ return (_jsxs(Text, { color: theme.colors.secondary, children: [' ', formatTitle(line.value), ":"] }, index));
190
+ case 'item':
191
+ return (_jsxs(Text, { color: theme.colors.text, children: [' ', "\u2022 ", line.value] }, index));
192
+ default:
193
+ return (_jsx(Text, { color: theme.colors.text, children: line.value }, index));
194
+ }
195
+ };
196
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.modal, borderColor: theme.colors.borderActive, paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsxs(Box, { children: [_jsxs(Text, { bold: activeTab === 'keybindings', color: activeTab === 'keybindings' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'keybindings', children: [' ', formatTitle(help.keybindingsTab), ' '] }), _jsx(Text, { color: theme.colors.textMuted, children: " \u2502 " }), _jsxs(Text, { bold: activeTab === 'info', color: activeTab === 'info' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'info', children: [' ', formatTitle(help.infoTab), ' '] }), _jsx(Text, { color: theme.colors.textMuted, children: " \u2502 " }), _jsxs(Text, { bold: activeTab === 'whatsNew', color: activeTab === 'whatsNew' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'whatsNew', children: [' ', formatTitle(help.whatsNewTab), ' '] })] }) }), _jsxs(Box, { flexDirection: "column", height: VISIBLE_LINES + 2, children: [showScrollUp && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25B2 scroll up (k)" })), !showScrollUp && _jsx(Text, { children: " " }), visibleContent.map((line, index) => renderLine(line, index)), showScrollDown && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25BC scroll down (j)" })), !showScrollDown && _jsx(Text, { children: " " })] }), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsxs(Text, { color: theme.colors.textMuted, children: [maxScroll > 0 ? `j/k: scroll | ` : '', help.tabHint, " | ", help.closeHint] }) })] }));
163
197
  }
@@ -1,8 +1,60 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
3
  import { useTheme } from '../theme/index.js';
4
+ // Round border characters (DQ style)
5
+ const BORDER = {
6
+ topLeft: '╭',
7
+ topRight: '╮',
8
+ bottomLeft: '╰',
9
+ bottomRight: '╯',
10
+ horizontal: '─',
11
+ vertical: '│',
12
+ };
13
+ const SHADOW = '░';
14
+ // Calculate display width of string (full-width chars = 2, half-width = 1)
15
+ function getDisplayWidth(str) {
16
+ let width = 0;
17
+ for (const char of str) {
18
+ const code = char.charCodeAt(0);
19
+ if ((code >= 0x1100 && code <= 0x115F) ||
20
+ (code >= 0x2E80 && code <= 0x9FFF) ||
21
+ (code >= 0xAC00 && code <= 0xD7AF) ||
22
+ (code >= 0xF900 && code <= 0xFAFF) ||
23
+ (code >= 0xFE10 && code <= 0xFE1F) ||
24
+ (code >= 0xFE30 && code <= 0xFE6F) ||
25
+ (code >= 0xFF00 && code <= 0xFF60) ||
26
+ (code >= 0xFFE0 && code <= 0xFFE6) ||
27
+ (code >= 0x20000 && code <= 0x2FFFF)) {
28
+ width += 2;
29
+ }
30
+ else {
31
+ width += 1;
32
+ }
33
+ }
34
+ return width;
35
+ }
4
36
  export function KanbanColumn({ title, tasks, isActive, selectedTaskIndex, columnIndex, }) {
5
37
  const theme = useTheme();
38
+ const isLastColumn = columnIndex === 2;
39
+ const showShadow = theme.uiStyle === 'titled-box' && isLastColumn;
40
+ // Use TitledBox style for dragon-quest theme
41
+ if (theme.uiStyle === 'titled-box') {
42
+ const color = isActive ? theme.colors.borderActive : theme.colors.border;
43
+ const shadowColor = theme.colors.muted;
44
+ const titleText = `${title} (${tasks.length})`;
45
+ const titleLength = getDisplayWidth(titleText);
46
+ // Fixed width for each column (will be stretched by flexGrow)
47
+ const innerWidth = 24;
48
+ const leftDashes = 2;
49
+ const titlePadding = 2;
50
+ const rightDashes = Math.max(2, innerWidth - leftDashes - titlePadding - titleLength);
51
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, flexBasis: 0, marginRight: columnIndex < 2 ? 1 : 0, children: [_jsxs(Box, { children: [_jsx(Text, { color: color, children: BORDER.topLeft }), _jsxs(Text, { color: color, children: [BORDER.horizontal.repeat(leftDashes), " "] }), _jsx(Text, { color: isActive ? theme.colors.accent : theme.colors.textMuted, bold: isActive, children: titleText }), _jsxs(Text, { color: color, children: [" ", BORDER.horizontal.repeat(rightDashes)] }), _jsx(Text, { color: color, children: BORDER.topRight }), showShadow && _jsx(Text, { children: " " })] }), _jsxs(Box, { flexDirection: "column", minHeight: 8, children: [tasks.length === 0 ? (_jsxs(Box, { children: [_jsx(Text, { color: color, children: BORDER.vertical }), _jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(Text, { color: theme.colors.textMuted, italic: true, children: "-" }) }), _jsx(Text, { color: color, children: BORDER.vertical }), showShadow && _jsx(Text, { color: shadowColor, children: SHADOW })] })) : (tasks.map((task, index) => {
52
+ const isSelected = isActive && index === selectedTaskIndex;
53
+ const shortId = task.id.slice(0, 6);
54
+ return (_jsxs(Box, { children: [_jsx(Text, { color: color, children: BORDER.vertical }), _jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, "[", shortId, "] ", task.title] }) }), _jsx(Text, { color: color, children: BORDER.vertical }), showShadow && _jsx(Text, { color: shadowColor, children: SHADOW })] }, task.id));
55
+ })), Array.from({ length: Math.max(0, 8 - Math.max(1, tasks.length)) }).map((_, i) => (_jsxs(Box, { children: [_jsx(Text, { color: color, children: BORDER.vertical }), _jsx(Box, { paddingX: 1, flexGrow: 1, children: _jsx(Text, { children: " " }) }), _jsx(Text, { color: color, children: BORDER.vertical }), showShadow && _jsx(Text, { color: shadowColor, children: SHADOW })] }, `empty-${i}`)))] }), _jsxs(Box, { children: [_jsx(Text, { color: color, children: BORDER.bottomLeft }), _jsx(Text, { color: color, children: BORDER.horizontal.repeat(innerWidth) }), _jsx(Text, { color: color, children: BORDER.bottomRight }), showShadow && _jsx(Text, { color: shadowColor, children: SHADOW })] }), showShadow && (_jsx(Box, { children: _jsxs(Text, { color: shadowColor, children: [" ", SHADOW.repeat(innerWidth + 2)] }) }))] }));
56
+ }
57
+ // Default style
6
58
  return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, flexBasis: 0, borderStyle: theme.borders.list, borderColor: isActive ? theme.colors.borderActive : theme.colors.border, marginRight: columnIndex < 2 ? 1 : 0, children: [_jsx(Box, { paddingX: 1, justifyContent: "center", borderStyle: undefined, children: _jsxs(Text, { bold: true, color: isActive ? theme.colors.primary : theme.colors.textMuted, inverse: isActive && theme.style.tabActiveInverse, children: [title, " (", tasks.length, ")"] }) }), _jsx(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, minHeight: 8, children: tasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: "-" })) : (tasks.map((task, index) => {
7
59
  const isSelected = isActive && index === selectedTaskIndex;
8
60
  const shortId = task.id.slice(0, 6);
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type SettingsMode = 'none' | 'theme-select' | 'mode-select' | 'lang-select';
3
+ interface KanbanDQProps {
4
+ onOpenSettings?: (mode: SettingsMode) => void;
5
+ }
6
+ export declare function KanbanDQ({ onOpenSettings }: KanbanDQProps): React.ReactElement;
7
+ export {};