|
8 | 8 | import { renderMarkdown } from '../utils/render-markdown.js'; |
9 | 9 | import SafeMarkdown from '../components/SafeMarkdown.svelte'; |
10 | 10 | import MobileHeader from './MobileHeader.svelte'; |
| 11 | + import { autoGrow, enterMovesFocus } from './autoGrowTextarea.js'; |
11 | 12 | import { pageAncestors, pageChildren } from './mobilePagesData.js'; |
12 | 13 |
|
13 | 14 | // Phone page reader: rendered markdown, breadcrumb, and sub-page rows. |
14 | | - // Editing is a plain markdown textarea (the Milkdown rich editor stays |
15 | | - // desktop-only) guarded by the page's content hash against lost updates. |
| 15 | + // Editing is a borderless full-page form (same Linear-style hero fields as |
| 16 | + // the item editors) guarded by the page's content hash against lost updates. |
16 | 17 | let { workspaceId, pageId } = $props(); |
17 | 18 |
|
18 | 19 | let page = $state(null); |
|
23 | 24 | let editing = $state(false); |
24 | 25 | let draftTitle = $state(''); |
25 | 26 | let draftContent = $state(''); |
| 27 | + let draftContentField = $state(null); |
26 | 28 | let saving = $state(false); |
27 | 29 | // Guard in-place navigation (page → sub-page) against out-of-order loads. |
28 | 30 | let loadToken = 0; |
|
143 | 145 | <button class="retry" onclick={() => load(++loadToken)} disabled={loading} type="button">Retry</button> |
144 | 146 | </div> |
145 | 147 | {:else if editing} |
| 148 | + <!-- Borderless Linear-style editor: hero title + content, actions pinned |
| 149 | + to the bottom edge. Same modality as the item create/edit pages. --> |
146 | 150 | <div class="editor" data-testid="mobile-page-editor"> |
147 | | - <input |
148 | | - class="title-input" |
| 151 | + <textarea |
| 152 | + class="hero-title" |
149 | 153 | bind:value={draftTitle} |
| 154 | + rows={1} |
| 155 | + enterkeyhint="next" |
| 156 | + placeholder="Page title" |
| 157 | + use:autoGrow={draftTitle} |
| 158 | + use:enterMovesFocus={{ next: draftContentField }} |
150 | 159 | data-testid="mobile-page-title-input" |
151 | 160 | aria-label="Page title" |
152 | | - type="text" |
153 | | - /> |
| 161 | + ></textarea> |
154 | 162 | <textarea |
155 | | - class="content-input" |
| 163 | + class="hero-content" |
156 | 164 | bind:value={draftContent} |
| 165 | + bind:this={draftContentField} |
157 | 166 | data-testid="mobile-page-content-input" |
158 | 167 | aria-label="Page content (Markdown)" |
159 | 168 | spellcheck="false" |
| 169 | + placeholder="Write something…" |
160 | 170 | ></textarea> |
161 | 171 | <div class="editor-actions"> |
162 | 172 | <button class="btn secondary" onclick={cancelEditing} data-testid="mobile-page-editor-cancel" type="button">Cancel</button> |
|
341 | 351 | } |
342 | 352 | .sub-row :global(.chev) { color: var(--ds-icon-subtle, var(--ds-text-subtle)); flex-shrink: 0; } |
343 | 353 |
|
344 | | - .editor { display: flex; flex-direction: column; gap: 0.6rem; padding: 0.75rem 0.875rem 2rem; } |
345 | | - .title-input { |
346 | | - min-height: 44px; |
347 | | - padding: 0.4rem 0.6rem; |
348 | | - border: 1px solid var(--ds-border); |
349 | | - border-radius: var(--radius-lg, 8px); |
350 | | - background-color: var(--ds-surface-raised); |
351 | | - font-size: 1.125rem; |
352 | | - font-weight: var(--font-semibold, 600); |
| 354 | + /* Borderless editor fields — same hero treatment as the item editors. */ |
| 355 | + .editor { |
| 356 | + display: flex; |
| 357 | + flex-direction: column; |
| 358 | + min-height: 100%; |
| 359 | + box-sizing: border-box; |
| 360 | + padding: 0.75rem 1rem 0; |
| 361 | + gap: 0.5rem; |
| 362 | + } |
| 363 | + .hero-title { |
| 364 | + width: 100%; |
| 365 | + margin: 0.5rem 0 0; |
| 366 | + padding: 0; |
| 367 | + border: none; |
| 368 | + background: transparent; |
353 | 369 | color: var(--ds-text); |
| 370 | + font-family: inherit; |
| 371 | + font-size: 1.35rem; |
| 372 | + font-weight: var(--font-semibold, 600); |
| 373 | + line-height: 1.25; |
| 374 | + overflow: hidden; |
| 375 | + resize: none; |
354 | 376 | } |
355 | | - .content-input { |
356 | | - min-height: 55dvh; |
357 | | - padding: 0.6rem; |
358 | | - border: 1px solid var(--ds-border); |
359 | | - border-radius: var(--radius-lg, 8px); |
360 | | - background-color: var(--ds-surface-raised); |
361 | | - font-family: var(--font-mono, monospace); |
362 | | - font-size: 0.875rem; |
363 | | - line-height: 1.5; |
| 377 | + .hero-title::placeholder { color: var(--ds-text-subtlest, var(--ds-text-subtle)); font-weight: var(--font-semibold, 600); } |
| 378 | + .hero-content { |
| 379 | + width: 100%; |
| 380 | + min-height: 50dvh; |
| 381 | + padding: 0; |
| 382 | + border: none; |
| 383 | + background: transparent; |
364 | 384 | color: var(--ds-text); |
365 | | - resize: vertical; |
| 385 | + font-family: inherit; |
| 386 | + font-size: max(1rem, 16px); |
| 387 | + line-height: 1.55; |
| 388 | + resize: none; |
| 389 | + } |
| 390 | + .hero-content::placeholder { color: var(--ds-text-subtlest, var(--ds-text-subtle)); } |
| 391 | + .hero-title:focus, |
| 392 | + .hero-content:focus { outline: none; } |
| 393 | +
|
| 394 | + .editor-actions { |
| 395 | + position: sticky; |
| 396 | + bottom: 0; |
| 397 | + z-index: 20; |
| 398 | + display: flex; |
| 399 | + gap: 0.5rem; |
| 400 | + justify-content: flex-end; |
| 401 | + margin-top: auto; |
| 402 | + padding: 0.6rem 0 calc(env(safe-area-inset-bottom, 0px) + 0.75rem); |
| 403 | + background: linear-gradient(to top, var(--ds-surface) 65%, transparent); |
366 | 404 | } |
367 | | - .title-input:focus, |
368 | | - .content-input:focus { outline: 2px solid var(--ds-interactive); outline-offset: -1px; } |
369 | | - .editor-actions { display: flex; gap: 0.5rem; justify-content: flex-end; } |
370 | 405 | .btn { |
371 | 406 | display: inline-flex; |
372 | 407 | align-items: center; |
|
380 | 415 | cursor: pointer; |
381 | 416 | } |
382 | 417 | .btn.secondary { |
383 | | - border: 1px solid var(--ds-border); |
384 | | - background: var(--ds-surface); |
| 418 | + border: none; |
| 419 | + background: var(--ds-background-neutral); |
385 | 420 | color: var(--ds-text); |
386 | 421 | } |
387 | 422 | .btn.primary { |
388 | | - border: 1px solid var(--ds-interactive); |
| 423 | + border: none; |
389 | 424 | background: var(--ds-interactive); |
390 | 425 | color: var(--ds-text-inverse, #fff); |
391 | 426 | } |
|
0 commit comments