this repo has no description
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

there was a bunch of changes i made, but then i ran the formatter...

So now it's just one commit, whoops.

I implemented filtering, and now we're actually saving and querying tags
as json in sqlite which is nice.

I also implemented working_ids as an sql view, so that should continue
to work on TUI, but TUI is now falling behind on features compared to
js, which is good. I'm almost at the point of parity, though it's been a
long time since I've run the TUI version... The web version probably
works better now, given all the changes to the parser

I implemented little badges for both the selection and the fliter
contexts. However you can't edit them right now, only know that they're
triggered. Under the hood it's still all one command parser, which is
really nice.

Next I need to work on edit, and then saved filters.

+1221 -1017
+14
db/migrations/0002.sql
··· 1 + CREATE VIEW IF NOT EXISTS active_todos AS 2 + SELECT 3 + id, 4 + ROW_NUMBER() OVER (ORDER BY id) as working_id, 5 + description, 6 + project, 7 + tags, 8 + due, 9 + wait, 10 + priority, 11 + urgency 12 + FROM todos 13 + WHERE completed = 0; 14 +
+268 -176
mast-react-vite/src/App.tsx
··· 1 - import { useState, useEffect } from 'react' 1 + import { useState, useEffect } from "react"; 2 2 import { useQuery } from "@vlcn.io/react"; 3 - import { 4 - ColumnDef, 5 - } from "@tanstack/react-table" 6 - import { DataTable } from "@/components/ui/data-table" 7 - import * as commandParser from '@/lib/command_js.js'; 8 - import { Checkbox } from "@/components/ui/checkbox" 9 - import { ActionParser } from "@/components/ui/action-parser" 10 - import { useSelection } from "@/contexts/selection-context" 11 - import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" 12 - import { AppSidebar } from "@/components/ui/app-sidebar" 13 - import { Badge } from "@/components/ui/badge" 3 + import { ColumnDef } from "@tanstack/react-table"; 4 + import { DataTable } from "@/components/ui/data-table"; 5 + import * as commandParser from "@/lib/command_js.js"; 6 + import { Checkbox } from "@/components/ui/checkbox"; 7 + import { ActionParser } from "@/components/ui/action-parser"; 8 + import { useSelection } from "@/contexts/selection-context"; 9 + import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; 10 + import { AppSidebar } from "@/components/ui/app-sidebar"; 11 + import { Badge } from "@/components/ui/badge"; 14 12 15 13 type Todo = { 16 - id: string 17 - description: string 18 - status: 0 | 1 19 - tags: string[] 20 - project: string 21 - } 14 + id: string; 15 + description: string; 16 + status: 0 | 1; 17 + tags: string[]; 18 + project: string; 19 + }; 22 20 23 21 export const columns: ColumnDef<Todo>[] = [ 24 22 { ··· 48 46 const setNewText = table.options.meta?.setNewText; 49 47 const NewText = table.options.meta?.newText; 50 48 if (setNewText && value === true) { 51 - const calculatedRowId = row.index + 1 49 + const calculatedRowId = row.index + 1; 52 50 setNewText(NewText + calculatedRowId + ","); 53 51 } else { 54 52 // TODO: ··· 79 77 accessorKey: "tags", 80 78 header: "Tags", 81 79 cell: ({ row }) => { 82 - const formatted = JSON.parse(row.getValue("tags")) 83 - return <div className="font-medium">{formatted.join(", ")}</div> 84 - } 80 + const formatted = JSON.parse(row.getValue("tags")); 81 + return <div className="font-medium">{formatted.join(", ")}</div>; 82 + }, 85 83 }, 86 - ] 84 + ]; 87 85 88 86 function App({ ctx }) { 89 87 const [newText, setNewText] = useState(""); 88 + const { selectedItems, clearSelection, getSelectionString } = useSelection(); 89 + const [currentAction, setCurrentAction] = useState("add"); 90 90 // TODO: 91 - // We need to seperate selection state 92 - // From filter state 93 - // Since we don't want selection to impact filter when we're filtering 94 - // We want add to our selection state while we filter 95 - // But selection state should apply to modify and done 96 - // (but probably not add?) 97 - const { selectedItems } = useSelection(); 98 - const [currentAction, setCurrentAction] = useState("add"); 91 + // executeCommand with currentAction == "filter" 92 + // should set the filterContext 93 + const [filterContext, setFilterContext] = useState({}); 94 + // TODO: 95 + // Updating newText should give us a new parsedCommand 96 + // State of the viewport should be determined using parsedCommand _only_ 97 + const [parsedCommand, setParsedCommand] = useState({}); 98 + 99 + // TODO: 100 + // todos should come from filterContext if it exists 101 + // not from the currentAction + newText 102 + const conditions = []; 103 + const params = []; 104 + 105 + if (filterContext.filterDescription && filterContext.filterDescription.length > 0) { 106 + conditions.push("description LIKE ?"); 107 + params.push(`%${filterContext.filterDescription}%`); 108 + } 109 + 110 + if (filterContext.filterProject && filterContext.filterProject.length > 0) { 111 + conditions.push("project = ?"); 112 + params.push(filterContext.filterProject); 113 + } 114 + 115 + if (filterContext.filterTags && filterContext.filterTags.length > 0) { 116 + conditions.push(`EXISTS ( 117 + SELECT 1 FROM json_each(tags) 118 + WHERE json_each.value IN (${filterContext.filterTags.map(() => "?").join(",")}) 119 + )`); 120 + params.push(...filterContext.filterTags); 121 + } 122 + console.log("conditions: " + conditions); 123 + console.log("params: " + params); 99 124 100 - const todos = useQuery(ctx, `SELECT * FROM todos where completed = 0 101 - ${(currentAction === "filter") && (newText != "") ? "AND description LIKE ?" : ""}`, 102 - newText ? [`%${newText}%`] : []).data; 103 - console.log(todos[0]) 125 + const todos = useQuery( 126 + ctx, 127 + `SELECT * FROM active_todos 128 + ${conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : ""}`, 129 + params, 130 + ).data; 104 131 105 132 const handleActionChange = (action: string) => { 106 133 setCurrentAction(action); 107 - if (action === "done") { 108 - setNewText(selectedItems.size + " items selected") 109 - } else { 110 - setNewText("") 111 - } 112 134 }; 113 135 114 - useEffect(() => { 115 - if (currentAction === "done") { 116 - setNewText(selectedItems.size + " items selected") 117 - } 118 - }, [selectedItems]); 136 + // This should be handled in the actionParser component 137 + // It should show the selection tag above the input, and possibly disable it 138 + // useEffect(() => { 139 + // console.log(selectedItems) 140 + // }, [selectedItems]); 141 + 142 + //useEffect(() => { 143 + // // TODO: 144 + // // We need something here that edits todos _as though_ the action had taken place 145 + // // We probably need to have a "state" variable 146 + // // Creating 147 + // // Deleting 148 + // // Completing 149 + // // Editng? 150 + //}, [parsedCommand]); 151 + 152 + const handleNewTextChange = (newText: string) => { 153 + setNewText(newText); 154 + //try{ 155 + // // TODO selection context should go here 156 + // const parsed = commandParser.parse(getSelectionString + " " + currentAction + " " + newText); 157 + // setParsedCommand(parsed) 158 + //} catch (error) { 159 + // console.log("not updating parsed command: " + error) 160 + //} 161 + }; 162 + 163 + const handleEnter = (e) => { 164 + if (e.key === "Enter") { 165 + executeCommand(); 166 + } 167 + }; 119 168 120 169 const executeCommand = (value: string) => { 170 + console.log(getSelectionString() + " " + currentAction + " " + newText); 121 171 try { 122 - console.log(value) 123 - const parsed = commandParser.parse(value); 124 - // TODO: 125 - // We need to identify the type of the command first 126 - // Currently assumes that it is done 127 - switch (parsed.type) { 172 + // TODO selection context should go here 173 + const parsed = commandParser.parse( 174 + getSelectionString() + " " + currentAction + " " + newText, 175 + ); 176 + console.log(parsed); 177 + switch (parsed.action) { 128 178 case "done": 129 - // if (parsed.filters && parsed.filters.length > 0) { 130 - // const idFilters = parsed.filters.filter(f => f.type === "id"); 179 + // Build SQL conditions for all selection types 180 + const conditions = []; 181 + const params = []; 182 + 183 + parsed.selection.forEach((sel) => { 184 + if (sel.type === "id") { 185 + conditions.push(`id IN ( 186 + SELECT id 187 + FROM active_todos 188 + WHERE working_id IN (${sel.ids.map(() => "?").join(",")}) 189 + )`); 190 + params.push(...sel.ids); 191 + } else if (sel.type === "tag") { 192 + // TODO this is busted 193 + conditions.push(`tags LIKE ?`); 194 + params.push(`%${sel.value}%`); 195 + } else if (sel.type === "project") { 196 + conditions.push(`project = ?`); 197 + params.push(sel.value); 198 + } 199 + }); 131 200 132 - // // Get the row IDs from the table 133 - // const rowIds = idFilters.flatMap(filter => 134 - // filter.ids.map(id => todos[id - 1]?.id) 135 - // ).filter(Boolean); 201 + if (conditions.length > 0) { 202 + const sqlQuery = ` 203 + UPDATE todos 204 + SET completed = 1 205 + WHERE ${conditions.join(" OR ")} 206 + `; 136 207 137 - // Execute SQL update for each row 138 - console.log("execute done: " + selectedItems) 139 - if (selectedItems.size > 0) { 140 - selectedItems.forEach(id => { 141 - console.log("clearing " + id) 142 - ctx.db.exec( 143 - `UPDATE todos SET completed = 1 WHERE id = ?`, 144 - [id] 145 - ); 146 - }); 147 - } 148 - break; 208 + ctx.db.exec(sqlQuery, params); 209 + clearSelection(); 210 + } 211 + break; 149 212 case "add": 150 - ctx.db.exec(`INSERT INTO todos (id, description, tags, project, completed) 151 - VALUES (lower(hex(randomblob(16))), ?, ?, ?, 0)`, 152 - [ 153 - parsed.description, 154 - parsed.tags, 155 - parsed.project 156 - ]); 213 + ctx.db.exec( 214 + `INSERT INTO todos (id, description, tags, project, completed) 215 + VALUES (lower(hex(randomblob(16))), ?, json(?), ?, 0)`, 216 + [parsed.description, parsed.tags, parsed.project], 217 + ); 218 + break; 219 + case "edit": 220 + break; 221 + case "filter": 222 + setFilterContext({ 223 + // I think reconstruct is broken 224 + // filterText: parsed.reconstruct(), 225 + filterTags: JSON.parse(parsed.tags), 226 + filterProject: parsed.project, 227 + filterDescription: parsed.description, 228 + }); 229 + console.log({ 230 + // I think reconstruct is broken 231 + // filterText: parsed.reconstruct(), 232 + filterTags: JSON.parse(parsed.tags), 233 + filterProject: parsed.project, 234 + filterDescription: parsed.description, 235 + }); 157 236 break; 158 237 } 159 238 setNewText(""); 239 + setParsedCommand({}); 160 240 } catch (error) { 161 241 // TODO: 162 242 // This is actually bad ··· 165 245 console.log(error); 166 246 return; 167 247 } 168 - } 248 + }; 169 249 170 - const parseTodos = (e) => { 171 - // On enter execute the command 172 - if (e.key === "Enter") { 173 - console.log("enter confirmed") 174 - executeCommand(currentAction + " " + e.target.value); 175 - } 176 - // React to key presses for selection 177 - // TODO: 178 - // We should use the react way 179 - // Where we update based on changes to newText 180 - // Rather than changes to the input value 181 - else if (e.target.value.trim() !== "") { 182 - try { 183 - const parsed = commandParser.parse(currentAction + " " + e.target.value); 184 - // TODO: 185 - // We want to reactively update based on command still 186 - // Right now this assumes the command is filter 250 + //const parseTodos = (e) => { 251 + // // On enter execute the command 252 + // if (e.key === "Enter") { 253 + // // TODO We should pass selectionContext here 254 + // executeCommand(currentAction + " " + e.target.value); 255 + // } 256 + // // React to key presses for selection 257 + // else if (e.target.value.trim() !== "") { 258 + // try { 259 + // // TODO: 260 + // // This allows editing selectionContext from the newText field 261 + // // But we've moved selectionContext into it's own place 262 + // // So we shouldn't have to do this here 263 + // const parsed = commandParser.parse(currentAction + " " + e.target.value); 264 + // if (parsed.filters && parsed.filters.length > 0) { 265 + // const idFilters = parsed.filters.filter(f => f.type === "id"); 266 + // // Create new selection state 267 + // const newSelection = {}; 268 + // idFilters.forEach(filter => { 269 + // filter.ids.forEach(id => { 270 + // newSelection[id - 1] = true; 271 + // }) 272 + // }); 187 273 188 - if (parsed.filters && parsed.filters.length > 0) { 189 - const idFilters = parsed.filters.filter(f => f.type === "id"); 190 - 191 - // TODO: 192 - // We should probably have selection state react to changes on NewText 193 - // Rather than manually updating here 194 - 195 - // Create new selection state 196 - const newSelection = {}; 197 - idFilters.forEach(filter => { 198 - filter.ids.forEach(id => { 199 - newSelection[id - 1] = true; 200 - }) 201 - }); 202 - 203 - } 204 - } catch (error) { 205 - console.log("Unable to parse field onUpdate") 206 - return; 207 - } 208 - } else { 209 - } 210 - } 274 + // } 275 + // } catch (error) { 276 + // console.log("Unable to parse field onUpdate") 277 + // return; 278 + // } 279 + // } else { 280 + // } 281 + //} 211 282 212 283 return ( 213 - <> 214 - <SidebarProvider defaultOpen={false} className="h-screen"> 215 - <div className="hidden md:flex flex-col w-full h-svh"> 216 - <AppSidebar/> 217 - <div className="bg-muted h-14 w-full absolute top-0 " /> 218 - <section className="flex-1 container py-12 h-[calc(100vh-theme(spacing.4))] overflow-hidden relative"> 219 - <SidebarTrigger className="absolute top-4 left-8 border border-foreground"/> 220 - {selectedItems.size > 0 && ( 221 - <Badge className="absolute top-4 right-12"> {selectedItems.size} selected </Badge> 222 - )} 223 - <div className="h-4" /> 224 - <ActionParser type="text" 225 - className="bg-background" 226 - value={newText} 227 - onKeyUp={parseTodos} 228 - onActionChange={handleActionChange} 229 - onChange={(e) => setNewText(e.target.value)} 230 - onSubmit={executeCommand} 231 - /> 232 - <div className="h-2" /> 233 - <div className="flex-1 w-full"> 234 - <DataTable 235 - data={todos} 236 - /> 237 - </div> 238 - </section> 239 - </div> 284 + <> 285 + <SidebarProvider defaultOpen={false} className="h-screen"> 286 + <div className="hidden md:flex flex-col w-full h-svh"> 287 + <AppSidebar /> 288 + <div className="bg-muted h-14 w-full absolute top-0 " /> 289 + <section className="flex-1 container py-12 h-[calc(100vh-theme(spacing.4))] overflow-hidden relative"> 290 + <SidebarTrigger className="absolute top-4 left-8 border border-foreground" /> 291 + <div className="absolute top-4 right-12 flex gap-2"> 292 + {selectedItems.size > 0 && ( 293 + <Badge variant="default"> {selectedItems.size} selected </Badge> 294 + )} 295 + {Object.keys(filterContext).length > 0 && ( 296 + <Badge variant="secondary" className="cursor-pointer"> 297 + {todos.length} items found 298 + <button 299 + onClick={() => setFilterContext({})} 300 + className="hover:bg-muted rounded-full p-1" 301 + > 302 + 303 + </button> 304 + </Badge> 305 + )} 306 + </div> 307 + <div className="h-4" /> 308 + <ActionParser 309 + type="text" 310 + className="bg-background" 311 + value={newText} 312 + onActionChange={handleActionChange} 313 + onKeyUp={(e) => handleEnter(e)} 314 + onChange={(e) => handleNewTextChange(e.target.value)} 315 + onSubmit={executeCommand} 316 + /> 317 + <div className="h-2" /> 318 + <div className="flex-1 w-full"> 319 + <DataTable data={todos} /> 320 + </div> 321 + </section> 322 + </div> 240 323 241 - {/* Mobile view layout - shown only on mobile */} 242 - <div className="md:hidden flex left-0 w-full h-full flex flex-col items-center pb-2"> 243 - <AppSidebar/> 244 - <section className="flex-1 py-12 h-[calc(100vh-theme(spacing.4))] w-full overflow-hidden relative"> 245 - <div className="flex-1 bg-muted h-14 absolute inset-x-0 top-0 " /> 246 - <SidebarTrigger className="fixed top-4 border border-foreground left-8"/> 247 - {selectedItems.size > 0 && ( 248 - <Badge className="absolute top-4 right-8"> {selectedItems.size} selected </Badge> 249 - )} 250 - <ActionParser type="text" 251 - className="bg-background" 252 - value={newText} 253 - onKeyUp={parseTodos} 254 - onActionChange={handleActionChange} 255 - onChange={(e) => setNewText(e.target.value)} 256 - onSubmit={executeCommand} 257 - /> 258 - <div className="h-2" /> 259 - <div className="flex-1 w-full h-full"> 260 - <DataTable 261 - columns={columns} 262 - data={todos} 263 - setNewText={setNewText} 264 - newText={newText} 265 - /> 266 - </div> 267 - </section> 268 - </div> 269 - </SidebarProvider> 270 - </> 271 - ) 324 + {/* Mobile view layout - shown only on mobile */} 325 + <div className="md:hidden flex left-0 w-full h-full flex flex-col items-center pb-2"> 326 + <AppSidebar /> 327 + <section className="flex-1 py-12 h-[calc(100vh-theme(spacing.4))] w-full overflow-hidden relative"> 328 + <div className="flex-1 bg-muted border-b border-foreground h-14 absolute inset-x-0 top-0 " /> 329 + <SidebarTrigger className="fixed top-4 border border-foreground left-8" /> 330 + <div className="absolute top-4 right-12 flex gap-2"> 331 + {selectedItems.size > 0 && ( 332 + <Badge variant="default"> {selectedItems.size} selected </Badge> 333 + )} 334 + {Object.keys(filterContext).length > 0 && ( 335 + <Badge variant="secondary" className="cursor-pointer"> 336 + {todos.length} items found 337 + <button 338 + onClick={() => setFilterContext({})} 339 + className="hover:bg-muted rounded-full p-1" 340 + > 341 + 342 + </button> 343 + </Badge> 344 + )} 345 + </div> 346 + <ActionParser 347 + type="text" 348 + className="bg-background" 349 + value={newText} 350 + onActionChange={handleActionChange} 351 + onKeyUp={(e) => handleEnter(e)} 352 + onChange={(e) => handleNewTextChange(e.target.value)} 353 + onSubmit={executeCommand} 354 + /> 355 + <div className="h-2" /> 356 + <div className="flex-1 w-full h-full"> 357 + <DataTable data={todos} /> 358 + </div> 359 + </section> 360 + </div> 361 + </SidebarProvider> 362 + </> 363 + ); 272 364 } 273 365 274 - export default App 366 + export default App;
+75 -53
mast-react-vite/src/components/ui/action-parser.tsx
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - import { cn } from "@/lib/utils" 4 - import { Button } from "@/components/ui/button" 5 - import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" 6 - import { Search, Plus, Pencil, Check } from "lucide-react" 3 + import { cn } from "@/lib/utils"; 4 + import { Button } from "@/components/ui/button"; 5 + import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; 6 + import { Search, Plus, Pencil, Check } from "lucide-react"; 7 7 8 8 // Add onActionChange to InputProps interface 9 9 export interface InputProps 10 10 extends React.InputHTMLAttributes<HTMLInputElement> { 11 11 onActionChange?: (action: string) => void; 12 - onSubmit?: (value: string) => void; // Add this prop 12 + onSubmit?: () => void; // Add this prop 13 13 } 14 14 15 15 const ActionParser = React.forwardRef<HTMLInputElement, InputProps>( 16 16 ({ className, type, onActionChange, onSubmit, value, ...props }, ref) => { 17 - const [selectedIndex, setSelectedIndex] = React.useState(0) 18 - const [selectedToggle, setSelectedToggle] = React.useState("add") 19 - const options = ["add", "filter", "done", "delete"] 20 - 21 - const handleWheel = (event: React.WheelEvent) => { 22 - event.preventDefault() 23 - if (event.deltaY > 0) { 24 - const newIndex = (selectedIndex + 1) % options.length 25 - setSelectedIndex(newIndex) 26 - onActionChange?.(options[newIndex]) 27 - } else { 28 - const newIndex = (selectedIndex - 1 + options.length) % options.length 29 - setSelectedIndex(newIndex) 30 - onActionChange?.(options[newIndex]) 31 - } 32 - } 17 + const [selectedIndex, setSelectedIndex] = React.useState(0); 18 + const [selectedToggle, setSelectedToggle] = React.useState("add"); 19 + const options = ["add", "filter", "done", "delete"]; 33 20 21 + //const handleWheel = (event: React.WheelEvent) => { 22 + // event.preventDefault() 23 + // if (event.deltaY > 0) { 24 + // const newIndex = (selectedIndex + 1) % options.length 25 + // setSelectedIndex(newIndex) 26 + // onActionChange?.(options[newIndex]) 27 + // } else { 28 + // const newIndex = (selectedIndex - 1 + options.length) % options.length 29 + // setSelectedIndex(newIndex) 30 + // onActionChange?.(options[newIndex]) 31 + // } 32 + //} 34 33 35 34 const handleToggleChange = (value: string) => { 36 35 if (value) { 37 - setSelectedToggle(value) 38 - onActionChange?.(value) 36 + setSelectedToggle(value); 37 + onActionChange?.(value); 39 38 } 40 - } 39 + }; 41 40 42 41 const handleSubmit = () => { 43 - if (onSubmit && value) { 44 - console.log("trigger button") 45 - onSubmit(selectedToggle + " " + value); 42 + if (onSubmit) { 43 + console.log("trigger button"); 44 + onSubmit(); 46 45 } 47 - } 46 + }; 48 47 49 48 return ( 50 - <div className="flex flex-col group" onWheel={handleWheel}> 49 + <div className="flex flex-col group"> 51 50 <div className="hidden md:flex flex-col w-full"> 52 51 <div className="flex w-full"> 53 52 <div className="relative w-auto"> 54 - <ToggleGroup type="single" 53 + <ToggleGroup 54 + type="single" 55 55 value={selectedToggle} 56 56 onValueChange={handleToggleChange} 57 57 defaultValue="add" ··· 59 59 "h-11 border rounded-t-md border-b-0 bg-transparent text-sm cursor-default pt-1/2 pl-1 pr-1 pb-1/2", 60 60 "shadow-sm transition-colors duration-0 text-center appearance-none group-focus-within:border-ring", 61 61 "rounded-t-md rounded-b-none", 62 - "focus:border-ring focus-within:border-ring" 63 - )}> 62 + "focus:border-ring focus-within:border-ring", 63 + )} 64 + > 64 65 <ToggleGroupItem value="filter" aria-label="filter tasks"> 65 66 <Search className="h-4 w-4 rounded-none" /> 66 67 </ToggleGroupItem> 67 - <ToggleGroupItem value="modify" aria-label="Modify selected task(s)"> 68 + <ToggleGroupItem 69 + value="modify" 70 + aria-label="Modify selected task(s)" 71 + > 68 72 <Pencil className="h-4 w-4" /> 69 73 </ToggleGroupItem> 70 74 <ToggleGroupItem value="add" aria-label="Add a task"> 71 75 <Plus className="h-4 w-4" /> 72 76 </ToggleGroupItem> 73 - <ToggleGroupItem value="done" aria-label="Toggle selected task(s) done"> 77 + <ToggleGroupItem 78 + value="done" 79 + aria-label="Toggle selected task(s) done" 80 + > 74 81 <Check className="h-4 w-4" /> 75 82 </ToggleGroupItem> 76 83 </ToggleGroup> ··· 87 94 "shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1", 88 95 "disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 89 96 "group-focus-within:border-ring", 90 - className 97 + className, 91 98 )} 92 99 ref={ref} 93 100 value={value} ··· 106 113 {/* Mobile view layout - shown only on mobile */} 107 114 <div className="md:hidden fixed bottom-0 left-0 w-full z-50 flex flex-col items-center pb-2"> 108 115 <div className="relative w-auto"> 109 - <ToggleGroup type="single" 116 + <ToggleGroup 117 + type="single" 110 118 value={selectedToggle} 111 119 onValueChange={handleToggleChange} 112 120 defaultValue="add" ··· 114 122 "h-11 border rounded-t-md border-b-0 bg-background text-sm cursor-default pt-1/2 pl-1 pr-1 pb-1/2", 115 123 "shadow-sm transition-colors duration-0 text-center appearance-none group-focus-within:border-ring", 116 124 "rounded-t-md rounded-b-none", 117 - "focus:border-ring" 118 - )}> 119 - <ToggleGroupItem value="filter" aria-label="filter tasks" className="rounded-md mx-1 p-2"> 125 + "focus:border-ring", 126 + )} 127 + > 128 + <ToggleGroupItem 129 + value="filter" 130 + aria-label="filter tasks" 131 + className="rounded-md mx-1 p-2" 132 + > 120 133 <Search className="h-5 w-5" /> 121 134 </ToggleGroupItem> 122 - <ToggleGroupItem value="modify" aria-label="Modify selected task(s)" className="rounded-md mx-1 p-2"> 135 + <ToggleGroupItem 136 + value="modify" 137 + aria-label="Modify selected task(s)" 138 + className="rounded-md mx-1 p-2" 139 + > 123 140 <Pencil className="h-5 w-5" /> 124 141 </ToggleGroupItem> 125 - <ToggleGroupItem value="add" aria-label="Add a task" className="rounded-md mx-1 p-2"> 142 + <ToggleGroupItem 143 + value="add" 144 + aria-label="Add a task" 145 + className="rounded-md mx-1 p-2" 146 + > 126 147 <Plus className="h-5 w-5" /> 127 148 </ToggleGroupItem> 128 - <ToggleGroupItem value="done" aria-label="Toggle selected task(s) done" className="rounded-md mx-1 p-2"> 149 + <ToggleGroupItem 150 + value="done" 151 + aria-label="Toggle selected task(s) done" 152 + className="rounded-md mx-1 p-2" 153 + > 129 154 <Check className="h-5 w-5" /> 130 155 </ToggleGroupItem> 131 156 </ToggleGroup> ··· 144 169 // Modify border radius based on viewport size 145 170 "md:rounded-r-md md:rounded-tl-none md:rounded-bl-md", 146 171 "rounded-md", // For mobile view (full rounded corners) 147 - className 148 - 172 + className, 149 173 )} 150 174 ref={ref} 151 175 value={value} ··· 159 183 160 184 </Button> 161 185 </div> 162 - 163 186 </div> 164 - </div > 165 - 166 - ) 167 - } 168 - ) 169 - ActionParser.displayName = "ActionParser" 187 + </div> 188 + ); 189 + }, 190 + ); 191 + ActionParser.displayName = "ActionParser"; 170 192 171 - export { ActionParser } 193 + export { ActionParser };
+3 -5
mast-react-vite/src/components/ui/app-sidebar.tsx
··· 4 4 SidebarFooter, 5 5 SidebarGroup, 6 6 SidebarHeader, 7 - } from "@/components/ui/sidebar" 8 - 7 + } from "@/components/ui/sidebar"; 8 + 9 9 export function AppSidebar() { 10 10 return ( 11 11 <Sidebar> ··· 16 16 </SidebarContent> 17 17 <SidebarFooter /> 18 18 </Sidebar> 19 - ) 19 + ); 20 20 } 21 - 22 -
+7 -7
mast-react-vite/src/components/ui/badge.tsx
··· 1 - import * as React from "react" 2 - import { cva, type VariantProps } from "class-variance-authority" 1 + import * as React from "react"; 2 + import { cva, type VariantProps } from "class-variance-authority"; 3 3 4 - import { cn } from "@/lib/utils" 4 + import { cn } from "@/lib/utils"; 5 5 6 6 const badgeVariants = cva( 7 7 "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", ··· 20 20 defaultVariants: { 21 21 variant: "default", 22 22 }, 23 - } 24 - ) 23 + }, 24 + ); 25 25 26 26 export interface BadgeProps 27 27 extends React.HTMLAttributes<HTMLDivElement>, ··· 30 30 function Badge({ className, variant, ...props }: BadgeProps) { 31 31 return ( 32 32 <div className={cn(badgeVariants({ variant }), className)} {...props} /> 33 - ) 33 + ); 34 34 } 35 35 36 - export { Badge, badgeVariants } 36 + export { Badge, badgeVariants };
+13 -13
mast-react-vite/src/components/ui/button.tsx
··· 1 - import * as React from "react" 2 - import { Slot } from "@radix-ui/react-slot" 3 - import { cva, type VariantProps } from "class-variance-authority" 1 + import * as React from "react"; 2 + import { Slot } from "@radix-ui/react-slot"; 3 + import { cva, type VariantProps } from "class-variance-authority"; 4 4 5 - import { cn } from "@/lib/utils" 5 + import { cn } from "@/lib/utils"; 6 6 7 7 const buttonVariants = cva( 8 8 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", ··· 31 31 variant: "default", 32 32 size: "default", 33 33 }, 34 - } 35 - ) 34 + }, 35 + ); 36 36 37 37 export interface ButtonProps 38 38 extends React.ButtonHTMLAttributes<HTMLButtonElement>, 39 39 VariantProps<typeof buttonVariants> { 40 - asChild?: boolean 40 + asChild?: boolean; 41 41 } 42 42 43 43 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 44 44 ({ className, variant, size, asChild = false, ...props }, ref) => { 45 - const Comp = asChild ? Slot : "button" 45 + const Comp = asChild ? Slot : "button"; 46 46 return ( 47 47 <Comp 48 48 className={cn(buttonVariants({ variant, size, className }))} 49 49 ref={ref} 50 50 {...props} 51 51 /> 52 - ) 53 - } 54 - ) 55 - Button.displayName = "Button" 52 + ); 53 + }, 54 + ); 55 + Button.displayName = "Button"; 56 56 57 - export { Button, buttonVariants } 57 + export { Button, buttonVariants };
+8 -8
mast-react-vite/src/components/ui/checkbox.tsx
··· 1 - import * as React from "react" 2 - import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 3 - import { cn } from "@/lib/utils" 4 - import { CheckIcon } from "@radix-ui/react-icons" 1 + import * as React from "react"; 2 + import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 3 + import { cn } from "@/lib/utils"; 4 + import { CheckIcon } from "@radix-ui/react-icons"; 5 5 6 6 const Checkbox = React.forwardRef< 7 7 React.ElementRef<typeof CheckboxPrimitive.Root>, ··· 11 11 ref={ref} 12 12 className={cn( 13 13 "flex h-4 w-4 shrink-0 items-center justify-center text-current rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", 14 - className 14 + className, 15 15 )} 16 16 {...props} 17 17 > ··· 21 21 <CheckIcon className="h-3 w-3" /> 22 22 </CheckboxPrimitive.Indicator> 23 23 </CheckboxPrimitive.Root> 24 - )) 25 - Checkbox.displayName = CheckboxPrimitive.Root.displayName 24 + )); 25 + Checkbox.displayName = CheckboxPrimitive.Root.displayName; 26 26 27 - export { Checkbox } 27 + export { Checkbox };
+35 -46
mast-react-vite/src/components/ui/data-table.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import { useState } from "react" 4 - import { ScrollArea } from "@/components/ui/scroll-area" 5 - import { Task } from "@/components/ui/task" 3 + import { useState } from "react"; 4 + import { ScrollArea } from "@/components/ui/scroll-area"; 5 + import { Task } from "@/components/ui/task"; 6 6 7 7 import { 8 8 ColumnSizingState, 9 9 getCoreRowModel, 10 10 useReactTable, 11 - } from "@tanstack/react-table" 11 + } from "@tanstack/react-table"; 12 12 13 13 interface DataTableProps<TData> { 14 - data: TData[] 14 + data: TData[]; 15 15 } 16 16 17 - export function DataTable<TData>({ data, }: DataTableProps<TData>) { 17 + export function DataTable<TData>({ data }: DataTableProps<TData>) { 18 + return ( 19 + <> 20 + <div className="hidden md:flex flex-col w-full"> 21 + <ScrollArea className="h-[calc(100vh-12rem)] rounded-md border"> 22 + {data.length ? ( 23 + data.map((item, index) => <Task key={index} data={item} />) 24 + ) : ( 25 + <div className="text-center py-4 text-muted-foreground"> 26 + No results. 27 + </div> 28 + )} 29 + </ScrollArea> 30 + </div> 18 31 19 - return ( 20 - 21 - <> 22 - <div className="hidden md:flex flex-col w-full"> 23 - <ScrollArea className="h-[calc(100vh-12rem)] rounded-md border"> 24 - {data.length ? ( 25 - data.map((item) => ( 26 - <Task 27 - data={item} 28 - /> 29 - )) 30 - ) : ( 31 - <div className="text-center py-4 text-muted-foreground"> 32 - No results. 33 - </div> 34 - )} 35 - </ScrollArea> 36 - </div> 37 - 38 - {/* Mobile view layout - shown only on mobile */} 39 - <div className="md:hidden"> 40 - <ScrollArea className="min-h-[120%] h-[calc(100vh)] rounded-md border"> 41 - {data.length ? ( 42 - data.map((item) => ( 43 - <Task 44 - data={item} 45 - /> 46 - )) 47 - ) : ( 48 - <div className="text-center py-4 text-muted-foreground"> 49 - No results. 50 - </div> 51 - )} 52 - </ScrollArea> 53 - </div> 54 - </> 55 - ) 56 - 32 + {/* Mobile view layout - shown only on mobile */} 33 + <div className="md:hidden"> 34 + <ScrollArea className="min-h-[120%] h-[calc(100vh)] border"> 35 + {data.length ? ( 36 + data.map((item, index) => <Task key={index} data={item} />) 37 + ) : ( 38 + <div className="text-center py-4 text-muted-foreground"> 39 + No results. 40 + </div> 41 + )} 42 + <div className="h-[10rem]" /> 43 + </ScrollArea> 44 + </div> 45 + </> 46 + ); 57 47 } 58 -
+8 -8
mast-react-vite/src/components/ui/input.tsx
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - import { cn } from "@/lib/utils" 3 + import { cn } from "@/lib/utils"; 4 4 5 5 const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( 6 6 ({ className, type, ...props }, ref) => { ··· 9 9 type={type} 10 10 className={cn( 11 11 "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 12 - className 12 + className, 13 13 )} 14 14 ref={ref} 15 15 {...props} 16 16 /> 17 - ) 18 - } 19 - ) 20 - Input.displayName = "Input" 17 + ); 18 + }, 19 + ); 20 + Input.displayName = "Input"; 21 21 22 - export { Input } 22 + export { Input };
+9 -9
mast-react-vite/src/components/ui/scroll-area.tsx
··· 1 - import * as React from "react" 2 - import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 1 + import * as React from "react"; 2 + import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; 3 3 4 - import { cn } from "@/lib/utils" 4 + import { cn } from "@/lib/utils"; 5 5 6 6 const ScrollArea = React.forwardRef< 7 7 React.ElementRef<typeof ScrollAreaPrimitive.Root>, ··· 18 18 <ScrollBar /> 19 19 <ScrollAreaPrimitive.Corner /> 20 20 </ScrollAreaPrimitive.Root> 21 - )) 22 - ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 21 + )); 22 + ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 23 23 24 24 const ScrollBar = React.forwardRef< 25 25 React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, ··· 34 34 "h-full w-2.5 border-l border-l-transparent p-[1px]", 35 35 orientation === "horizontal" && 36 36 "h-2.5 flex-col border-t border-t-transparent p-[1px]", 37 - className 37 + className, 38 38 )} 39 39 {...props} 40 40 > 41 41 <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" /> 42 42 </ScrollAreaPrimitive.ScrollAreaScrollbar> 43 - )) 44 - ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 43 + )); 44 + ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 45 45 46 - export { ScrollArea, ScrollBar } 46 + export { ScrollArea, ScrollBar };
+9 -9
mast-react-vite/src/components/ui/separator.tsx
··· 1 - import * as React from "react" 2 - import * as SeparatorPrimitive from "@radix-ui/react-separator" 1 + import * as React from "react"; 2 + import * as SeparatorPrimitive from "@radix-ui/react-separator"; 3 3 4 - import { cn } from "@/lib/utils" 4 + import { cn } from "@/lib/utils"; 5 5 6 6 const Separator = React.forwardRef< 7 7 React.ElementRef<typeof SeparatorPrimitive.Root>, ··· 9 9 >( 10 10 ( 11 11 { className, orientation = "horizontal", decorative = true, ...props }, 12 - ref 12 + ref, 13 13 ) => ( 14 14 <SeparatorPrimitive.Root 15 15 ref={ref} ··· 18 18 className={cn( 19 19 "shrink-0 bg-border", 20 20 orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", 21 - className 21 + className, 22 22 )} 23 23 {...props} 24 24 /> 25 - ) 26 - ) 27 - Separator.displayName = SeparatorPrimitive.Root.displayName 25 + ), 26 + ); 27 + Separator.displayName = SeparatorPrimitive.Root.displayName; 28 28 29 - export { Separator } 29 + export { Separator };
+28 -28
mast-react-vite/src/components/ui/sheet.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import * as SheetPrimitive from "@radix-ui/react-dialog" 5 - import { cva, type VariantProps } from "class-variance-authority" 6 - import { cn } from "@/lib/utils" 7 - import { Cross2Icon } from "@radix-ui/react-icons" 3 + import * as React from "react"; 4 + import * as SheetPrimitive from "@radix-ui/react-dialog"; 5 + import { cva, type VariantProps } from "class-variance-authority"; 6 + import { cn } from "@/lib/utils"; 7 + import { Cross2Icon } from "@radix-ui/react-icons"; 8 8 9 - const Sheet = SheetPrimitive.Root 9 + const Sheet = SheetPrimitive.Root; 10 10 11 - const SheetTrigger = SheetPrimitive.Trigger 11 + const SheetTrigger = SheetPrimitive.Trigger; 12 12 13 - const SheetClose = SheetPrimitive.Close 13 + const SheetClose = SheetPrimitive.Close; 14 14 15 - const SheetPortal = SheetPrimitive.Portal 15 + const SheetPortal = SheetPrimitive.Portal; 16 16 17 17 const SheetOverlay = React.forwardRef< 18 18 React.ElementRef<typeof SheetPrimitive.Overlay>, ··· 21 21 <SheetPrimitive.Overlay 22 22 className={cn( 23 23 "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", 24 - className 24 + className, 25 25 )} 26 26 {...props} 27 27 ref={ref} 28 28 /> 29 - )) 30 - SheetOverlay.displayName = SheetPrimitive.Overlay.displayName 29 + )); 30 + SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; 31 31 32 32 const sheetVariants = cva( 33 33 "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out", ··· 45 45 defaultVariants: { 46 46 side: "right", 47 47 }, 48 - } 49 - ) 48 + }, 49 + ); 50 50 51 51 interface SheetContentProps 52 52 extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, ··· 70 70 {children} 71 71 </SheetPrimitive.Content> 72 72 </SheetPortal> 73 - )) 74 - SheetContent.displayName = SheetPrimitive.Content.displayName 73 + )); 74 + SheetContent.displayName = SheetPrimitive.Content.displayName; 75 75 76 76 const SheetHeader = ({ 77 77 className, ··· 80 80 <div 81 81 className={cn( 82 82 "flex flex-col space-y-2 text-center sm:text-left", 83 - className 83 + className, 84 84 )} 85 85 {...props} 86 86 /> 87 - ) 88 - SheetHeader.displayName = "SheetHeader" 87 + ); 88 + SheetHeader.displayName = "SheetHeader"; 89 89 90 90 const SheetFooter = ({ 91 91 className, ··· 94 94 <div 95 95 className={cn( 96 96 "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", 97 - className 97 + className, 98 98 )} 99 99 {...props} 100 100 /> 101 - ) 102 - SheetFooter.displayName = "SheetFooter" 101 + ); 102 + SheetFooter.displayName = "SheetFooter"; 103 103 104 104 const SheetTitle = React.forwardRef< 105 105 React.ElementRef<typeof SheetPrimitive.Title>, ··· 110 110 className={cn("text-lg font-semibold text-foreground", className)} 111 111 {...props} 112 112 /> 113 - )) 114 - SheetTitle.displayName = SheetPrimitive.Title.displayName 113 + )); 114 + SheetTitle.displayName = SheetPrimitive.Title.displayName; 115 115 116 116 const SheetDescription = React.forwardRef< 117 117 React.ElementRef<typeof SheetPrimitive.Description>, ··· 122 122 className={cn("text-sm text-muted-foreground", className)} 123 123 {...props} 124 124 /> 125 - )) 126 - SheetDescription.displayName = SheetPrimitive.Description.displayName 125 + )); 126 + SheetDescription.displayName = SheetPrimitive.Description.displayName; 127 127 128 128 export { 129 129 Sheet, ··· 136 136 SheetFooter, 137 137 SheetTitle, 138 138 SheetDescription, 139 - } 139 + };
+178 -170
mast-react-vite/src/components/ui/sidebar.tsx
··· 1 - import * as React from "react" 2 - import { Slot } from "@radix-ui/react-slot" 3 - import { VariantProps, cva } from "class-variance-authority" 4 - import { useIsMobile } from "@/hooks/use-mobile" 5 - import { cn } from "@/lib/utils" 6 - import { Button } from "@/components/ui/button" 7 - import { Input } from "@/components/ui/input" 8 - import { Separator } from "@/components/ui/separator" 1 + import * as React from "react"; 2 + import { Slot } from "@radix-ui/react-slot"; 3 + import { VariantProps, cva } from "class-variance-authority"; 4 + import { useIsMobile } from "@/hooks/use-mobile"; 5 + import { cn } from "@/lib/utils"; 6 + import { Button } from "@/components/ui/button"; 7 + import { Input } from "@/components/ui/input"; 8 + import { Separator } from "@/components/ui/separator"; 9 9 import { 10 10 Sheet, 11 11 SheetContent, 12 12 SheetDescription, 13 13 SheetHeader, 14 14 SheetTitle, 15 - } from "@/components/ui/sheet" 16 - import { Skeleton } from "@/components/ui/skeleton" 15 + } from "@/components/ui/sheet"; 16 + import { Skeleton } from "@/components/ui/skeleton"; 17 17 import { 18 18 Tooltip, 19 19 TooltipContent, 20 20 TooltipProvider, 21 21 TooltipTrigger, 22 - } from "@/components/ui/tooltip" 23 - import { ViewVerticalIcon } from "@radix-ui/react-icons" 22 + } from "@/components/ui/tooltip"; 23 + import { ViewVerticalIcon } from "@radix-ui/react-icons"; 24 24 25 - const SIDEBAR_COOKIE_NAME = "sidebar_state" 26 - const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 27 - const SIDEBAR_WIDTH = "16rem" 28 - const SIDEBAR_WIDTH_MOBILE = "18rem" 29 - const SIDEBAR_WIDTH_ICON = "3rem" 30 - const SIDEBAR_KEYBOARD_SHORTCUT = "b" 25 + const SIDEBAR_COOKIE_NAME = "sidebar_state"; 26 + const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; 27 + const SIDEBAR_WIDTH = "16rem"; 28 + const SIDEBAR_WIDTH_MOBILE = "18rem"; 29 + const SIDEBAR_WIDTH_ICON = "3rem"; 30 + const SIDEBAR_KEYBOARD_SHORTCUT = "b"; 31 31 32 32 type SidebarContext = { 33 - state: "expanded" | "collapsed" 34 - open: boolean 35 - setOpen: (open: boolean) => void 36 - openMobile: boolean 37 - setOpenMobile: (open: boolean) => void 38 - isMobile: boolean 39 - toggleSidebar: () => void 40 - } 33 + state: "expanded" | "collapsed"; 34 + open: boolean; 35 + setOpen: (open: boolean) => void; 36 + openMobile: boolean; 37 + setOpenMobile: (open: boolean) => void; 38 + isMobile: boolean; 39 + toggleSidebar: () => void; 40 + }; 41 41 42 - const SidebarContext = React.createContext<SidebarContext | null>(null) 42 + const SidebarContext = React.createContext<SidebarContext | null>(null); 43 43 44 44 function useSidebar() { 45 - const context = React.useContext(SidebarContext) 45 + const context = React.useContext(SidebarContext); 46 46 if (!context) { 47 - throw new Error("useSidebar must be used within a SidebarProvider.") 47 + throw new Error("useSidebar must be used within a SidebarProvider."); 48 48 } 49 49 50 - return context 50 + return context; 51 51 } 52 52 53 53 const SidebarProvider = React.forwardRef< 54 54 HTMLDivElement, 55 55 React.ComponentProps<"div"> & { 56 - defaultOpen?: boolean 57 - open?: boolean 58 - onOpenChange?: (open: boolean) => void 56 + defaultOpen?: boolean; 57 + open?: boolean; 58 + onOpenChange?: (open: boolean) => void; 59 59 } 60 60 >( 61 61 ( ··· 68 68 children, 69 69 ...props 70 70 }, 71 - ref 71 + ref, 72 72 ) => { 73 - const isMobile = useIsMobile() 74 - const [openMobile, setOpenMobile] = React.useState(false) 73 + const isMobile = useIsMobile(); 74 + const [openMobile, setOpenMobile] = React.useState(false); 75 75 76 76 // This is the internal state of the sidebar. 77 77 // We use openProp and setOpenProp for control from outside the component. 78 - const [_open, _setOpen] = React.useState(defaultOpen) 79 - const open = openProp ?? _open 78 + const [_open, _setOpen] = React.useState(defaultOpen); 79 + const open = openProp ?? _open; 80 80 const setOpen = React.useCallback( 81 81 (value: boolean | ((value: boolean) => boolean)) => { 82 - const openState = typeof value === "function" ? value(open) : value 82 + const openState = typeof value === "function" ? value(open) : value; 83 83 if (setOpenProp) { 84 - setOpenProp(openState) 84 + setOpenProp(openState); 85 85 } else { 86 - _setOpen(openState) 86 + _setOpen(openState); 87 87 } 88 88 89 89 // This sets the cookie to keep the sidebar state. 90 - document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` 90 + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; 91 91 }, 92 - [setOpenProp, open] 93 - ) 92 + [setOpenProp, open], 93 + ); 94 94 95 95 // Helper to toggle the sidebar. 96 96 const toggleSidebar = React.useCallback(() => { 97 97 return isMobile 98 98 ? setOpenMobile((open) => !open) 99 - : setOpen((open) => !open) 100 - }, [isMobile, setOpen, setOpenMobile]) 99 + : setOpen((open) => !open); 100 + }, [isMobile, setOpen, setOpenMobile]); 101 101 102 102 // Adds a keyboard shortcut to toggle the sidebar. 103 103 React.useEffect(() => { ··· 106 106 event.key === SIDEBAR_KEYBOARD_SHORTCUT && 107 107 (event.metaKey || event.ctrlKey) 108 108 ) { 109 - event.preventDefault() 110 - toggleSidebar() 109 + event.preventDefault(); 110 + toggleSidebar(); 111 111 } 112 - } 112 + }; 113 113 114 - window.addEventListener("keydown", handleKeyDown) 115 - return () => window.removeEventListener("keydown", handleKeyDown) 116 - }, [toggleSidebar]) 114 + window.addEventListener("keydown", handleKeyDown); 115 + return () => window.removeEventListener("keydown", handleKeyDown); 116 + }, [toggleSidebar]); 117 117 118 118 // We add a state so that we can do data-state="expanded" or "collapsed". 119 119 // This makes it easier to style the sidebar with Tailwind classes. 120 - const state = open ? "expanded" : "collapsed" 120 + const state = open ? "expanded" : "collapsed"; 121 121 122 122 const contextValue = React.useMemo<SidebarContext>( 123 123 () => ({ ··· 129 129 setOpenMobile, 130 130 toggleSidebar, 131 131 }), 132 - [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] 133 - ) 132 + [ 133 + state, 134 + open, 135 + setOpen, 136 + isMobile, 137 + openMobile, 138 + setOpenMobile, 139 + toggleSidebar, 140 + ], 141 + ); 134 142 135 143 return ( 136 144 <SidebarContext.Provider value={contextValue}> ··· 145 153 } 146 154 className={cn( 147 155 "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar", 148 - className 156 + className, 149 157 )} 150 158 ref={ref} 151 159 {...props} ··· 154 162 </div> 155 163 </TooltipProvider> 156 164 </SidebarContext.Provider> 157 - ) 158 - } 159 - ) 160 - SidebarProvider.displayName = "SidebarProvider" 165 + ); 166 + }, 167 + ); 168 + SidebarProvider.displayName = "SidebarProvider"; 161 169 162 170 const Sidebar = React.forwardRef< 163 171 HTMLDivElement, 164 172 React.ComponentProps<"div"> & { 165 - side?: "left" | "right" 166 - variant?: "sidebar" | "floating" | "inset" 167 - collapsible?: "offcanvas" | "icon" | "none" 173 + side?: "left" | "right"; 174 + variant?: "sidebar" | "floating" | "inset"; 175 + collapsible?: "offcanvas" | "icon" | "none"; 168 176 } 169 177 >( 170 178 ( ··· 176 184 children, 177 185 ...props 178 186 }, 179 - ref 187 + ref, 180 188 ) => { 181 - const { isMobile, state, openMobile, setOpenMobile } = useSidebar() 189 + const { isMobile, state, openMobile, setOpenMobile } = useSidebar(); 182 190 183 191 if (collapsible === "none") { 184 192 return ( 185 193 <div 186 194 className={cn( 187 195 "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground", 188 - className 196 + className, 189 197 )} 190 198 ref={ref} 191 199 {...props} 192 200 > 193 201 {children} 194 202 </div> 195 - ) 203 + ); 196 204 } 197 205 198 206 if (isMobile) { ··· 216 224 <div className="flex h-full w-full flex-col">{children}</div> 217 225 </SheetContent> 218 226 </Sheet> 219 - ) 227 + ); 220 228 } 221 229 222 230 return ( ··· 236 244 "group-data-[side=right]:rotate-180", 237 245 variant === "floating" || variant === "inset" 238 246 ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]" 239 - : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]" 247 + : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]", 240 248 )} 241 249 /> 242 250 <div ··· 249 257 variant === "floating" || variant === "inset" 250 258 ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]" 251 259 : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l", 252 - className 260 + className, 253 261 )} 254 262 {...props} 255 263 > ··· 261 269 </div> 262 270 </div> 263 271 </div> 264 - ) 265 - } 266 - ) 267 - Sidebar.displayName = "Sidebar" 272 + ); 273 + }, 274 + ); 275 + Sidebar.displayName = "Sidebar"; 268 276 269 277 const SidebarTrigger = React.forwardRef< 270 278 React.ElementRef<typeof Button>, 271 279 React.ComponentProps<typeof Button> 272 280 >(({ className, onClick, ...props }, ref) => { 273 - const { toggleSidebar } = useSidebar() 281 + const { toggleSidebar } = useSidebar(); 274 282 275 283 return ( 276 284 <Button ··· 280 288 size="icon" 281 289 className={cn("h-7 w-7", className)} 282 290 onClick={(event) => { 283 - onClick?.(event) 284 - toggleSidebar() 291 + onClick?.(event); 292 + toggleSidebar(); 285 293 }} 286 294 {...props} 287 295 > 288 296 <ViewVerticalIcon /> 289 297 <span className="sr-only">Toggle Sidebar</span> 290 298 </Button> 291 - ) 292 - }) 293 - SidebarTrigger.displayName = "SidebarTrigger" 299 + ); 300 + }); 301 + SidebarTrigger.displayName = "SidebarTrigger"; 294 302 295 303 const SidebarRail = React.forwardRef< 296 304 HTMLButtonElement, 297 305 React.ComponentProps<"button"> 298 306 >(({ className, ...props }, ref) => { 299 - const { toggleSidebar } = useSidebar() 307 + const { toggleSidebar } = useSidebar(); 300 308 301 309 return ( 302 310 <button ··· 313 321 "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar", 314 322 "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", 315 323 "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", 316 - className 324 + className, 317 325 )} 318 326 {...props} 319 327 /> 320 - ) 321 - }) 322 - SidebarRail.displayName = "SidebarRail" 328 + ); 329 + }); 330 + SidebarRail.displayName = "SidebarRail"; 323 331 324 332 const SidebarInset = React.forwardRef< 325 333 HTMLDivElement, ··· 331 339 className={cn( 332 340 "relative flex w-full flex-1 flex-col bg-background", 333 341 "md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow", 334 - className 342 + className, 335 343 )} 336 344 {...props} 337 345 /> 338 - ) 339 - }) 340 - SidebarInset.displayName = "SidebarInset" 346 + ); 347 + }); 348 + SidebarInset.displayName = "SidebarInset"; 341 349 342 350 const SidebarInput = React.forwardRef< 343 351 React.ElementRef<typeof Input>, ··· 349 357 data-sidebar="input" 350 358 className={cn( 351 359 "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring", 352 - className 360 + className, 353 361 )} 354 362 {...props} 355 363 /> 356 - ) 357 - }) 358 - SidebarInput.displayName = "SidebarInput" 364 + ); 365 + }); 366 + SidebarInput.displayName = "SidebarInput"; 359 367 360 368 const SidebarHeader = React.forwardRef< 361 369 HTMLDivElement, ··· 368 376 className={cn("flex flex-col gap-2 p-2", className)} 369 377 {...props} 370 378 /> 371 - ) 372 - }) 373 - SidebarHeader.displayName = "SidebarHeader" 379 + ); 380 + }); 381 + SidebarHeader.displayName = "SidebarHeader"; 374 382 375 383 const SidebarFooter = React.forwardRef< 376 384 HTMLDivElement, ··· 383 391 className={cn("flex flex-col gap-2 p-2", className)} 384 392 {...props} 385 393 /> 386 - ) 387 - }) 388 - SidebarFooter.displayName = "SidebarFooter" 394 + ); 395 + }); 396 + SidebarFooter.displayName = "SidebarFooter"; 389 397 390 398 const SidebarSeparator = React.forwardRef< 391 399 React.ElementRef<typeof Separator>, ··· 398 406 className={cn("mx-2 w-auto bg-sidebar-border", className)} 399 407 {...props} 400 408 /> 401 - ) 402 - }) 403 - SidebarSeparator.displayName = "SidebarSeparator" 409 + ); 410 + }); 411 + SidebarSeparator.displayName = "SidebarSeparator"; 404 412 405 413 const SidebarContent = React.forwardRef< 406 414 HTMLDivElement, ··· 412 420 data-sidebar="content" 413 421 className={cn( 414 422 "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", 415 - className 423 + className, 416 424 )} 417 425 {...props} 418 426 /> 419 - ) 420 - }) 421 - SidebarContent.displayName = "SidebarContent" 427 + ); 428 + }); 429 + SidebarContent.displayName = "SidebarContent"; 422 430 423 431 const SidebarGroup = React.forwardRef< 424 432 HTMLDivElement, ··· 431 439 className={cn("relative flex w-full min-w-0 flex-col p-2", className)} 432 440 {...props} 433 441 /> 434 - ) 435 - }) 436 - SidebarGroup.displayName = "SidebarGroup" 442 + ); 443 + }); 444 + SidebarGroup.displayName = "SidebarGroup"; 437 445 438 446 const SidebarGroupLabel = React.forwardRef< 439 447 HTMLDivElement, 440 448 React.ComponentProps<"div"> & { asChild?: boolean } 441 449 >(({ className, asChild = false, ...props }, ref) => { 442 - const Comp = asChild ? Slot : "div" 450 + const Comp = asChild ? Slot : "div"; 443 451 444 452 return ( 445 453 <Comp ··· 448 456 className={cn( 449 457 "flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 450 458 "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", 451 - className 459 + className, 452 460 )} 453 461 {...props} 454 462 /> 455 - ) 456 - }) 457 - SidebarGroupLabel.displayName = "SidebarGroupLabel" 463 + ); 464 + }); 465 + SidebarGroupLabel.displayName = "SidebarGroupLabel"; 458 466 459 467 const SidebarGroupAction = React.forwardRef< 460 468 HTMLButtonElement, 461 469 React.ComponentProps<"button"> & { asChild?: boolean } 462 470 >(({ className, asChild = false, ...props }, ref) => { 463 - const Comp = asChild ? Slot : "button" 471 + const Comp = asChild ? Slot : "button"; 464 472 465 473 return ( 466 474 <Comp ··· 471 479 // Increases the hit area of the button on mobile. 472 480 "after:absolute after:-inset-2 after:md:hidden", 473 481 "group-data-[collapsible=icon]:hidden", 474 - className 482 + className, 475 483 )} 476 484 {...props} 477 485 /> 478 - ) 479 - }) 480 - SidebarGroupAction.displayName = "SidebarGroupAction" 486 + ); 487 + }); 488 + SidebarGroupAction.displayName = "SidebarGroupAction"; 481 489 482 490 const SidebarGroupContent = React.forwardRef< 483 491 HTMLDivElement, ··· 489 497 className={cn("w-full text-sm", className)} 490 498 {...props} 491 499 /> 492 - )) 493 - SidebarGroupContent.displayName = "SidebarGroupContent" 500 + )); 501 + SidebarGroupContent.displayName = "SidebarGroupContent"; 494 502 495 503 const SidebarMenu = React.forwardRef< 496 504 HTMLUListElement, ··· 502 510 className={cn("flex w-full min-w-0 flex-col gap-1", className)} 503 511 {...props} 504 512 /> 505 - )) 506 - SidebarMenu.displayName = "SidebarMenu" 513 + )); 514 + SidebarMenu.displayName = "SidebarMenu"; 507 515 508 516 const SidebarMenuItem = React.forwardRef< 509 517 HTMLLIElement, ··· 515 523 className={cn("group/menu-item relative", className)} 516 524 {...props} 517 525 /> 518 - )) 519 - SidebarMenuItem.displayName = "SidebarMenuItem" 526 + )); 527 + SidebarMenuItem.displayName = "SidebarMenuItem"; 520 528 521 529 const sidebarMenuButtonVariants = cva( 522 530 "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", ··· 537 545 variant: "default", 538 546 size: "default", 539 547 }, 540 - } 541 - ) 548 + }, 549 + ); 542 550 543 551 const SidebarMenuButton = React.forwardRef< 544 552 HTMLButtonElement, 545 553 React.ComponentProps<"button"> & { 546 - asChild?: boolean 547 - isActive?: boolean 548 - tooltip?: string | React.ComponentProps<typeof TooltipContent> 554 + asChild?: boolean; 555 + isActive?: boolean; 556 + tooltip?: string | React.ComponentProps<typeof TooltipContent>; 549 557 } & VariantProps<typeof sidebarMenuButtonVariants> 550 558 >( 551 559 ( ··· 558 566 className, 559 567 ...props 560 568 }, 561 - ref 569 + ref, 562 570 ) => { 563 - const Comp = asChild ? Slot : "button" 564 - const { isMobile, state } = useSidebar() 571 + const Comp = asChild ? Slot : "button"; 572 + const { isMobile, state } = useSidebar(); 565 573 566 574 const button = ( 567 575 <Comp ··· 572 580 className={cn(sidebarMenuButtonVariants({ variant, size }), className)} 573 581 {...props} 574 582 /> 575 - ) 583 + ); 576 584 577 585 if (!tooltip) { 578 - return button 586 + return button; 579 587 } 580 588 581 589 if (typeof tooltip === "string") { 582 590 tooltip = { 583 591 children: tooltip, 584 - } 592 + }; 585 593 } 586 594 587 595 return ( ··· 594 602 {...tooltip} 595 603 /> 596 604 </Tooltip> 597 - ) 598 - } 599 - ) 600 - SidebarMenuButton.displayName = "SidebarMenuButton" 605 + ); 606 + }, 607 + ); 608 + SidebarMenuButton.displayName = "SidebarMenuButton"; 601 609 602 610 const SidebarMenuAction = React.forwardRef< 603 611 HTMLButtonElement, 604 612 React.ComponentProps<"button"> & { 605 - asChild?: boolean 606 - showOnHover?: boolean 613 + asChild?: boolean; 614 + showOnHover?: boolean; 607 615 } 608 616 >(({ className, asChild = false, showOnHover = false, ...props }, ref) => { 609 - const Comp = asChild ? Slot : "button" 617 + const Comp = asChild ? Slot : "button"; 610 618 611 619 return ( 612 620 <Comp ··· 622 630 "group-data-[collapsible=icon]:hidden", 623 631 showOnHover && 624 632 "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0", 625 - className 633 + className, 626 634 )} 627 635 {...props} 628 636 /> 629 - ) 630 - }) 631 - SidebarMenuAction.displayName = "SidebarMenuAction" 637 + ); 638 + }); 639 + SidebarMenuAction.displayName = "SidebarMenuAction"; 632 640 633 641 const SidebarMenuBadge = React.forwardRef< 634 642 HTMLDivElement, ··· 644 652 "peer-data-[size=default]/menu-button:top-1.5", 645 653 "peer-data-[size=lg]/menu-button:top-2.5", 646 654 "group-data-[collapsible=icon]:hidden", 647 - className 655 + className, 648 656 )} 649 657 {...props} 650 658 /> 651 - )) 652 - SidebarMenuBadge.displayName = "SidebarMenuBadge" 659 + )); 660 + SidebarMenuBadge.displayName = "SidebarMenuBadge"; 653 661 654 662 const SidebarMenuSkeleton = React.forwardRef< 655 663 HTMLDivElement, 656 664 React.ComponentProps<"div"> & { 657 - showIcon?: boolean 665 + showIcon?: boolean; 658 666 } 659 667 >(({ className, showIcon = false, ...props }, ref) => { 660 668 // Random width between 50 to 90%. 661 669 const width = React.useMemo(() => { 662 - return `${Math.floor(Math.random() * 40) + 50}%` 663 - }, []) 670 + return `${Math.floor(Math.random() * 40) + 50}%`; 671 + }, []); 664 672 665 673 return ( 666 674 <div ··· 685 693 } 686 694 /> 687 695 </div> 688 - ) 689 - }) 690 - SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton" 696 + ); 697 + }); 698 + SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"; 691 699 692 700 const SidebarMenuSub = React.forwardRef< 693 701 HTMLUListElement, ··· 699 707 className={cn( 700 708 "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5", 701 709 "group-data-[collapsible=icon]:hidden", 702 - className 710 + className, 703 711 )} 704 712 {...props} 705 713 /> 706 - )) 707 - SidebarMenuSub.displayName = "SidebarMenuSub" 714 + )); 715 + SidebarMenuSub.displayName = "SidebarMenuSub"; 708 716 709 717 const SidebarMenuSubItem = React.forwardRef< 710 718 HTMLLIElement, 711 719 React.ComponentProps<"li"> 712 - >(({ ...props }, ref) => <li ref={ref} {...props} />) 713 - SidebarMenuSubItem.displayName = "SidebarMenuSubItem" 720 + >(({ ...props }, ref) => <li ref={ref} {...props} />); 721 + SidebarMenuSubItem.displayName = "SidebarMenuSubItem"; 714 722 715 723 const SidebarMenuSubButton = React.forwardRef< 716 724 HTMLAnchorElement, 717 725 React.ComponentProps<"a"> & { 718 - asChild?: boolean 719 - size?: "sm" | "md" 720 - isActive?: boolean 726 + asChild?: boolean; 727 + size?: "sm" | "md"; 728 + isActive?: boolean; 721 729 } 722 730 >(({ asChild = false, size = "md", isActive, className, ...props }, ref) => { 723 - const Comp = asChild ? Slot : "a" 731 + const Comp = asChild ? Slot : "a"; 724 732 725 733 return ( 726 734 <Comp ··· 734 742 size === "sm" && "text-xs", 735 743 size === "md" && "text-sm", 736 744 "group-data-[collapsible=icon]:hidden", 737 - className 745 + className, 738 746 )} 739 747 {...props} 740 748 /> 741 - ) 742 - }) 743 - SidebarMenuSubButton.displayName = "SidebarMenuSubButton" 749 + ); 750 + }); 751 + SidebarMenuSubButton.displayName = "SidebarMenuSubButton"; 744 752 745 753 export { 746 754 Sidebar, ··· 767 775 SidebarSeparator, 768 776 SidebarTrigger, 769 777 useSidebar, 770 - } 778 + };
+3 -3
mast-react-vite/src/components/ui/skeleton.tsx
··· 1 - import { cn } from "@/lib/utils" 1 + import { cn } from "@/lib/utils"; 2 2 3 3 function Skeleton({ 4 4 className, ··· 9 9 className={cn("animate-pulse rounded-md bg-primary/10", className)} 10 10 {...props} 11 11 /> 12 - ) 12 + ); 13 13 } 14 14 15 - export { Skeleton } 15 + export { Skeleton };
+3 -4
mast-react-vite/src/components/ui/table.tsx
··· 44 44 ref={ref} 45 45 className={cn( 46 46 "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", 47 - className 47 + className, 48 48 )} 49 49 {...props} 50 50 /> ··· 59 59 ref={ref} 60 60 className={cn( 61 61 "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", 62 - className 62 + className, 63 63 )} 64 64 {...props} 65 65 /> ··· 74 74 ref={ref} 75 75 className={cn( 76 76 "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", 77 - className 77 + className, 78 78 )} 79 79 {...props} 80 80 /> ··· 115 115 TableCell, 116 116 TableCaption, 117 117 }; 118 -
+53 -47
mast-react-vite/src/components/ui/task.tsx
··· 1 - import { Separator } from "@/components/ui/separator" 2 - import { Badge } from "@/components/ui/badge" 3 - import { useSelection } from "@/contexts/selection-context" 1 + import { Separator } from "@/components/ui/separator"; 2 + import { Badge } from "@/components/ui/badge"; 3 + import { useSelection } from "@/contexts/selection-context"; 4 4 5 5 interface TaskProps { 6 - selected?: boolean 7 - onSelect?: () => void 8 - data: any 6 + selected?: boolean; 7 + onSelect?: () => void; 8 + data: any; 9 + index: number; 9 10 } 10 11 11 12 export function Task({ selected, onSelect, data }: TaskProps) { 12 - const { isSelected, toggleSelection } = useSelection(); 13 - const tagList = JSON.parse(data.tags) 13 + const { isSelected, toggleSelection } = useSelection(); 14 + const tagList = JSON.parse(data.tags); 14 15 15 16 return ( 16 - <> 17 - <div 18 - className={`p-4 ${ 19 - isSelected(data.id) ? 'bg-muted border-primary' : 'bg-card' 20 - } hover:bg-muted/50 transition-colors`} 21 - onClick={() => toggleSelection(data.id)} 22 - > 23 - <div className="flex items-start gap-4"> 24 - {/* Description - always shown */} 25 - <div className="flex-1 min-w-0"> 26 - {data.project && ( 27 - <div className="flex items-center"> 28 - <div className="w-2 h-2 rounded-full bg-blue-500"></div> 29 - <span className="text-xs text-muted-foreground leading-6 ml-1"> 30 - {data.project} 31 - </span> 32 - </div> 33 - )} 34 - 35 - {/* Optional fields */} 36 - <div className="mt-1 flex flex-wrap gap-2 items-center"> 37 - <p className="text-sm font-medium leading-none truncate min-h-6 leading-6"> 38 - {data.description} 39 - </p> 40 - {tagList.length > 0 && ( 41 - <div className="flex gap-1 flex-wrap"> 42 - {tagList.map((tag: string) => ( 43 - <Badge key={tag} className="items-center text-xs bg-background border-muted-foreground text-muted-foreground"> 17 + <> 18 + <div 19 + className={`p-4 ${ 20 + isSelected(data.working_id) ? "bg-muted border-primary" : "bg-card" 21 + } hover:bg-muted/50 transition-colors`} 22 + onClick={() => toggleSelection(data.working_id)} 23 + > 24 + <div className="flex items-start gap-4"> 25 + <div className="flex-1 min-w-0"> 26 + <div className="flex items-center relative"> 27 + {data.project && ( 28 + <div className="flex items-center"> 29 + <div className="w-2 h-2 rounded-full bg-blue-500"></div> 30 + <span className="text-xs text-muted-foreground leading-6 ml-1"> 31 + {data.project} 32 + </span> 33 + </div> 34 + )} 35 + <div className="absolute right-4 top-1 w-6 h-6 flex items-center justify-center rounded-full border border-gray-800 text-gray-700 text-xs"> 36 + {data.working_id} 37 + </div> 38 + </div> 39 + 40 + <div className="mt-1 flex flex-wrap gap-2 items-center"> 41 + <p className="text-sm font-medium leading-none truncate min-h-6 leading-6"> 42 + {data.description} 43 + </p> 44 + {tagList.length > 0 && ( 45 + <div className="flex gap-1"> 46 + {tagList.map((tag: string) => ( 47 + <Badge 48 + key={tag} 49 + className="items-center text-xs bg-background border-muted-foreground text-muted-foreground -mt-2" 50 + > 44 51 # {tag} 45 52 </Badge> 46 53 ))} 47 54 </div> 48 - )} 49 - {data.dueDate && ( 50 - <span className="text-xs text-muted-foreground"> 51 - Due: {data.dueDate} 52 - </span> 53 - )} 55 + )} 56 + {data.dueDate && ( 57 + <span className="text-xs text-muted-foreground"> 58 + Due: {data.dueDate} 59 + </span> 60 + )} 61 + </div> 54 62 </div> 55 63 </div> 56 64 </div> 57 - </div> 58 - <Separator/> 59 - </> 60 - ) 65 + <Separator /> 66 + </> 67 + ); 61 68 } 62 -
+14 -14
mast-react-vite/src/components/ui/toggle-group.tsx
··· 1 - import * as React from "react" 2 - import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" 3 - import { type VariantProps } from "class-variance-authority" 1 + import * as React from "react"; 2 + import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; 3 + import { type VariantProps } from "class-variance-authority"; 4 4 5 - import { cn } from "@/lib/utils" 6 - import { toggleVariants } from "@/components/ui/toggle" 5 + import { cn } from "@/lib/utils"; 6 + import { toggleVariants } from "@/components/ui/toggle"; 7 7 8 8 const ToggleGroupContext = React.createContext< 9 9 VariantProps<typeof toggleVariants> 10 10 >({ 11 11 size: "default", 12 12 variant: "default", 13 - }) 13 + }); 14 14 15 15 const ToggleGroup = React.forwardRef< 16 16 React.ElementRef<typeof ToggleGroupPrimitive.Root>, ··· 26 26 {children} 27 27 </ToggleGroupContext.Provider> 28 28 </ToggleGroupPrimitive.Root> 29 - )) 29 + )); 30 30 31 - ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName 31 + ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; 32 32 33 33 const ToggleGroupItem = React.forwardRef< 34 34 React.ElementRef<typeof ToggleGroupPrimitive.Item>, 35 35 React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> & 36 36 VariantProps<typeof toggleVariants> 37 37 >(({ className, children, variant, size, ...props }, ref) => { 38 - const context = React.useContext(ToggleGroupContext) 38 + const context = React.useContext(ToggleGroupContext); 39 39 40 40 return ( 41 41 <ToggleGroupPrimitive.Item ··· 45 45 variant: context.variant || variant, 46 46 size: context.size || size, 47 47 }), 48 - className 48 + className, 49 49 )} 50 50 {...props} 51 51 > 52 52 {children} 53 53 </ToggleGroupPrimitive.Item> 54 - ) 55 - }) 54 + ); 55 + }); 56 56 57 - ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName 57 + ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName; 58 58 59 - export { ToggleGroup, ToggleGroupItem } 59 + export { ToggleGroup, ToggleGroupItem };
+10 -10
mast-react-vite/src/components/ui/toggle.tsx
··· 1 - "use client" 1 + "use client"; 2 2 3 - import * as React from "react" 4 - import * as TogglePrimitive from "@radix-ui/react-toggle" 5 - import { cva, type VariantProps } from "class-variance-authority" 3 + import * as React from "react"; 4 + import * as TogglePrimitive from "@radix-ui/react-toggle"; 5 + import { cva, type VariantProps } from "class-variance-authority"; 6 6 7 - import { cn } from "@/lib/utils" 7 + import { cn } from "@/lib/utils"; 8 8 9 9 const toggleVariants = cva( 10 10 "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", ··· 25 25 variant: "default", 26 26 size: "default", 27 27 }, 28 - } 29 - ) 28 + }, 29 + ); 30 30 31 31 const Toggle = React.forwardRef< 32 32 React.ElementRef<typeof TogglePrimitive.Root>, ··· 38 38 className={cn(toggleVariants({ variant, size, className }))} 39 39 {...props} 40 40 /> 41 - )) 41 + )); 42 42 43 - Toggle.displayName = TogglePrimitive.Root.displayName 43 + Toggle.displayName = TogglePrimitive.Root.displayName; 44 44 45 - export { Toggle, toggleVariants } 45 + export { Toggle, toggleVariants };
+10 -10
mast-react-vite/src/components/ui/tooltip.tsx
··· 1 - import * as React from "react" 2 - import * as TooltipPrimitive from "@radix-ui/react-tooltip" 1 + import * as React from "react"; 2 + import * as TooltipPrimitive from "@radix-ui/react-tooltip"; 3 3 4 - import { cn } from "@/lib/utils" 4 + import { cn } from "@/lib/utils"; 5 5 6 - const TooltipProvider = TooltipPrimitive.Provider 6 + const TooltipProvider = TooltipPrimitive.Provider; 7 7 8 - const Tooltip = TooltipPrimitive.Root 8 + const Tooltip = TooltipPrimitive.Root; 9 9 10 - const TooltipTrigger = TooltipPrimitive.Trigger 10 + const TooltipTrigger = TooltipPrimitive.Trigger; 11 11 12 12 const TooltipContent = React.forwardRef< 13 13 React.ElementRef<typeof TooltipPrimitive.Content>, ··· 19 19 sideOffset={sideOffset} 20 20 className={cn( 21 21 "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 22 - className 22 + className, 23 23 )} 24 24 {...props} 25 25 /> 26 26 </TooltipPrimitive.Portal> 27 - )) 28 - TooltipContent.displayName = TooltipPrimitive.Content.displayName 27 + )); 28 + TooltipContent.displayName = TooltipPrimitive.Content.displayName; 29 29 30 - export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 30 + export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+24 -11
mast-react-vite/src/contexts/selection-context.tsx
··· 1 - import { createContext, useContext, useState } from 'react'; 1 + import { createContext, useContext, useState } from "react"; 2 2 3 3 type SelectionContextType = { 4 - selectedItems: Set<string>; 5 - toggleSelection: (id: string) => void; 4 + selectedItems: Set<number>; 5 + toggleSelection: (id: number) => void; 6 6 clearSelection: () => void; 7 - isSelected: (id: string) => boolean; 7 + isSelected: (id: number) => boolean; 8 + getSelectionString: () => string; 8 9 }; 9 10 10 - const SelectionContext = createContext<SelectionContextType | undefined>(undefined); 11 + const SelectionContext = createContext<SelectionContextType | undefined>( 12 + undefined, 13 + ); 11 14 12 15 export function SelectionProvider({ children }) { 13 - const [selectedItems, setSelectedItems] = useState<Set<string>>(new Set()); 16 + const [selectedItems, setSelectedItems] = useState<Set<number>>(new Set()); 14 17 15 - const toggleSelection = (id: string) => { 16 - setSelectedItems(prev => { 18 + const toggleSelection = (id: number) => { 19 + setSelectedItems((prev) => { 17 20 const next = new Set(prev); 18 21 if (next.has(id)) { 19 22 next.delete(id); ··· 25 28 }; 26 29 27 30 const clearSelection = () => setSelectedItems(new Set()); 28 - const isSelected = (id: string) => selectedItems.has(id); 31 + const isSelected = (id: number) => selectedItems.has(id); 32 + const getSelectionString = () => Array.from(selectedItems).join(","); 29 33 30 34 return ( 31 - <SelectionContext.Provider value={{ selectedItems, toggleSelection, clearSelection, isSelected }}> 35 + <SelectionContext.Provider 36 + value={{ 37 + selectedItems, 38 + toggleSelection, 39 + clearSelection, 40 + isSelected, 41 + getSelectionString, 42 + }} 43 + > 32 44 {children} 33 45 </SelectionContext.Provider> 34 46 ); ··· 36 48 37 49 export const useSelection = () => { 38 50 const context = useContext(SelectionContext); 39 - if (!context) throw new Error('useSelection must be used within SelectionProvider'); 51 + if (!context) 52 + throw new Error("useSelection must be used within SelectionProvider"); 40 53 return context; 41 54 };
+13 -11
mast-react-vite/src/hooks/use-mobile.tsx
··· 1 - import * as React from "react" 1 + import * as React from "react"; 2 2 3 - const MOBILE_BREAKPOINT = 768 3 + const MOBILE_BREAKPOINT = 768; 4 4 5 5 export function useIsMobile() { 6 - const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) 6 + const [isMobile, setIsMobile] = React.useState<boolean | undefined>( 7 + undefined, 8 + ); 7 9 8 10 React.useEffect(() => { 9 - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 11 + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); 10 12 const onChange = () => { 11 - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 - } 13 - mql.addEventListener("change", onChange) 14 - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 - return () => mql.removeEventListener("change", onChange) 16 - }, []) 13 + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 14 + }; 15 + mql.addEventListener("change", onChange); 16 + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); 17 + return () => mql.removeEventListener("change", onChange); 18 + }, []); 17 19 18 - return !!isMobile 20 + return !!isMobile; 19 21 }
+187 -165
mast-react-vite/src/lib/command_js.js
··· 197 197 198 198 var peg$e0 = peg$literalExpectation("add", false); 199 199 var peg$e1 = peg$literalExpectation("done", false); 200 - var peg$e2 = peg$anyExpectation(); 201 - var peg$e3 = peg$literalExpectation("filter", false); 202 - var peg$e4 = peg$literalExpectation("-", false); 203 - var peg$e5 = peg$literalExpectation(",", false); 204 - var peg$e6 = peg$literalExpectation("@", false); 205 - var peg$e7 = peg$literalExpectation("pro:", false); 206 - var peg$e8 = peg$literalExpectation("project:", false); 207 - var peg$e9 = peg$literalExpectation("+", false); 208 - var peg$e10 = peg$literalExpectation("priority:", false); 209 - var peg$e11 = peg$classExpectation(["H", "M", "L"], false, false); 210 - var peg$e12 = peg$literalExpectation("#", false); 211 - var peg$e13 = peg$classExpectation([["0", "9"]], false, false); 212 - var peg$e14 = peg$classExpectation([["0", "9"], ":"], false, false); 213 - var peg$e15 = peg$literalExpectation("am", false); 214 - var peg$e16 = peg$literalExpectation("pm", false); 215 - var peg$e17 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false); 216 - var peg$e18 = peg$classExpectation([" ", "\t"], false, false); 200 + var peg$e2 = peg$literalExpectation("filter", false); 201 + var peg$e3 = peg$literalExpectation("-", false); 202 + var peg$e4 = peg$literalExpectation(",", false); 203 + var peg$e5 = peg$literalExpectation("@", false); 204 + var peg$e6 = peg$literalExpectation("pro:", false); 205 + var peg$e7 = peg$literalExpectation("project:", false); 206 + var peg$e8 = peg$literalExpectation("+", false); 207 + var peg$e9 = peg$literalExpectation("priority:", false); 208 + var peg$e10 = peg$classExpectation(["H", "M", "L"], false, false); 209 + var peg$e11 = peg$literalExpectation("#", false); 210 + var peg$e12 = peg$classExpectation([["0", "9"]], false, false); 211 + var peg$e13 = peg$classExpectation([["0", "9"], ":"], false, false); 212 + var peg$e14 = peg$literalExpectation("am", false); 213 + var peg$e15 = peg$literalExpectation("pm", false); 214 + var peg$e16 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false); 215 + var peg$e17 = peg$classExpectation([" ", "\t"], false, false); 216 + var peg$e18 = peg$anyExpectation(); 217 217 218 - var peg$f0 = function(parts) { 219 - return makeCommand('add', null, parts.filter(p => p !== null)); 218 + var peg$f0 = function(selection, parts) { 219 + return makeCommand('add', selection, parts.filter(p => p !== null)); 220 220 }; 221 - var peg$f1 = function() { 222 - return makeCommand('done', null, null); 221 + var peg$f1 = function(selection, parts) { 222 + return makeCommand('done', selection, parts); 223 223 }; 224 - var peg$f2 = function(moreFilters) { 225 - return makeCommand('filter', null, moreFilters); 224 + var peg$f2 = function(selection, moreFilters) { 225 + return makeCommand('filter', selection, moreFilters); 226 226 }; 227 - var peg$f3 = function(filters) { 228 - return makeCommand('filter', filters, null); 229 - }; 230 - var peg$f4 = function(first, rest) { 227 + var peg$f3 = function(first, rest) { 231 228 return [first, ...rest.map(r => r[1])]; 232 229 }; 233 - var peg$f5 = function(start, end) { 230 + var peg$f4 = function(start, end) { 234 231 const ids = []; 235 232 for (let i = start; i <= end; i++) { 236 233 ids.push(i); 237 234 } 238 235 return ids; 239 236 }; 240 - var peg$f6 = function(id) { 237 + var peg$f5 = function(id) { 241 238 return [id]; 239 + }; 240 + var peg$f6 = function(first, rest) { 241 + return [first, ...rest.map(r => r[1])]; 242 242 }; 243 243 var peg$f7 = function(first, rest, trailing) { 244 244 const ids = [first, ...rest.map(r => r[1])].flat(); ··· 465 465 s0 = peg$parseDoneCommand(); 466 466 if (s0 === peg$FAILED) { 467 467 s0 = peg$parseExplicitFilterCommand(); 468 - if (s0 === peg$FAILED) { 469 - s0 = peg$parseImplicitFilterCommand(); 470 - } 471 468 } 472 469 } 473 470 ··· 475 472 } 476 473 477 474 function peg$parseAddCommand() { 478 - var s0, s1, s2, s3, s4; 475 + var s0, s1, s2, s3, s4, s5, s6; 479 476 480 477 s0 = peg$currPos; 478 + s1 = peg$parseSelections(); 479 + if (s1 === peg$FAILED) { 480 + s1 = null; 481 + } 482 + s2 = peg$parse_(); 483 + if (s2 === peg$FAILED) { 484 + s2 = null; 485 + } 481 486 if (input.substr(peg$currPos, 3) === peg$c0) { 482 - s1 = peg$c0; 487 + s3 = peg$c0; 483 488 peg$currPos += 3; 484 489 } else { 485 - s1 = peg$FAILED; 490 + s3 = peg$FAILED; 486 491 if (peg$silentFails === 0) { peg$fail(peg$e0); } 487 492 } 488 - if (s1 !== peg$FAILED) { 489 - s2 = peg$parse_(); 490 - if (s2 !== peg$FAILED) { 491 - s3 = []; 492 - s4 = peg$parsePart(); 493 - if (s4 === peg$FAILED) { 494 - s4 = peg$parse_(); 493 + if (s3 !== peg$FAILED) { 494 + s4 = peg$parse_(); 495 + if (s4 !== peg$FAILED) { 496 + s5 = []; 497 + s6 = peg$parsePart(); 498 + if (s6 === peg$FAILED) { 499 + s6 = peg$parse_(); 495 500 } 496 - if (s4 !== peg$FAILED) { 497 - while (s4 !== peg$FAILED) { 498 - s3.push(s4); 499 - s4 = peg$parsePart(); 500 - if (s4 === peg$FAILED) { 501 - s4 = peg$parse_(); 501 + if (s6 !== peg$FAILED) { 502 + while (s6 !== peg$FAILED) { 503 + s5.push(s6); 504 + s6 = peg$parsePart(); 505 + if (s6 === peg$FAILED) { 506 + s6 = peg$parse_(); 502 507 } 503 508 } 504 509 } else { 505 - s3 = peg$FAILED; 510 + s5 = peg$FAILED; 506 511 } 507 - if (s3 !== peg$FAILED) { 508 - s4 = peg$parseEOF(); 509 - if (s4 !== peg$FAILED) { 512 + if (s5 !== peg$FAILED) { 513 + s6 = peg$parseEOF(); 514 + if (s6 !== peg$FAILED) { 510 515 peg$savedPos = s0; 511 - s0 = peg$f0(s3); 516 + s0 = peg$f0(s1, s5); 512 517 } else { 513 518 peg$currPos = s0; 514 519 s0 = peg$FAILED; ··· 530 535 } 531 536 532 537 function peg$parseDoneCommand() { 533 - var s0, s1, s2, s3; 538 + var s0, s1, s2, s3, s4, s5, s6; 534 539 535 540 s0 = peg$currPos; 541 + s1 = peg$parseSelections(); 542 + if (s1 === peg$FAILED) { 543 + s1 = null; 544 + } 545 + s2 = peg$parse_(); 546 + if (s2 === peg$FAILED) { 547 + s2 = null; 548 + } 536 549 if (input.substr(peg$currPos, 4) === peg$c1) { 537 - s1 = peg$c1; 550 + s3 = peg$c1; 538 551 peg$currPos += 4; 539 552 } else { 540 - s1 = peg$FAILED; 553 + s3 = peg$FAILED; 541 554 if (peg$silentFails === 0) { peg$fail(peg$e1); } 542 555 } 543 - if (s1 !== peg$FAILED) { 544 - s2 = []; 545 - if (input.length > peg$currPos) { 546 - s3 = input.charAt(peg$currPos); 547 - peg$currPos++; 548 - } else { 549 - s3 = peg$FAILED; 550 - if (peg$silentFails === 0) { peg$fail(peg$e2); } 556 + if (s3 !== peg$FAILED) { 557 + s4 = peg$parse_(); 558 + if (s4 === peg$FAILED) { 559 + s4 = null; 560 + } 561 + s5 = peg$parsePart(); 562 + if (s5 === peg$FAILED) { 563 + s5 = peg$parse_(); 551 564 } 552 - while (s3 !== peg$FAILED) { 553 - s2.push(s3); 554 - if (input.length > peg$currPos) { 555 - s3 = input.charAt(peg$currPos); 556 - peg$currPos++; 557 - } else { 558 - s3 = peg$FAILED; 559 - if (peg$silentFails === 0) { peg$fail(peg$e2); } 560 - } 565 + if (s5 === peg$FAILED) { 566 + s5 = null; 561 567 } 562 - s3 = peg$parseEOF(); 563 - if (s3 !== peg$FAILED) { 568 + s6 = peg$parseEOF(); 569 + if (s6 !== peg$FAILED) { 564 570 peg$savedPos = s0; 565 - s0 = peg$f1(); 571 + s0 = peg$f1(s1, s5); 566 572 } else { 567 573 peg$currPos = s0; 568 574 s0 = peg$FAILED; ··· 576 582 } 577 583 578 584 function peg$parseExplicitFilterCommand() { 579 - var s0, s1, s2, s3, s4; 585 + var s0, s1, s2, s3, s4, s5, s6; 580 586 581 587 s0 = peg$currPos; 588 + s1 = peg$parseSelections(); 589 + if (s1 === peg$FAILED) { 590 + s1 = null; 591 + } 592 + s2 = peg$parse_(); 593 + if (s2 === peg$FAILED) { 594 + s2 = null; 595 + } 582 596 if (input.substr(peg$currPos, 6) === peg$c2) { 583 - s1 = peg$c2; 597 + s3 = peg$c2; 584 598 peg$currPos += 6; 585 599 } else { 586 - s1 = peg$FAILED; 587 - if (peg$silentFails === 0) { peg$fail(peg$e3); } 600 + s3 = peg$FAILED; 601 + if (peg$silentFails === 0) { peg$fail(peg$e2); } 588 602 } 589 - if (s1 !== peg$FAILED) { 590 - s2 = []; 591 - s3 = peg$parse_(); 592 - while (s3 !== peg$FAILED) { 593 - s2.push(s3); 594 - s3 = peg$parse_(); 603 + if (s3 !== peg$FAILED) { 604 + s4 = []; 605 + s5 = peg$parse_(); 606 + while (s5 !== peg$FAILED) { 607 + s4.push(s5); 608 + s5 = peg$parse_(); 595 609 } 596 - s3 = peg$parseFilters(); 597 - if (s3 !== peg$FAILED) { 598 - s4 = peg$parseEOF(); 599 - if (s4 !== peg$FAILED) { 610 + s5 = peg$parseFilters(); 611 + if (s5 !== peg$FAILED) { 612 + s6 = peg$parseEOF(); 613 + if (s6 !== peg$FAILED) { 600 614 peg$savedPos = s0; 601 - s0 = peg$f2(s3); 615 + s0 = peg$f2(s1, s5); 602 616 } else { 603 617 peg$currPos = s0; 604 618 s0 = peg$FAILED; ··· 615 629 return s0; 616 630 } 617 631 618 - function peg$parseImplicitFilterCommand() { 619 - var s0, s1, s2; 620 - 621 - s0 = peg$currPos; 622 - s1 = []; 623 - s2 = peg$parseFilters(); 624 - if (s2 !== peg$FAILED) { 625 - while (s2 !== peg$FAILED) { 626 - s1.push(s2); 627 - s2 = peg$parseFilters(); 628 - } 629 - } else { 630 - s1 = peg$FAILED; 631 - } 632 - if (s1 !== peg$FAILED) { 633 - s2 = peg$parseEOF(); 634 - if (s2 !== peg$FAILED) { 635 - peg$savedPos = s0; 636 - s0 = peg$f3(s1); 637 - } else { 638 - peg$currPos = s0; 639 - s0 = peg$FAILED; 640 - } 641 - } else { 642 - peg$currPos = s0; 643 - s0 = peg$FAILED; 644 - } 645 - 646 - return s0; 647 - } 648 - 649 632 function peg$parseFilters() { 650 633 var s0, s1, s2, s3, s4, s5; 651 634 652 635 s0 = peg$currPos; 653 - s1 = peg$parseFilter(); 636 + s1 = peg$parsePart(); 654 637 if (s1 !== peg$FAILED) { 655 638 s2 = []; 656 639 s3 = peg$currPos; 657 640 s4 = peg$parse_(); 658 641 if (s4 !== peg$FAILED) { 659 - s5 = peg$parseFilter(); 642 + s5 = peg$parsePart(); 660 643 if (s5 !== peg$FAILED) { 661 644 s4 = [s4, s5]; 662 645 s3 = s4; ··· 673 656 s3 = peg$currPos; 674 657 s4 = peg$parse_(); 675 658 if (s4 !== peg$FAILED) { 676 - s5 = peg$parseFilter(); 659 + s5 = peg$parsePart(); 677 660 if (s5 !== peg$FAILED) { 678 661 s4 = [s4, s5]; 679 662 s3 = s4; ··· 687 670 } 688 671 } 689 672 peg$savedPos = s0; 690 - s0 = peg$f4(s1, s2); 673 + s0 = peg$f3(s1, s2); 691 674 } else { 692 675 peg$currPos = s0; 693 676 s0 = peg$FAILED; ··· 707 690 peg$currPos++; 708 691 } else { 709 692 s2 = peg$FAILED; 710 - if (peg$silentFails === 0) { peg$fail(peg$e4); } 693 + if (peg$silentFails === 0) { peg$fail(peg$e3); } 711 694 } 712 695 if (s2 !== peg$FAILED) { 713 696 s3 = peg$parseInteger(); 714 697 if (s3 !== peg$FAILED) { 715 698 peg$savedPos = s0; 716 - s0 = peg$f5(s1, s3); 699 + s0 = peg$f4(s1, s3); 717 700 } else { 718 701 peg$currPos = s0; 719 702 s0 = peg$FAILED; ··· 737 720 s1 = peg$parseInteger(); 738 721 if (s1 !== peg$FAILED) { 739 722 peg$savedPos = s0; 740 - s1 = peg$f6(s1); 723 + s1 = peg$f5(s1); 741 724 } 742 725 s0 = s1; 743 726 744 727 return s0; 745 728 } 746 729 747 - function peg$parseFilter() { 730 + function peg$parseSelections() { 731 + var s0, s1, s2, s3, s4, s5; 732 + 733 + s0 = peg$currPos; 734 + s1 = peg$parseSelection(); 735 + if (s1 !== peg$FAILED) { 736 + s2 = []; 737 + s3 = peg$currPos; 738 + s4 = peg$parse_(); 739 + if (s4 !== peg$FAILED) { 740 + s5 = peg$parseSelection(); 741 + if (s5 !== peg$FAILED) { 742 + s4 = [s4, s5]; 743 + s3 = s4; 744 + } else { 745 + peg$currPos = s3; 746 + s3 = peg$FAILED; 747 + } 748 + } else { 749 + peg$currPos = s3; 750 + s3 = peg$FAILED; 751 + } 752 + while (s3 !== peg$FAILED) { 753 + s2.push(s3); 754 + s3 = peg$currPos; 755 + s4 = peg$parse_(); 756 + if (s4 !== peg$FAILED) { 757 + s5 = peg$parseSelection(); 758 + if (s5 !== peg$FAILED) { 759 + s4 = [s4, s5]; 760 + s3 = s4; 761 + } else { 762 + peg$currPos = s3; 763 + s3 = peg$FAILED; 764 + } 765 + } else { 766 + peg$currPos = s3; 767 + s3 = peg$FAILED; 768 + } 769 + } 770 + peg$savedPos = s0; 771 + s0 = peg$f6(s1, s2); 772 + } else { 773 + peg$currPos = s0; 774 + s0 = peg$FAILED; 775 + } 776 + 777 + return s0; 778 + } 779 + 780 + function peg$parseSelection() { 748 781 var s0; 749 782 750 783 s0 = peg$parseIdFilter(); 751 784 if (s0 === peg$FAILED) { 752 785 s0 = peg$parseAttribute(); 753 - if (s0 === peg$FAILED) { 754 - s0 = peg$parsePart(); 755 - } 756 786 } 757 787 758 788 return s0; ··· 774 804 peg$currPos++; 775 805 } else { 776 806 s4 = peg$FAILED; 777 - if (peg$silentFails === 0) { peg$fail(peg$e5); } 807 + if (peg$silentFails === 0) { peg$fail(peg$e4); } 778 808 } 779 809 if (s4 !== peg$FAILED) { 780 810 s5 = peg$parseIdRange(); ··· 800 830 peg$currPos++; 801 831 } else { 802 832 s4 = peg$FAILED; 803 - if (peg$silentFails === 0) { peg$fail(peg$e5); } 833 + if (peg$silentFails === 0) { peg$fail(peg$e4); } 804 834 } 805 835 if (s4 !== peg$FAILED) { 806 836 s5 = peg$parseIdRange(); ··· 824 854 peg$currPos++; 825 855 } else { 826 856 s3 = peg$FAILED; 827 - if (peg$silentFails === 0) { peg$fail(peg$e5); } 857 + if (peg$silentFails === 0) { peg$fail(peg$e4); } 828 858 } 829 859 if (s3 === peg$FAILED) { 830 860 s3 = null; ··· 890 920 peg$currPos++; 891 921 } else { 892 922 s1 = peg$FAILED; 893 - if (peg$silentFails === 0) { peg$fail(peg$e6); } 923 + if (peg$silentFails === 0) { peg$fail(peg$e5); } 894 924 } 895 925 if (s1 !== peg$FAILED) { 896 926 s2 = peg$parseTimeValue(); ··· 918 948 peg$currPos += 4; 919 949 } else { 920 950 s1 = peg$FAILED; 921 - if (peg$silentFails === 0) { peg$fail(peg$e7); } 951 + if (peg$silentFails === 0) { peg$fail(peg$e6); } 922 952 } 923 953 if (s1 === peg$FAILED) { 924 954 if (input.substr(peg$currPos, 8) === peg$c7) { ··· 926 956 peg$currPos += 8; 927 957 } else { 928 958 s1 = peg$FAILED; 929 - if (peg$silentFails === 0) { peg$fail(peg$e8); } 959 + if (peg$silentFails === 0) { peg$fail(peg$e7); } 930 960 } 931 961 if (s1 === peg$FAILED) { 932 962 if (input.charCodeAt(peg$currPos) === 43) { ··· 934 964 peg$currPos++; 935 965 } else { 936 966 s1 = peg$FAILED; 937 - if (peg$silentFails === 0) { peg$fail(peg$e9); } 967 + if (peg$silentFails === 0) { peg$fail(peg$e8); } 938 968 } 939 969 } 940 970 } ··· 964 994 peg$currPos += 9; 965 995 } else { 966 996 s1 = peg$FAILED; 967 - if (peg$silentFails === 0) { peg$fail(peg$e10); } 997 + if (peg$silentFails === 0) { peg$fail(peg$e9); } 968 998 } 969 999 if (s1 !== peg$FAILED) { 970 1000 s2 = input.charAt(peg$currPos); ··· 972 1002 peg$currPos++; 973 1003 } else { 974 1004 s2 = peg$FAILED; 975 - if (peg$silentFails === 0) { peg$fail(peg$e11); } 1005 + if (peg$silentFails === 0) { peg$fail(peg$e10); } 976 1006 } 977 1007 if (s2 !== peg$FAILED) { 978 1008 peg$savedPos = s0; ··· 998 1028 peg$currPos++; 999 1029 } else { 1000 1030 s1 = peg$FAILED; 1001 - if (peg$silentFails === 0) { peg$fail(peg$e12); } 1031 + if (peg$silentFails === 0) { peg$fail(peg$e11); } 1002 1032 } 1003 1033 if (s1 !== peg$FAILED) { 1004 1034 s2 = peg$parseWord(); ··· 1027 1057 peg$currPos++; 1028 1058 } else { 1029 1059 s2 = peg$FAILED; 1030 - if (peg$silentFails === 0) { peg$fail(peg$e13); } 1060 + if (peg$silentFails === 0) { peg$fail(peg$e12); } 1031 1061 } 1032 1062 if (s2 !== peg$FAILED) { 1033 1063 while (s2 !== peg$FAILED) { ··· 1037 1067 peg$currPos++; 1038 1068 } else { 1039 1069 s2 = peg$FAILED; 1040 - if (peg$silentFails === 0) { peg$fail(peg$e13); } 1070 + if (peg$silentFails === 0) { peg$fail(peg$e12); } 1041 1071 } 1042 1072 } 1043 1073 } else { ··· 1062 1092 peg$currPos++; 1063 1093 } else { 1064 1094 s2 = peg$FAILED; 1065 - if (peg$silentFails === 0) { peg$fail(peg$e14); } 1095 + if (peg$silentFails === 0) { peg$fail(peg$e13); } 1066 1096 } 1067 1097 if (s2 !== peg$FAILED) { 1068 1098 while (s2 !== peg$FAILED) { ··· 1072 1102 peg$currPos++; 1073 1103 } else { 1074 1104 s2 = peg$FAILED; 1075 - if (peg$silentFails === 0) { peg$fail(peg$e14); } 1105 + if (peg$silentFails === 0) { peg$fail(peg$e13); } 1076 1106 } 1077 1107 } 1078 1108 } else { ··· 1084 1114 peg$currPos += 2; 1085 1115 } else { 1086 1116 s2 = peg$FAILED; 1087 - if (peg$silentFails === 0) { peg$fail(peg$e15); } 1117 + if (peg$silentFails === 0) { peg$fail(peg$e14); } 1088 1118 } 1089 1119 if (s2 === peg$FAILED) { 1090 1120 if (input.substr(peg$currPos, 2) === peg$c12) { ··· 1092 1122 peg$currPos += 2; 1093 1123 } else { 1094 1124 s2 = peg$FAILED; 1095 - if (peg$silentFails === 0) { peg$fail(peg$e16); } 1125 + if (peg$silentFails === 0) { peg$fail(peg$e15); } 1096 1126 } 1097 1127 } 1098 1128 if (s2 === peg$FAILED) { ··· 1118 1148 peg$currPos++; 1119 1149 } else { 1120 1150 s2 = peg$FAILED; 1121 - if (peg$silentFails === 0) { peg$fail(peg$e17); } 1151 + if (peg$silentFails === 0) { peg$fail(peg$e16); } 1122 1152 } 1123 1153 if (s2 !== peg$FAILED) { 1124 1154 while (s2 !== peg$FAILED) { ··· 1128 1158 peg$currPos++; 1129 1159 } else { 1130 1160 s2 = peg$FAILED; 1131 - if (peg$silentFails === 0) { peg$fail(peg$e17); } 1161 + if (peg$silentFails === 0) { peg$fail(peg$e16); } 1132 1162 } 1133 1163 } 1134 1164 } else { ··· 1153 1183 peg$currPos++; 1154 1184 } else { 1155 1185 s2 = peg$FAILED; 1156 - if (peg$silentFails === 0) { peg$fail(peg$e18); } 1186 + if (peg$silentFails === 0) { peg$fail(peg$e17); } 1157 1187 } 1158 1188 if (s2 !== peg$FAILED) { 1159 1189 while (s2 !== peg$FAILED) { ··· 1163 1193 peg$currPos++; 1164 1194 } else { 1165 1195 s2 = peg$FAILED; 1166 - if (peg$silentFails === 0) { peg$fail(peg$e18); } 1196 + if (peg$silentFails === 0) { peg$fail(peg$e17); } 1167 1197 } 1168 1198 } 1169 1199 } else { ··· 1188 1218 peg$currPos++; 1189 1219 } else { 1190 1220 s1 = peg$FAILED; 1191 - if (peg$silentFails === 0) { peg$fail(peg$e2); } 1221 + if (peg$silentFails === 0) { peg$fail(peg$e18); } 1192 1222 } 1193 1223 peg$silentFails--; 1194 1224 if (s1 === peg$FAILED) { ··· 1202 1232 } 1203 1233 1204 1234 1205 - function makeCommand(type, filters, parts) { 1235 + function makeCommand(action, selection, parts) { 1206 1236 1207 1237 const validParts = parts || []; 1208 1238 // Extract pure description (without attributes) ··· 1228 1258 .map(part => part.value); 1229 1259 1230 1260 1231 - // Store original parts for reconstruction 1232 - const originalParts = validParts.filter(part => part !== null); 1233 - let validFilters; 1234 - if (!!filters) { 1235 - validFilters = filters.flat(); 1236 - } else { 1237 - validFilters = []; 1238 - } 1239 1261 1240 1262 return { 1241 - type: type, 1263 + action: action, 1264 + selection: selection || [], 1242 1265 description: description || [], 1243 1266 attributes: attributes || [], 1244 - filters: validFilters, 1245 1267 project: project, 1246 1268 tags: JSON.stringify(tags), 1247 1269 parts: parts || [],
+37 -21
mast-react-vite/src/main.tsx
··· 1 - import { createRoot } from 'react-dom/client' 2 - import './index.css' 1 + import { createRoot } from "react-dom/client"; 2 + import "./index.css"; 3 3 import App from "./App.tsx"; 4 4 5 - import { Helmet } from 'react-helmet'; 5 + import { Helmet } from "react-helmet"; 6 6 import { DBAsync } from "@vlcn.io/xplat-api"; 7 - import initWasm from '@vlcn.io/crsqlite-wasm'; 8 - import wasmUrl from '@vlcn.io/crsqlite-wasm/crsqlite.wasm?url'; 9 - import tblrx from '@vlcn.io/rx-tbl'; 7 + import initWasm from "@vlcn.io/crsqlite-wasm"; 8 + import wasmUrl from "@vlcn.io/crsqlite-wasm/crsqlite.wasm?url"; 9 + import tblrx from "@vlcn.io/rx-tbl"; 10 10 11 - import { SelectionProvider } from "@/contexts/selection-context" 11 + import { SelectionProvider } from "@/contexts/selection-context"; 12 12 13 13 const initDb = async () => { 14 14 const sqlite = await initWasm(() => wasmUrl); 15 15 const db: DBAsync = await sqlite.open("todo.db"); 16 - await db.exec(` 16 + await db.exec(` 17 17 CREATE TABLE IF NOT EXISTS todos ( 18 18 id BLOB PRIMARY KEY NOT NULL, 19 19 description TEXT, ··· 26 26 completed INTEGER NOT NULL DEFAULT 0 27 27 ); 28 28 SELECT crsql_as_crr('todos'); 29 + CREATE VIEW IF NOT EXISTS active_todos AS 30 + SELECT 31 + id, 32 + ROW_NUMBER() OVER (ORDER BY id) as working_id, 33 + description, 34 + project, 35 + tags, 36 + due, 37 + wait, 38 + priority, 39 + urgency 40 + FROM todos 41 + WHERE completed = 0; 29 42 `); 30 - const rx = tblrx(db); 31 - return {db, rx}; 32 - } 43 + const rx = tblrx(db); 44 + return { db, rx }; 45 + }; 33 46 34 47 const init = async () => { 35 48 const ctx = await initDb(); 36 49 createRoot(document.getElementById("root") as HTMLElement).render( 37 - <> 38 - <Helmet> 39 - <meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content" /> 50 + <> 51 + <Helmet> 52 + <meta 53 + name="viewport" 54 + content="width=device-width, initial-scale=1, interactive-widget=resizes-content" 55 + /> 40 56 </Helmet> 41 - <main className="h-screen flex flex-col bg-background text-gray-200"> 42 - <SelectionProvider> 43 - <App ctx={ctx} /> 44 - </SelectionProvider> 45 - </main> 46 - </> 57 + <main className="h-screen flex flex-col bg-background text-gray-200"> 58 + <SelectionProvider> 59 + <App ctx={ctx} /> 60 + </SelectionProvider> 61 + </main> 62 + </>, 47 63 ); 48 - } 64 + }; 49 65 50 66 document.getElementById("root")!.classList.add("dark"); 51 67 init();
+187 -165
parser/command_js.js
··· 197 197 198 198 var peg$e0 = peg$literalExpectation("add", false); 199 199 var peg$e1 = peg$literalExpectation("done", false); 200 - var peg$e2 = peg$anyExpectation(); 201 - var peg$e3 = peg$literalExpectation("filter", false); 202 - var peg$e4 = peg$literalExpectation("-", false); 203 - var peg$e5 = peg$literalExpectation(",", false); 204 - var peg$e6 = peg$literalExpectation("@", false); 205 - var peg$e7 = peg$literalExpectation("pro:", false); 206 - var peg$e8 = peg$literalExpectation("project:", false); 207 - var peg$e9 = peg$literalExpectation("+", false); 208 - var peg$e10 = peg$literalExpectation("priority:", false); 209 - var peg$e11 = peg$classExpectation(["H", "M", "L"], false, false); 210 - var peg$e12 = peg$literalExpectation("#", false); 211 - var peg$e13 = peg$classExpectation([["0", "9"]], false, false); 212 - var peg$e14 = peg$classExpectation([["0", "9"], ":"], false, false); 213 - var peg$e15 = peg$literalExpectation("am", false); 214 - var peg$e16 = peg$literalExpectation("pm", false); 215 - var peg$e17 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false); 216 - var peg$e18 = peg$classExpectation([" ", "\t"], false, false); 200 + var peg$e2 = peg$literalExpectation("filter", false); 201 + var peg$e3 = peg$literalExpectation("-", false); 202 + var peg$e4 = peg$literalExpectation(",", false); 203 + var peg$e5 = peg$literalExpectation("@", false); 204 + var peg$e6 = peg$literalExpectation("pro:", false); 205 + var peg$e7 = peg$literalExpectation("project:", false); 206 + var peg$e8 = peg$literalExpectation("+", false); 207 + var peg$e9 = peg$literalExpectation("priority:", false); 208 + var peg$e10 = peg$classExpectation(["H", "M", "L"], false, false); 209 + var peg$e11 = peg$literalExpectation("#", false); 210 + var peg$e12 = peg$classExpectation([["0", "9"]], false, false); 211 + var peg$e13 = peg$classExpectation([["0", "9"], ":"], false, false); 212 + var peg$e14 = peg$literalExpectation("am", false); 213 + var peg$e15 = peg$literalExpectation("pm", false); 214 + var peg$e16 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false); 215 + var peg$e17 = peg$classExpectation([" ", "\t"], false, false); 216 + var peg$e18 = peg$anyExpectation(); 217 217 218 - var peg$f0 = function(parts) { 219 - return makeCommand('add', null, parts.filter(p => p !== null)); 218 + var peg$f0 = function(selection, parts) { 219 + return makeCommand('add', selection, parts.filter(p => p !== null)); 220 220 }; 221 - var peg$f1 = function() { 222 - return makeCommand('done', null, null); 221 + var peg$f1 = function(selection, parts) { 222 + return makeCommand('done', selection, parts); 223 223 }; 224 - var peg$f2 = function(moreFilters) { 225 - return makeCommand('filter', null, moreFilters); 224 + var peg$f2 = function(selection, moreFilters) { 225 + return makeCommand('filter', selection, moreFilters); 226 226 }; 227 - var peg$f3 = function(filters) { 228 - return makeCommand('filter', filters, null); 229 - }; 230 - var peg$f4 = function(first, rest) { 227 + var peg$f3 = function(first, rest) { 231 228 return [first, ...rest.map(r => r[1])]; 232 229 }; 233 - var peg$f5 = function(start, end) { 230 + var peg$f4 = function(start, end) { 234 231 const ids = []; 235 232 for (let i = start; i <= end; i++) { 236 233 ids.push(i); 237 234 } 238 235 return ids; 239 236 }; 240 - var peg$f6 = function(id) { 237 + var peg$f5 = function(id) { 241 238 return [id]; 239 + }; 240 + var peg$f6 = function(first, rest) { 241 + return [first, ...rest.map(r => r[1])]; 242 242 }; 243 243 var peg$f7 = function(first, rest, trailing) { 244 244 const ids = [first, ...rest.map(r => r[1])].flat(); ··· 465 465 s0 = peg$parseDoneCommand(); 466 466 if (s0 === peg$FAILED) { 467 467 s0 = peg$parseExplicitFilterCommand(); 468 - if (s0 === peg$FAILED) { 469 - s0 = peg$parseImplicitFilterCommand(); 470 - } 471 468 } 472 469 } 473 470 ··· 475 472 } 476 473 477 474 function peg$parseAddCommand() { 478 - var s0, s1, s2, s3, s4; 475 + var s0, s1, s2, s3, s4, s5, s6; 479 476 480 477 s0 = peg$currPos; 478 + s1 = peg$parseSelections(); 479 + if (s1 === peg$FAILED) { 480 + s1 = null; 481 + } 482 + s2 = peg$parse_(); 483 + if (s2 === peg$FAILED) { 484 + s2 = null; 485 + } 481 486 if (input.substr(peg$currPos, 3) === peg$c0) { 482 - s1 = peg$c0; 487 + s3 = peg$c0; 483 488 peg$currPos += 3; 484 489 } else { 485 - s1 = peg$FAILED; 490 + s3 = peg$FAILED; 486 491 if (peg$silentFails === 0) { peg$fail(peg$e0); } 487 492 } 488 - if (s1 !== peg$FAILED) { 489 - s2 = peg$parse_(); 490 - if (s2 !== peg$FAILED) { 491 - s3 = []; 492 - s4 = peg$parsePart(); 493 - if (s4 === peg$FAILED) { 494 - s4 = peg$parse_(); 493 + if (s3 !== peg$FAILED) { 494 + s4 = peg$parse_(); 495 + if (s4 !== peg$FAILED) { 496 + s5 = []; 497 + s6 = peg$parsePart(); 498 + if (s6 === peg$FAILED) { 499 + s6 = peg$parse_(); 495 500 } 496 - if (s4 !== peg$FAILED) { 497 - while (s4 !== peg$FAILED) { 498 - s3.push(s4); 499 - s4 = peg$parsePart(); 500 - if (s4 === peg$FAILED) { 501 - s4 = peg$parse_(); 501 + if (s6 !== peg$FAILED) { 502 + while (s6 !== peg$FAILED) { 503 + s5.push(s6); 504 + s6 = peg$parsePart(); 505 + if (s6 === peg$FAILED) { 506 + s6 = peg$parse_(); 502 507 } 503 508 } 504 509 } else { 505 - s3 = peg$FAILED; 510 + s5 = peg$FAILED; 506 511 } 507 - if (s3 !== peg$FAILED) { 508 - s4 = peg$parseEOF(); 509 - if (s4 !== peg$FAILED) { 512 + if (s5 !== peg$FAILED) { 513 + s6 = peg$parseEOF(); 514 + if (s6 !== peg$FAILED) { 510 515 peg$savedPos = s0; 511 - s0 = peg$f0(s3); 516 + s0 = peg$f0(s1, s5); 512 517 } else { 513 518 peg$currPos = s0; 514 519 s0 = peg$FAILED; ··· 530 535 } 531 536 532 537 function peg$parseDoneCommand() { 533 - var s0, s1, s2, s3; 538 + var s0, s1, s2, s3, s4, s5, s6; 534 539 535 540 s0 = peg$currPos; 541 + s1 = peg$parseSelections(); 542 + if (s1 === peg$FAILED) { 543 + s1 = null; 544 + } 545 + s2 = peg$parse_(); 546 + if (s2 === peg$FAILED) { 547 + s2 = null; 548 + } 536 549 if (input.substr(peg$currPos, 4) === peg$c1) { 537 - s1 = peg$c1; 550 + s3 = peg$c1; 538 551 peg$currPos += 4; 539 552 } else { 540 - s1 = peg$FAILED; 553 + s3 = peg$FAILED; 541 554 if (peg$silentFails === 0) { peg$fail(peg$e1); } 542 555 } 543 - if (s1 !== peg$FAILED) { 544 - s2 = []; 545 - if (input.length > peg$currPos) { 546 - s3 = input.charAt(peg$currPos); 547 - peg$currPos++; 548 - } else { 549 - s3 = peg$FAILED; 550 - if (peg$silentFails === 0) { peg$fail(peg$e2); } 556 + if (s3 !== peg$FAILED) { 557 + s4 = peg$parse_(); 558 + if (s4 === peg$FAILED) { 559 + s4 = null; 560 + } 561 + s5 = peg$parsePart(); 562 + if (s5 === peg$FAILED) { 563 + s5 = peg$parse_(); 551 564 } 552 - while (s3 !== peg$FAILED) { 553 - s2.push(s3); 554 - if (input.length > peg$currPos) { 555 - s3 = input.charAt(peg$currPos); 556 - peg$currPos++; 557 - } else { 558 - s3 = peg$FAILED; 559 - if (peg$silentFails === 0) { peg$fail(peg$e2); } 560 - } 565 + if (s5 === peg$FAILED) { 566 + s5 = null; 561 567 } 562 - s3 = peg$parseEOF(); 563 - if (s3 !== peg$FAILED) { 568 + s6 = peg$parseEOF(); 569 + if (s6 !== peg$FAILED) { 564 570 peg$savedPos = s0; 565 - s0 = peg$f1(); 571 + s0 = peg$f1(s1, s5); 566 572 } else { 567 573 peg$currPos = s0; 568 574 s0 = peg$FAILED; ··· 576 582 } 577 583 578 584 function peg$parseExplicitFilterCommand() { 579 - var s0, s1, s2, s3, s4; 585 + var s0, s1, s2, s3, s4, s5, s6; 580 586 581 587 s0 = peg$currPos; 588 + s1 = peg$parseSelections(); 589 + if (s1 === peg$FAILED) { 590 + s1 = null; 591 + } 592 + s2 = peg$parse_(); 593 + if (s2 === peg$FAILED) { 594 + s2 = null; 595 + } 582 596 if (input.substr(peg$currPos, 6) === peg$c2) { 583 - s1 = peg$c2; 597 + s3 = peg$c2; 584 598 peg$currPos += 6; 585 599 } else { 586 - s1 = peg$FAILED; 587 - if (peg$silentFails === 0) { peg$fail(peg$e3); } 600 + s3 = peg$FAILED; 601 + if (peg$silentFails === 0) { peg$fail(peg$e2); } 588 602 } 589 - if (s1 !== peg$FAILED) { 590 - s2 = []; 591 - s3 = peg$parse_(); 592 - while (s3 !== peg$FAILED) { 593 - s2.push(s3); 594 - s3 = peg$parse_(); 603 + if (s3 !== peg$FAILED) { 604 + s4 = []; 605 + s5 = peg$parse_(); 606 + while (s5 !== peg$FAILED) { 607 + s4.push(s5); 608 + s5 = peg$parse_(); 595 609 } 596 - s3 = peg$parseFilters(); 597 - if (s3 !== peg$FAILED) { 598 - s4 = peg$parseEOF(); 599 - if (s4 !== peg$FAILED) { 610 + s5 = peg$parseFilters(); 611 + if (s5 !== peg$FAILED) { 612 + s6 = peg$parseEOF(); 613 + if (s6 !== peg$FAILED) { 600 614 peg$savedPos = s0; 601 - s0 = peg$f2(s3); 615 + s0 = peg$f2(s1, s5); 602 616 } else { 603 617 peg$currPos = s0; 604 618 s0 = peg$FAILED; ··· 615 629 return s0; 616 630 } 617 631 618 - function peg$parseImplicitFilterCommand() { 619 - var s0, s1, s2; 620 - 621 - s0 = peg$currPos; 622 - s1 = []; 623 - s2 = peg$parseFilters(); 624 - if (s2 !== peg$FAILED) { 625 - while (s2 !== peg$FAILED) { 626 - s1.push(s2); 627 - s2 = peg$parseFilters(); 628 - } 629 - } else { 630 - s1 = peg$FAILED; 631 - } 632 - if (s1 !== peg$FAILED) { 633 - s2 = peg$parseEOF(); 634 - if (s2 !== peg$FAILED) { 635 - peg$savedPos = s0; 636 - s0 = peg$f3(s1); 637 - } else { 638 - peg$currPos = s0; 639 - s0 = peg$FAILED; 640 - } 641 - } else { 642 - peg$currPos = s0; 643 - s0 = peg$FAILED; 644 - } 645 - 646 - return s0; 647 - } 648 - 649 632 function peg$parseFilters() { 650 633 var s0, s1, s2, s3, s4, s5; 651 634 652 635 s0 = peg$currPos; 653 - s1 = peg$parseFilter(); 636 + s1 = peg$parsePart(); 654 637 if (s1 !== peg$FAILED) { 655 638 s2 = []; 656 639 s3 = peg$currPos; 657 640 s4 = peg$parse_(); 658 641 if (s4 !== peg$FAILED) { 659 - s5 = peg$parseFilter(); 642 + s5 = peg$parsePart(); 660 643 if (s5 !== peg$FAILED) { 661 644 s4 = [s4, s5]; 662 645 s3 = s4; ··· 673 656 s3 = peg$currPos; 674 657 s4 = peg$parse_(); 675 658 if (s4 !== peg$FAILED) { 676 - s5 = peg$parseFilter(); 659 + s5 = peg$parsePart(); 677 660 if (s5 !== peg$FAILED) { 678 661 s4 = [s4, s5]; 679 662 s3 = s4; ··· 687 670 } 688 671 } 689 672 peg$savedPos = s0; 690 - s0 = peg$f4(s1, s2); 673 + s0 = peg$f3(s1, s2); 691 674 } else { 692 675 peg$currPos = s0; 693 676 s0 = peg$FAILED; ··· 707 690 peg$currPos++; 708 691 } else { 709 692 s2 = peg$FAILED; 710 - if (peg$silentFails === 0) { peg$fail(peg$e4); } 693 + if (peg$silentFails === 0) { peg$fail(peg$e3); } 711 694 } 712 695 if (s2 !== peg$FAILED) { 713 696 s3 = peg$parseInteger(); 714 697 if (s3 !== peg$FAILED) { 715 698 peg$savedPos = s0; 716 - s0 = peg$f5(s1, s3); 699 + s0 = peg$f4(s1, s3); 717 700 } else { 718 701 peg$currPos = s0; 719 702 s0 = peg$FAILED; ··· 737 720 s1 = peg$parseInteger(); 738 721 if (s1 !== peg$FAILED) { 739 722 peg$savedPos = s0; 740 - s1 = peg$f6(s1); 723 + s1 = peg$f5(s1); 741 724 } 742 725 s0 = s1; 743 726 744 727 return s0; 745 728 } 746 729 747 - function peg$parseFilter() { 730 + function peg$parseSelections() { 731 + var s0, s1, s2, s3, s4, s5; 732 + 733 + s0 = peg$currPos; 734 + s1 = peg$parseSelection(); 735 + if (s1 !== peg$FAILED) { 736 + s2 = []; 737 + s3 = peg$currPos; 738 + s4 = peg$parse_(); 739 + if (s4 !== peg$FAILED) { 740 + s5 = peg$parseSelection(); 741 + if (s5 !== peg$FAILED) { 742 + s4 = [s4, s5]; 743 + s3 = s4; 744 + } else { 745 + peg$currPos = s3; 746 + s3 = peg$FAILED; 747 + } 748 + } else { 749 + peg$currPos = s3; 750 + s3 = peg$FAILED; 751 + } 752 + while (s3 !== peg$FAILED) { 753 + s2.push(s3); 754 + s3 = peg$currPos; 755 + s4 = peg$parse_(); 756 + if (s4 !== peg$FAILED) { 757 + s5 = peg$parseSelection(); 758 + if (s5 !== peg$FAILED) { 759 + s4 = [s4, s5]; 760 + s3 = s4; 761 + } else { 762 + peg$currPos = s3; 763 + s3 = peg$FAILED; 764 + } 765 + } else { 766 + peg$currPos = s3; 767 + s3 = peg$FAILED; 768 + } 769 + } 770 + peg$savedPos = s0; 771 + s0 = peg$f6(s1, s2); 772 + } else { 773 + peg$currPos = s0; 774 + s0 = peg$FAILED; 775 + } 776 + 777 + return s0; 778 + } 779 + 780 + function peg$parseSelection() { 748 781 var s0; 749 782 750 783 s0 = peg$parseIdFilter(); 751 784 if (s0 === peg$FAILED) { 752 785 s0 = peg$parseAttribute(); 753 - if (s0 === peg$FAILED) { 754 - s0 = peg$parsePart(); 755 - } 756 786 } 757 787 758 788 return s0; ··· 774 804 peg$currPos++; 775 805 } else { 776 806 s4 = peg$FAILED; 777 - if (peg$silentFails === 0) { peg$fail(peg$e5); } 807 + if (peg$silentFails === 0) { peg$fail(peg$e4); } 778 808 } 779 809 if (s4 !== peg$FAILED) { 780 810 s5 = peg$parseIdRange(); ··· 800 830 peg$currPos++; 801 831 } else { 802 832 s4 = peg$FAILED; 803 - if (peg$silentFails === 0) { peg$fail(peg$e5); } 833 + if (peg$silentFails === 0) { peg$fail(peg$e4); } 804 834 } 805 835 if (s4 !== peg$FAILED) { 806 836 s5 = peg$parseIdRange(); ··· 824 854 peg$currPos++; 825 855 } else { 826 856 s3 = peg$FAILED; 827 - if (peg$silentFails === 0) { peg$fail(peg$e5); } 857 + if (peg$silentFails === 0) { peg$fail(peg$e4); } 828 858 } 829 859 if (s3 === peg$FAILED) { 830 860 s3 = null; ··· 890 920 peg$currPos++; 891 921 } else { 892 922 s1 = peg$FAILED; 893 - if (peg$silentFails === 0) { peg$fail(peg$e6); } 923 + if (peg$silentFails === 0) { peg$fail(peg$e5); } 894 924 } 895 925 if (s1 !== peg$FAILED) { 896 926 s2 = peg$parseTimeValue(); ··· 918 948 peg$currPos += 4; 919 949 } else { 920 950 s1 = peg$FAILED; 921 - if (peg$silentFails === 0) { peg$fail(peg$e7); } 951 + if (peg$silentFails === 0) { peg$fail(peg$e6); } 922 952 } 923 953 if (s1 === peg$FAILED) { 924 954 if (input.substr(peg$currPos, 8) === peg$c7) { ··· 926 956 peg$currPos += 8; 927 957 } else { 928 958 s1 = peg$FAILED; 929 - if (peg$silentFails === 0) { peg$fail(peg$e8); } 959 + if (peg$silentFails === 0) { peg$fail(peg$e7); } 930 960 } 931 961 if (s1 === peg$FAILED) { 932 962 if (input.charCodeAt(peg$currPos) === 43) { ··· 934 964 peg$currPos++; 935 965 } else { 936 966 s1 = peg$FAILED; 937 - if (peg$silentFails === 0) { peg$fail(peg$e9); } 967 + if (peg$silentFails === 0) { peg$fail(peg$e8); } 938 968 } 939 969 } 940 970 } ··· 964 994 peg$currPos += 9; 965 995 } else { 966 996 s1 = peg$FAILED; 967 - if (peg$silentFails === 0) { peg$fail(peg$e10); } 997 + if (peg$silentFails === 0) { peg$fail(peg$e9); } 968 998 } 969 999 if (s1 !== peg$FAILED) { 970 1000 s2 = input.charAt(peg$currPos); ··· 972 1002 peg$currPos++; 973 1003 } else { 974 1004 s2 = peg$FAILED; 975 - if (peg$silentFails === 0) { peg$fail(peg$e11); } 1005 + if (peg$silentFails === 0) { peg$fail(peg$e10); } 976 1006 } 977 1007 if (s2 !== peg$FAILED) { 978 1008 peg$savedPos = s0; ··· 998 1028 peg$currPos++; 999 1029 } else { 1000 1030 s1 = peg$FAILED; 1001 - if (peg$silentFails === 0) { peg$fail(peg$e12); } 1031 + if (peg$silentFails === 0) { peg$fail(peg$e11); } 1002 1032 } 1003 1033 if (s1 !== peg$FAILED) { 1004 1034 s2 = peg$parseWord(); ··· 1027 1057 peg$currPos++; 1028 1058 } else { 1029 1059 s2 = peg$FAILED; 1030 - if (peg$silentFails === 0) { peg$fail(peg$e13); } 1060 + if (peg$silentFails === 0) { peg$fail(peg$e12); } 1031 1061 } 1032 1062 if (s2 !== peg$FAILED) { 1033 1063 while (s2 !== peg$FAILED) { ··· 1037 1067 peg$currPos++; 1038 1068 } else { 1039 1069 s2 = peg$FAILED; 1040 - if (peg$silentFails === 0) { peg$fail(peg$e13); } 1070 + if (peg$silentFails === 0) { peg$fail(peg$e12); } 1041 1071 } 1042 1072 } 1043 1073 } else { ··· 1062 1092 peg$currPos++; 1063 1093 } else { 1064 1094 s2 = peg$FAILED; 1065 - if (peg$silentFails === 0) { peg$fail(peg$e14); } 1095 + if (peg$silentFails === 0) { peg$fail(peg$e13); } 1066 1096 } 1067 1097 if (s2 !== peg$FAILED) { 1068 1098 while (s2 !== peg$FAILED) { ··· 1072 1102 peg$currPos++; 1073 1103 } else { 1074 1104 s2 = peg$FAILED; 1075 - if (peg$silentFails === 0) { peg$fail(peg$e14); } 1105 + if (peg$silentFails === 0) { peg$fail(peg$e13); } 1076 1106 } 1077 1107 } 1078 1108 } else { ··· 1084 1114 peg$currPos += 2; 1085 1115 } else { 1086 1116 s2 = peg$FAILED; 1087 - if (peg$silentFails === 0) { peg$fail(peg$e15); } 1117 + if (peg$silentFails === 0) { peg$fail(peg$e14); } 1088 1118 } 1089 1119 if (s2 === peg$FAILED) { 1090 1120 if (input.substr(peg$currPos, 2) === peg$c12) { ··· 1092 1122 peg$currPos += 2; 1093 1123 } else { 1094 1124 s2 = peg$FAILED; 1095 - if (peg$silentFails === 0) { peg$fail(peg$e16); } 1125 + if (peg$silentFails === 0) { peg$fail(peg$e15); } 1096 1126 } 1097 1127 } 1098 1128 if (s2 === peg$FAILED) { ··· 1118 1148 peg$currPos++; 1119 1149 } else { 1120 1150 s2 = peg$FAILED; 1121 - if (peg$silentFails === 0) { peg$fail(peg$e17); } 1151 + if (peg$silentFails === 0) { peg$fail(peg$e16); } 1122 1152 } 1123 1153 if (s2 !== peg$FAILED) { 1124 1154 while (s2 !== peg$FAILED) { ··· 1128 1158 peg$currPos++; 1129 1159 } else { 1130 1160 s2 = peg$FAILED; 1131 - if (peg$silentFails === 0) { peg$fail(peg$e17); } 1161 + if (peg$silentFails === 0) { peg$fail(peg$e16); } 1132 1162 } 1133 1163 } 1134 1164 } else { ··· 1153 1183 peg$currPos++; 1154 1184 } else { 1155 1185 s2 = peg$FAILED; 1156 - if (peg$silentFails === 0) { peg$fail(peg$e18); } 1186 + if (peg$silentFails === 0) { peg$fail(peg$e17); } 1157 1187 } 1158 1188 if (s2 !== peg$FAILED) { 1159 1189 while (s2 !== peg$FAILED) { ··· 1163 1193 peg$currPos++; 1164 1194 } else { 1165 1195 s2 = peg$FAILED; 1166 - if (peg$silentFails === 0) { peg$fail(peg$e18); } 1196 + if (peg$silentFails === 0) { peg$fail(peg$e17); } 1167 1197 } 1168 1198 } 1169 1199 } else { ··· 1188 1218 peg$currPos++; 1189 1219 } else { 1190 1220 s1 = peg$FAILED; 1191 - if (peg$silentFails === 0) { peg$fail(peg$e2); } 1221 + if (peg$silentFails === 0) { peg$fail(peg$e18); } 1192 1222 } 1193 1223 peg$silentFails--; 1194 1224 if (s1 === peg$FAILED) { ··· 1202 1232 } 1203 1233 1204 1234 1205 - function makeCommand(type, filters, parts) { 1235 + function makeCommand(action, selection, parts) { 1206 1236 1207 1237 const validParts = parts || []; 1208 1238 // Extract pure description (without attributes) ··· 1228 1258 .map(part => part.value); 1229 1259 1230 1260 1231 - // Store original parts for reconstruction 1232 - const originalParts = validParts.filter(part => part !== null); 1233 - let validFilters; 1234 - if (!!filters) { 1235 - validFilters = filters.flat(); 1236 - } else { 1237 - validFilters = []; 1238 - } 1239 1261 1240 1262 return { 1241 - type: type, 1263 + action: action, 1264 + selection: selection || [], 1242 1265 description: description || [], 1243 1266 attributes: attributes || [], 1244 - filters: validFilters, 1245 1267 project: project, 1246 1268 tags: JSON.stringify(tags), 1247 1269 parts: parts || [],
+17 -24
parser/command_js.peg
··· 1 1 { 2 - function makeCommand(type, filters, parts) { 2 + function makeCommand(action, selection, parts) { 3 3 4 4 const validParts = parts || []; 5 5 // Extract pure description (without attributes) ··· 25 25 .map(part => part.value); 26 26 27 27 28 - // Store original parts for reconstruction 29 - const originalParts = validParts.filter(part => part !== null); 30 - let validFilters; 31 - if (!!filters) { 32 - validFilters = filters.flat(); 33 - } else { 34 - validFilters = []; 35 - } 36 28 37 29 return { 38 - type: type, 30 + action: action, 31 + selection: selection || [], 39 32 description: description || [], 40 33 attributes: attributes || [], 41 - filters: validFilters, 42 34 project: project, 43 35 tags: JSON.stringify(tags), 44 36 parts: parts || [], ··· 51 43 } 52 44 } 53 45 54 - Start = AddCommand / DoneCommand / ExplicitFilterCommand / ImplicitFilterCommand 46 + Start = AddCommand / DoneCommand / ExplicitFilterCommand 55 47 56 48 // ADD COMMAND 57 - AddCommand = "add" _ parts:(Part / _)+ EOF { 58 - return makeCommand('add', null, parts.filter(p => p !== null)); 49 + AddCommand = selection:Selections? _? "add" _ parts:(Part / _)+ EOF { 50 + return makeCommand('add', selection, parts.filter(p => p !== null)); 59 51 } 60 52 61 53 // DONE COMMAND 62 - DoneCommand = "done" .* EOF { 63 - return makeCommand('done', null, null); 54 + DoneCommand = selection:Selections? _? "done" _? parts:(Part / _)? EOF { 55 + return makeCommand('done', selection, parts); 64 56 } 65 57 66 - ExplicitFilterCommand = "filter" _* moreFilters:Filters EOF { 67 - return makeCommand('filter', null, moreFilters); 58 + ExplicitFilterCommand = selection:Selections? _? "filter" _* moreFilters:Filters EOF { 59 + return makeCommand('filter', selection, moreFilters); 68 60 } 69 61 70 - ImplicitFilterCommand = filters:Filters+ EOF { 71 - return makeCommand('filter', filters, null); 72 - } 73 - 74 - Filters = first:Filter rest:(_ Filter)* { 62 + Filters = first:Part rest:(_ Part)* { 75 63 return [first, ...rest.map(r => r[1])]; 76 64 } 77 65 ··· 88 76 return [id]; 89 77 } 90 78 91 - Filter = IdFilter / Attribute / Part 79 + Selections = first:Selection rest:(_ Selection)* { 80 + return [first, ...rest.map(r => r[1])]; 81 + } 82 + 83 + Selection = IdFilter / Attribute 92 84 93 85 IdFilter = first:(IdRange / SingleId) rest:("," (IdRange / SingleId))* trailing:"," ? { 94 86 const ids = [first, ...rest.map(r => r[1])].flat(); ··· 161 153 162 154 EOF = !. 163 155 156 +
+8
scripts/update_parser.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + ROOT_DIR=$(git rev-parse --show-toplevel) 4 + ( 5 + cd "$ROOT_DIR/parser" 6 + npx peggy --format es command_js.peg 7 + cp command_js.js ../mast-react-vite/src/lib/ 8 + )