|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount } from 'svelte'; |
| 3 | + import { browser } from '$app/environment'; |
3 | 4 | import { goto } from '$app/navigation'; |
4 | 5 | import { auth } from '$lib/stores/auth.svelte'; |
5 | 6 | import { subscriptionsStore } from '$lib/stores/subscriptions.svelte'; |
|
39 | 40 |
|
40 | 41 | let showImportModal = $state(false); |
41 | 42 |
|
| 43 | + // "Save from anywhere" — browser bookmarklets + a Share Sheet shortcut. |
| 44 | + // Paste a published iCloud Shortcut link (icloud.com/shortcuts/...) into either |
| 45 | + // constant to turn the manual steps into a one-tap "Add Shortcut" button. |
| 46 | + const APPLE_SAVE_SHORTCUT_URL = |
| 47 | + 'https://www.icloud.com/shortcuts/ead7df12455949fa92271ec3d0bea3f7'; |
| 48 | + const APPLE_SUBSCRIBE_SHORTCUT_URL = |
| 49 | + 'https://www.icloud.com/shortcuts/4b70e834a8ae48ee8c039cbf01e5b8c4'; |
| 50 | +
|
| 51 | + // Build links against the current origin so they also work on staging/local. |
| 52 | + const appOrigin = browser ? window.location.origin : 'https://skyreader.app'; |
| 53 | + const saveBookmarklet = `javascript:void(window.open('${appOrigin}/save?url='+encodeURIComponent(location.href)))`; |
| 54 | + const subscribeBookmarklet = `javascript:void(window.open('${appOrigin}/subscribe?url='+encodeURIComponent(location.href)))`; |
| 55 | +
|
| 56 | + let copiedKey = $state<string | null>(null); |
| 57 | + let bookmarkletHint = $state(false); |
| 58 | +
|
| 59 | + async function copyText(text: string, key: string) { |
| 60 | + try { |
| 61 | + await navigator.clipboard.writeText(text); |
| 62 | + copiedKey = key; |
| 63 | + setTimeout(() => { |
| 64 | + if (copiedKey === key) copiedKey = null; |
| 65 | + }, 1500); |
| 66 | + } catch { |
| 67 | + // Clipboard unavailable (e.g. insecure context); the drag target still works. |
| 68 | + } |
| 69 | + } |
| 70 | +
|
| 71 | + // A bookmarklet is meant to be dragged to the bookmarks bar, not clicked here: |
| 72 | + // the app's CSP blocks the javascript: navigation anyway. Nudge the user. |
| 73 | + function showDragHint(e: MouseEvent) { |
| 74 | + e.preventDefault(); |
| 75 | + bookmarkletHint = true; |
| 76 | + } |
| 77 | +
|
42 | 78 | // PDS Sync state |
43 | 79 | let pdsSyncEnabled = $state(false); |
44 | 80 | let lastSyncSubscriptions = $state<number | null>(null); |
|
548 | 584 | </div> |
549 | 585 | </section> |
550 | 586 |
|
| 587 | + <section class="card"> |
| 588 | + <h2>Save from anywhere</h2> |
| 589 | + <p>Save an article or subscribe to a feed without leaving the page you're reading.</p> |
| 590 | + |
| 591 | + <h3 class="subhead">On your computer</h3> |
| 592 | + <p class="hint-text">Drag a button to your bookmarks bar, then click it on any page:</p> |
| 593 | + <div class="bookmarklet-row"> |
| 594 | + <a class="bookmarklet" href={saveBookmarklet} onclick={showDragHint}>Save to Skyreader</a> |
| 595 | + <a class="bookmarklet" href={subscribeBookmarklet} onclick={showDragHint}> |
| 596 | + Subscribe in Skyreader |
| 597 | + </a> |
| 598 | + </div> |
| 599 | + {#if bookmarkletHint} |
| 600 | + <p class="hint-text">Drag these up to your bookmarks bar. Clicking here won't run them.</p> |
| 601 | + {/if} |
| 602 | + <div class="button-row"> |
| 603 | + <button class="btn btn-secondary" onclick={() => copyText(saveBookmarklet, 'save')}> |
| 604 | + {copiedKey === 'save' ? 'Copied' : 'Copy Save link'} |
| 605 | + </button> |
| 606 | + <button class="btn btn-secondary" onclick={() => copyText(subscribeBookmarklet, 'subscribe')}> |
| 607 | + {copiedKey === 'subscribe' ? 'Copied' : 'Copy Subscribe link'} |
| 608 | + </button> |
| 609 | + </div> |
| 610 | + |
| 611 | + <h3 class="subhead">On iPhone or iPad</h3> |
| 612 | + {#if APPLE_SAVE_SHORTCUT_URL || APPLE_SUBSCRIBE_SHORTCUT_URL} |
| 613 | + <p class="hint-text">Add a shortcut, then use it from any Share Sheet:</p> |
| 614 | + <div class="button-row"> |
| 615 | + {#if APPLE_SAVE_SHORTCUT_URL} |
| 616 | + <a |
| 617 | + class="btn btn-secondary" |
| 618 | + href={APPLE_SAVE_SHORTCUT_URL} |
| 619 | + target="_blank" |
| 620 | + rel="noopener noreferrer">Add Save shortcut</a |
| 621 | + > |
| 622 | + {/if} |
| 623 | + {#if APPLE_SUBSCRIBE_SHORTCUT_URL} |
| 624 | + <a |
| 625 | + class="btn btn-secondary" |
| 626 | + href={APPLE_SUBSCRIBE_SHORTCUT_URL} |
| 627 | + target="_blank" |
| 628 | + rel="noopener noreferrer">Add Subscribe shortcut</a |
| 629 | + > |
| 630 | + {/if} |
| 631 | + </div> |
| 632 | + {:else} |
| 633 | + <details class="shortcut-steps"> |
| 634 | + <summary>Build a Share Sheet shortcut</summary> |
| 635 | + <ol> |
| 636 | + <li>Open the <strong>Shortcuts</strong> app and create a new shortcut.</li> |
| 637 | + <li> |
| 638 | + In its settings, turn on <strong>Show in Share Sheet</strong> and set the type to |
| 639 | + <strong>URLs</strong>. |
| 640 | + </li> |
| 641 | + <li>Add <strong>Get URLs from Input</strong>, set to Shortcut Input.</li> |
| 642 | + <li>Add <strong>URL Encode</strong> (Encode) on that URL.</li> |
| 643 | + <li> |
| 644 | + Add <strong>Text</strong>: <code>{appOrigin}/save?url=</code> followed by the Encoded |
| 645 | + URL. Use <code>/subscribe?url=</code> instead for a feed shortcut. |
| 646 | + </li> |
| 647 | + <li>Add <strong>Open URLs</strong> with that text.</li> |
| 648 | + </ol> |
| 649 | + <p class="hint-text"> |
| 650 | + Share any page, pick your shortcut, and it opens here and saves while you stay logged in. |
| 651 | + </p> |
| 652 | + </details> |
| 653 | + {/if} |
| 654 | + </section> |
| 655 | + |
551 | 656 | <section class="card"> |
552 | 657 | <h2>About</h2> |
553 | 658 | <p>Skyreader is a reading app that helps you make sense of what you read.</p> |
|
891 | 996 | flex-wrap: wrap; |
892 | 997 | } |
893 | 998 |
|
| 999 | + .subhead { |
| 1000 | + font-size: var(--text-md); |
| 1001 | + font-weight: var(--weight-semibold); |
| 1002 | + margin: 1.25rem 0 0.5rem; |
| 1003 | + } |
| 1004 | +
|
| 1005 | + .hint-text { |
| 1006 | + font-size: var(--text-sm); |
| 1007 | + color: var(--color-text-secondary); |
| 1008 | + margin: 0 0 0.5rem; |
| 1009 | + } |
| 1010 | +
|
| 1011 | + .bookmarklet-row { |
| 1012 | + display: flex; |
| 1013 | + flex-wrap: wrap; |
| 1014 | + gap: 0.5rem; |
| 1015 | + margin-bottom: 0.5rem; |
| 1016 | + } |
| 1017 | +
|
| 1018 | + .bookmarklet { |
| 1019 | + display: inline-flex; |
| 1020 | + align-items: center; |
| 1021 | + padding: 0.45rem 0.85rem; |
| 1022 | + border: 1px solid var(--color-primary, #0066cc); |
| 1023 | + border-radius: 6px; |
| 1024 | + background: var(--color-bg); |
| 1025 | + color: var(--color-primary, #0066cc); |
| 1026 | + font-size: var(--text-md); |
| 1027 | + font-weight: var(--weight-medium); |
| 1028 | + text-decoration: none; |
| 1029 | + cursor: grab; |
| 1030 | + } |
| 1031 | +
|
| 1032 | + .bookmarklet:active { |
| 1033 | + cursor: grabbing; |
| 1034 | + } |
| 1035 | +
|
| 1036 | + .shortcut-steps summary { |
| 1037 | + cursor: pointer; |
| 1038 | + font-weight: var(--weight-medium); |
| 1039 | + color: var(--color-primary); |
| 1040 | + } |
| 1041 | +
|
| 1042 | + .shortcut-steps ol { |
| 1043 | + margin: 0.75rem 0; |
| 1044 | + padding-left: 1.25rem; |
| 1045 | + display: flex; |
| 1046 | + flex-direction: column; |
| 1047 | + gap: 0.4rem; |
| 1048 | + font-size: var(--text-md); |
| 1049 | + color: var(--color-text); |
| 1050 | + } |
| 1051 | +
|
| 1052 | + .shortcut-steps code { |
| 1053 | + font-family: var(--font-mono, monospace); |
| 1054 | + font-size: 0.9em; |
| 1055 | + background: var(--color-bg-secondary); |
| 1056 | + padding: 0.05em 0.3em; |
| 1057 | + border-radius: 4px; |
| 1058 | + word-break: break-all; |
| 1059 | + } |
| 1060 | +
|
894 | 1061 | .loading { |
895 | 1062 | color: var(--color-text-secondary); |
896 | 1063 | font-style: italic; |
|
0 commit comments