Skip to content

Commit 1a1ce62

Browse files
authored
Merge pull request #20 from soranjiro/feat/secret-mode
feat: Implement favicon and Markdown rendering for itinerary memos wi…
2 parents 0687504 + 1d27737 commit 1a1ce62

4 files changed

Lines changed: 156 additions & 4 deletions

File tree

apps/web/src/lib/themes/standard-autumn/ItineraryView.svelte

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { authApi } from "$lib/api/auth";
77
import { onMount } from "svelte";
88
import StepList from "./StepList.svelte";
9+
import { marked } from "marked";
910
import "./theme.css";
1011
1112
interface Props {
@@ -184,7 +185,7 @@
184185
185186
async function handleMemoUpdate() {
186187
if (onUpdateItinerary) {
187-
await onUpdateItinerary({ memo: editedMemo.trim() || undefined });
188+
await onUpdateItinerary({ memo: editedMemo.trim() });
188189
}
189190
showMemoDialog = false;
190191
}
@@ -298,6 +299,16 @@
298299
});
299300
}
300301
}
302+
303+
// Configure marked options
304+
marked.setOptions({
305+
breaks: true,
306+
gfm: true,
307+
});
308+
309+
function renderMarkdown(text: string): string {
310+
return marked.parse(text, { async: false }) as string;
311+
}
301312
</script>
302313

