this repo has no description

localStorage saving for formatString in EventFormatter

+185 -18
+2 -2
fresh.gen.ts
··· 4 4 5 5 import * as $_404 from "./routes/_404.tsx"; 6 6 import * as $_app from "./routes/_app.tsx"; 7 + import * as $api_fetch_calendar_events from "./routes/api/fetch-calendar-events.ts"; 7 8 import * as $api_fetch_calendar_svg from "./routes/api/fetch-calendar-svg.ts"; 8 - import * as $api_fetch_calendar_text from "./routes/api/fetch-calendar-events.ts"; 9 9 import * as $api_svg_to_png from "./routes/api/svg-to-png.ts"; 10 10 import * as $index from "./routes/index.tsx"; 11 11 import * as $EventFormatter from "./islands/EventFormatter.tsx"; ··· 19 19 routes: { 20 20 "./routes/_404.tsx": $_404, 21 21 "./routes/_app.tsx": $_app, 22 + "./routes/api/fetch-calendar-events.ts": $api_fetch_calendar_events, 22 23 "./routes/api/fetch-calendar-svg.ts": $api_fetch_calendar_svg, 23 - "./routes/api/fetch-calendar-text.ts": $api_fetch_calendar_text, 24 24 "./routes/api/svg-to-png.ts": $api_svg_to_png, 25 25 "./routes/index.tsx": $index, 26 26 },
+183 -16
islands/EventFormatter.tsx
··· 1 - import { useEffect, useState } from "preact/hooks"; 1 + import { useEffect, useRef, useState } from "preact/hooks"; 2 + import { IS_BROWSER } from "$fresh/runtime.ts"; 2 3 3 4 interface EventFormatterProps { 4 5 events: ICalEvent[]; ··· 10 11 ) { 11 12 const defaultFormat = "- {timestamp} | **{summary}**{description}"; 12 13 const [formatString, setFormatString] = useState(defaultFormat); 13 - const [formattedText, setFormattedText] = useState(""); 14 + const [formatHistory, setFormatHistory] = useState<string[]>([]); 15 + const [showFormatHistory, setShowFormatHistory] = useState<boolean>(false); 16 + const formatInputRef = useRef<HTMLInputElement>(null); 17 + const formatDropdownRef = useRef<HTMLDivElement>(null); 18 + 19 + // Load format history from localStorage on component mount 20 + useEffect(() => { 21 + if (IS_BROWSER) { 22 + // Get saved format from localStorage or use default 23 + const savedFormat = localStorage.getItem("calendarFormatCurrent"); 24 + if (savedFormat) { 25 + setFormatString(savedFormat); 26 + } 27 + 28 + // Get format history from localStorage 29 + const storedFormats = localStorage.getItem("calendarFormatHistory"); 30 + if (storedFormats) { 31 + setFormatHistory(JSON.parse(storedFormats)); 32 + } 33 + } 34 + }, []); 35 + 36 + // Add click outside listener to close dropdown 37 + useEffect(() => { 38 + if (IS_BROWSER && showFormatHistory) { 39 + const handleClickOutside = (event: MouseEvent) => { 40 + if ( 41 + formatDropdownRef.current && 42 + !formatDropdownRef.current.contains(event.target as Node) && 43 + formatInputRef.current && 44 + !formatInputRef.current.contains(event.target as Node) 45 + ) { 46 + setShowFormatHistory(false); 47 + } 48 + }; 49 + 50 + document.addEventListener("mousedown", handleClickOutside); 51 + return () => { 52 + document.removeEventListener("mousedown", handleClickOutside); 53 + }; 54 + } 55 + }, [showFormatHistory]); 14 56 15 57 // Format events whenever the format string or events change 16 58 useEffect(() => { ··· 37 79 return line; 38 80 }).join("\n"); 39 81 40 - setFormattedText(formatted); 41 82 onFormattedTextChange(formatted); 42 83 }, [formatString, events]); 43 84 85 + // Save format string to localStorage when it changes 86 + useEffect(() => { 87 + if (IS_BROWSER && formatString) { 88 + localStorage.setItem("calendarFormatCurrent", formatString); 89 + } 90 + }, [formatString]); 91 + 92 + // Handle format input changes 93 + const handleFormatChange = (e: Event) => { 94 + const target = e.target as HTMLInputElement; 95 + setFormatString(target.value); 96 + }; 97 + 98 + // Handle format input focus 99 + const handleFormatFocus = () => { 100 + setShowFormatHistory(true); 101 + }; 102 + 103 + // Handle format submission 104 + const handleFormatSubmit = (e: Event) => { 105 + e.preventDefault(); 106 + 107 + if (!formatString || formatString === defaultFormat) return; 108 + 109 + // Add to history if not already present 110 + if (!formatHistory.includes(formatString)) { 111 + const newHistory = [formatString, ...formatHistory].slice(0, 10); // Keep last 10 formats 112 + setFormatHistory(newHistory); 113 + 114 + // Save to localStorage 115 + if (IS_BROWSER) { 116 + localStorage.setItem( 117 + "calendarFormatHistory", 118 + JSON.stringify(newHistory), 119 + ); 120 + } 121 + } 122 + 123 + setShowFormatHistory(false); 124 + }; 125 + 126 + // Handle format selection from history 127 + const selectFormat = (selectedFormat: string) => { 128 + setFormatString(selectedFormat); 129 + setShowFormatHistory(false); 130 + // Focus the input after selection 131 + if (formatInputRef.current) { 132 + formatInputRef.current.focus(); 133 + } 134 + }; 135 + 44 136 return ( 45 137 <div className="event-formatter"> 46 - <div className="format-input-container"> 138 + <form 139 + onSubmit={handleFormatSubmit} 140 + className="format-input-container" 141 + > 47 142 <label htmlFor="format-input">Custom Format Pattern:</label> 48 - <input 49 - id="format-input" 50 - type="text" 51 - value={formatString} 52 - onChange={(e) => 53 - setFormatString((e.target as HTMLInputElement).value)} 54 - className="format-input" 55 - placeholder="Enter format pattern" 56 - /> 143 + <div className="input-with-button"> 144 + <input 145 + id="format-input" 146 + type="text" 147 + value={formatString} 148 + onInput={handleFormatChange} 149 + onFocus={handleFormatFocus} 150 + className="format-input" 151 + placeholder="Enter format pattern" 152 + ref={formatInputRef} 153 + /> 154 + <button type="submit" className="save-format-button"> 155 + Save 156 + </button> 157 + </div> 158 + 57 159 <p className="format-help"> 58 160 Available keywords: {"{timestamp}"}, {"{summary}"},{" "} 59 161 {"{description}"}, {"{location}"} 60 162 </p> 61 - </div> 163 + 164 + {showFormatHistory && formatHistory.length > 0 && ( 165 + <div 166 + className="format-history-dropdown" 167 + ref={formatDropdownRef} 168 + > 169 + {formatHistory.map((historyFormat, index) => ( 170 + <div 171 + key={index} 172 + className="format-history-item" 173 + onClick={() => selectFormat(historyFormat)} 174 + > 175 + {historyFormat} 176 + </div> 177 + ))} 178 + </div> 179 + )} 180 + </form> 62 181 63 182 <style> 64 183 {` ··· 67 186 } 68 187 69 188 .format-input-container { 189 + position: relative; 70 190 margin-bottom: 10px; 71 191 } 72 192 193 + .input-with-button { 194 + display: flex; 195 + gap: 8px; 196 + margin-top: 5px; 197 + } 198 + 73 199 .format-input { 74 - width: 100%; 200 + flex: 1; 75 201 padding: 8px; 76 - margin-top: 5px; 77 202 border: 1px solid #ddd; 78 203 border-radius: 4px; 79 204 font-family: monospace; 80 205 } 81 206 207 + .save-format-button { 208 + padding: 8px 12px; 209 + background-color: #1976d2; 210 + color: white; 211 + border: none; 212 + border-radius: 4px; 213 + cursor: pointer; 214 + font-size: 14px; 215 + transition: background-color 0.2s; 216 + } 217 + 218 + .save-format-button:hover { 219 + background-color: #1565c0; 220 + } 221 + 82 222 .format-help { 83 223 margin-top: 5px; 84 224 font-size: 12px; 85 225 color: #666; 226 + } 227 + 228 + .format-history-dropdown { 229 + position: absolute; 230 + top: 100%; 231 + left: 0; 232 + width: 100%; 233 + max-height: 200px; 234 + overflow-y: auto; 235 + background-color: white; 236 + border: 1px solid #ccc; 237 + border-top: none; 238 + border-radius: 0 0 4px 4px; 239 + z-index: 10; 240 + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 241 + } 242 + 243 + .format-history-item { 244 + padding: 8px 12px; 245 + cursor: pointer; 246 + white-space: nowrap; 247 + overflow: hidden; 248 + text-overflow: ellipsis; 249 + } 250 + 251 + .format-history-item:hover { 252 + background-color: #f5f5f5; 86 253 } 87 254 `} 88 255 </style>