303314
<div class="standard-autumn-theme">
@@ -355,14 +366,17 @@
355366
<!-- svelte-ignore a11y_no_static_element_interactions -->
356367
<div
357368
class="standard-autumn-memo-display"
358-
onclick={() => {
369+
onclick={(e) => {
370+
// Don't open edit dialog if clicking a link
371+
if ((e.target as HTMLElement).closest("a")) return;
372+
359373
if (hasEditPermission) {
360374
editedMemo = itinerary.memo || "";
361375
showMemoDialog = true;
362376
}
363377
}}
364378
>
365-
{itinerary.memo}
379+
{@html renderMarkdown(itinerary.memo)}
366380
</div>
367381
{:else if hasEditPermission}
368382
<button
@@ -623,6 +637,7 @@
623637
onchange={handleSecretModeUpdate}
624638
class="standard-autumn-settings-select"
625639
>
640+
<option value={0}>即時</option>
626641
<option value={15}>15分前</option>
627642
<option value={30}>30分前</option>
628643
<option value={60}>1時間前</option>

apps/web/src/lib/themes/standard-autumn/theme.css

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,49 @@ body {
228228
width: 100%;
229229
margin: 0 auto;
230230
backdrop-filter: blur(4px);
231-
white-space: pre-wrap;
231+
white-space: normal;
232232
word-break: break-word;
233233
position: relative;
234+
text-align: left;
235+
}
236+
237+
.standard-autumn-memo-display p {
238+
margin: 0 0 0.5em 0;
239+
}
240+
241+
.standard-autumn-memo-display p:last-child {
242+
margin-bottom: 0;
243+
}
244+
245+
.standard-autumn-memo-display a {
246+
color: var(--standard-autumn-primary);
247+
text-decoration: underline;
248+
font-weight: 600;
249+
position: relative;
250+
z-index: 5;
251+
transition: color 0.2s;
252+
}
253+
254+
.standard-autumn-memo-display a:hover {
255+
color: var(--standard-autumn-accent);
256+
}
257+
258+
.standard-autumn-memo-display ul,
259+
.standard-autumn-memo-display ol {
260+
margin: 0.5em 0;
261+
padding-left: 1.5em;
262+
}
263+
264+
.standard-autumn-memo-display ul {
265+
list-style-type: disc;
266+
}
267+
268+
.standard-autumn-memo-display ol {
269+
list-style-type: decimal;
270+
}
271+
272+
.standard-autumn-memo-display li {
273+
margin: 0.25em 0;
234274
}
235275

236276
.standard-autumn-memo-display::before {
@@ -275,6 +315,11 @@ body {
275315
box-shadow: var(--standard-autumn-shadow-sm);
276316
}
277317

318+
.standard-autumn-share-icon svg {
319+
width: 24px;
320+
height: 24px;
321+
}
322+
278323
.standard-autumn-copy-msg {
279324
position: absolute;
280325
top: 1.5rem;
@@ -999,6 +1044,11 @@ body {
9991044
box-shadow: var(--standard-autumn-shadow-sm);
10001045
}
10011046

1047+
.standard-autumn-carousel-btn svg {
1048+
width: 24px;
1049+
height: 24px;
1050+
}
1051+
10021052
.standard-autumn-carousel-btn:hover:not(:disabled) {
10031053
background: var(--standard-autumn-primary);
10041054
color: white;

apps/web/static/favicon.png

7.99 KB
Loading

convert_favicon.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Convert favicon.png to various formats and sizes with padding removed.
4+
"""
5+
6+
from PIL import Image
7+
import io
8+
import base64
9+
10+
def trim_whitespace(image):
11+
"""Remove excess whitespace/transparent areas from an image."""
12+
# Convert to RGBA if not already
13+
if image.mode != 'RGBA':
14+
image = image.convert('RGBA')
15+
16+
# Get bounding box of non-transparent pixels
17+
bbox = image.getbbox()
18+
19+
if bbox:
20+
# Crop to content
21+
image = image.crop(bbox)
22+
23+
return image
24+
25+
def create_icon(source_path, output_path, size, add_padding=True):
26+
"""Create an icon from source image with specified size."""
27+
# Open and trim the source image
28+
img = Image.open(source_path)
29+
img = trim_whitespace(img)
30+
31+
# Add padding if requested (10% on each side)
32+
if add_padding:
33+
padding = int(size * 0.05) # 5% padding on each side
34+
new_size = size - (padding * 2)
35+
img.thumbnail((new_size, new_size), Image.Resampling.LANCZOS)
36+
37+
# Create new image with padding
38+
final_img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
39+
offset = ((size - img.width) // 2, (size - img.height) // 2)
40+
final_img.paste(img, offset, img if img.mode == 'RGBA' else None)
41+
else:
42+
img.thumbnail((size, size), Image.Resampling.LANCZOS)
43+
final_img = img
44+
45+
final_img.save(output_path, 'PNG', optimize=True, compress_level=9)
46+
print(f"Created {output_path} ({size}x{size})")
47+
48+
def create_svg(source_path, output_path):
49+
"""Create SVG from source image."""
50+
# Open and trim the source image
51+
img = Image.open(source_path)
52+
img = trim_whitespace(img)
53+
54+
# Save as PNG to base64 with optimization
55+
temp_png = io.BytesIO()
56+
img.save(temp_png, 'PNG', optimize=True, compress_level=9)
57+
img_data = temp_png.getvalue()
58+
img_base64 = base64.b64encode(img_data).decode('utf-8')
59+
60+
width, height = img.size
61+
62+
svg_content = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}">
63+
<image href="data:image/png;base64,{img_base64}" width="{width}" height="{height}"/>
64+
</svg>'''
65+
66+
with open(output_path, 'w') as f:
67+
f.write(svg_content)
68+
69+
print(f"Created {output_path}")
70+
71+
def main():
72+
base_path = '/Users/user/Documents/03_app/tabitabi/apps/web/static'
73+
source = f'{base_path}/favicon.png'
74+
75+
# Create trimmed favicon.svg
76+
create_svg(source, f'{base_path}/favicon.svg')
77+
78+
# Create icon-192.png
79+
create_icon(source, f'{base_path}/icons/icon-192.png', 192)
80+
81+
# Create icon-512.png
82+
create_icon(source, f'{base_path}/icons/icon-512.png', 512)
83+
84+
print("\nAll icons created successfully!")
85+
86+
if __name__ == '__main__':
87+
main()

0 commit comments

Comments
 (0)