Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
806d298
feat: add settings gear component for opening options page
Seismix Sep 30, 2025
e350ef3
refactor: improve layout and styling of action buttons and options page
Seismix Sep 30, 2025
c9483cb
feat: add advanced settings section and settings gear component for A…
Seismix Sep 30, 2025
06ba5da
feat: add patch notes button and dedicated patch notes page with updates
Seismix Sep 30, 2025
b947278
feat: implement dynamic patch notes display
Seismix Sep 30, 2025
31b9015
refactor: improve formatting and layout of patch notes component
Seismix Oct 1, 2025
2412ff5
refactor: adjust spacing for update card layout
Seismix Oct 1, 2025
e42389f
feat: enhance patch notes display with new and fixes sections
Seismix Oct 1, 2025
44f8f7a
fix: add missing comment for icon source in PatchNotesButton component
Seismix Oct 1, 2025
b191827
fix: update release date for version 1.2.0 in patch notes
Seismix Oct 1, 2025
8e5b141
fix: reword descriptions in patch note 1.2.0
Seismix Oct 1, 2025
1219497
fix: reword more descriptions in 1.2.0
Seismix Oct 3, 2025
749ad1e
feat: implement BackButton and PatchNotesButton components; restructu…
Seismix Oct 6, 2025
e17d4ce
feat: add icon components and update button styles; implement variant…
Seismix Oct 6, 2025
e65c4f8
refactor: organize components into icons and buttons directories
Seismix Oct 6, 2025
285294c
refactor: change ActionButtons to appear side-by-side and not stacked
Seismix Oct 6, 2025
0f14644
feat: add expanding patch notes and refactor reduced motion checking
Seismix Oct 7, 2025
4dc6457
feat: add enableAnimations setting and adjust scroll behavior based o…
Seismix Oct 7, 2025
05be88c
fix: allow changing scroll behavior to instant after enabling animations
Seismix Oct 7, 2025
51ac474
refactor: streamline scroll behavior handling and remove enableAnimat…
Seismix Oct 7, 2025
fe90b6d
feat: add GitHubButton and GitHubIcon components; refactor imports fo…
Seismix Oct 7, 2025
f4680e7
feat: enhance mobile touch target styles for buttons and icons; add n…
Seismix Oct 7, 2025
2dba81d
fix: improve layout and overflow handling for Popup on Android
Seismix Oct 7, 2025
806f76f
feat: load and utilize extension settings for patch notes scrolling b…
Seismix Oct 7, 2025
b4420c9
feat: implement patch notes view
Seismix Oct 7, 2025
2415f0f
refactor: streamline settings loading and watching logic in Options a…
Seismix Oct 11, 2025
c2581a1
feat: refactor icon components to use a unified Icon wrapper and add …
Seismix Oct 11, 2025
0b930b6
feat: add PageHeader component for consistent header layout across op…
Seismix Oct 11, 2025
3a3b30d
chore: formatting
Seismix Oct 11, 2025
f8fef38
fix: update import paths for button components to use consistent alias
Seismix Oct 11, 2025
cba7bf3
refactor: streamline settings loading logic across components and pre…
Seismix Oct 11, 2025
4bf1967
chore: format
Seismix Oct 11, 2025
b6a30b7
chore: update version to 1.4.1 in package.json
Seismix Oct 11, 2025
ac67033
feat: add patch notes for version 1.3.0 and 1.4.0 with new features a…
Seismix Oct 27, 2025
5e3bf8c
fix: remove hover effect from update cards to simplify UI interactions
Seismix Oct 27, 2025
724bf09
fix: update patch notes for versions 1.3.0 and 1.4.0 to reflect accur…
Seismix Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "royalrefresh",
"version": "1.3.1",
"version": "1.4.1",
"description": "A web extension for royalroad.com. For people who juggle multiple stories",
"main": "background.js",
"directories": {
Expand Down
18 changes: 18 additions & 0 deletions src/assets/patches/v1.0.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "1.0.0",
"releasedOn": "2025-03-05",
"summary": "Initial stable release — settings, improved recaps, and first public builds for Chrome and Firefox.",
"new": [
"Options and settings page lets you control refresh behavior and recap preferences",
"Recap length preference now sticks across sessions",
"Recap text formatting is clearer and easier to read",
"Short loading message appears on the first chapter to reduce perceived delay",
"Packaged builds available for Chrome and Firefox, including Android support"
],
"fixes": [
"Recap failures now show friendly error messages instead of silently failing",
"Chapter navigation reliably scrolls chapters into view",
"Popup and options buttons have improved styling and accessibility",
"Popup-to-content message passing stays reliable during interactions"
]
}
12 changes: 12 additions & 0 deletions src/assets/patches/v1.1.1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "1.1.1",
"releasedOn": "2025-03-06",
"summary": "Bugfix release: recap visibility and settings reset behavior improvements.",
"new": [],
"fixes": [
"Recap button appears on stories whose headers use background images",
"Reset to default settings only replaces CSS selector settings; other preferences stay put",
"Recap detection is consistent across different layouts so the button appears when available",
"Minor UI tweaks keep the recap button and popup easier to use"
]
}
17 changes: 17 additions & 0 deletions src/assets/patches/v1.2.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "1.2.0",
"releasedOn": "2025-09-12",
"summary": "Expanded recap customization and polished the popup experience.",
"new": [
"Smooth scroll setting has been renamed to jump to recap, with existing preferences migrated",
"Jump to recap setting lets you disable the jump, and if enabled, choose between smooth or instant modes",
"Settings pages now change from dark to light mode based on the OS theme"
],
"fixes": [
"Jump to recap option respects reduced-motion OS setting with visual feedback",
"Extension settings now center correctly in Firefox",
"Warning labels appear in fiction blurbs on first chapters",
"Toggle buttons no longer stay pressed after clicking",
"Recap HTML is sanitized to prevent security risks"
]
}
5 changes: 5 additions & 0 deletions src/assets/patches/v1.3.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": "1.3.0",
"releasedOn": "2025-09-24",
"summary": "Better code tooling: This release focuses on internal improvements with no new user-facing features."
}
19 changes: 19 additions & 0 deletions src/assets/patches/v1.4.0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "1.4.0",
"releasedOn": "2025-10-22",
"summary": "Animation preferences and enhanced patch notes navigation.",
"new": [
"Patch notes viewer shows what's new in each version, accessible from the popup",
"Improved touch targets on mobile for better button and icon interactions",
"Animation preferences control smooth scrolling to recaps with an enable animations toggle",
"Expanding patch notes feature allows viewing full details for each version update",
"Scroll behavior can be adjusted to instant mode even after enabling animations"
],
"fixes": [
"Popup layout and overflow handling works correctly on Android devices",
"Settings loading logic streamlined to prevent unnecessary re-renders",
"Scroll behavior respects animation settings and changes apply immediately",
"Reduced motion preferences checked more reliably for accessibility",
"Settings watching logic simplified for better performance across components"
]
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script lang="ts">
import type { ExtensionSettings } from "~/types/types"
import {
updateSettings,
restoreDefaults,
restoreSelectors,
updateSettings,
} from "~/lib/utils/storage-utils"
import type { ExtensionSettings } from "~/types/types"

let {
settings,
Expand Down Expand Up @@ -84,7 +84,7 @@
</script>

{#if showRestoreSelectors}
<div class="action-buttons">
<div class="action-buttons-full">
<button
type="button"
class="btn btn-danger"
Expand Down Expand Up @@ -116,15 +116,28 @@
<style>
@import "~/lib/styles/buttons.css";

.action-buttons-full {
display: flex;
gap: var(--spacing-lg);
margin-top: var(--spacing-xxl);
width: 100%;
}

.action-buttons-full button {
flex: 1;
white-space: normal;
}

.action-buttons {
display: flex;
gap: var(--spacing-lg);
margin-top: var(--spacing-xxl);
width: 100%;
}

.action-buttons button {
flex: auto;
white-space: nowrap;
flex: 1;
white-space: normal;
}

/* State-specific styles */
Expand Down
22 changes: 22 additions & 0 deletions src/components/buttons/BackButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script lang="ts">
import { ArrowLeftIcon } from "~/components/icons"
import type { IconButtonProps } from "~/types/icon-button"

interface Props extends IconButtonProps {
onclick: () => void
}

let { onclick, variant = "default" }: Props = $props()
</script>

<button aria-label="Back to settings" class="icon-button back-button" {onclick}>
<ArrowLeftIcon {variant} />
</button>

<style>
@import "~/lib/styles/icon-button.css";

.back-button {
position: relative;
}
</style>
25 changes: 25 additions & 0 deletions src/components/buttons/GitHubButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts">
import { GitHubIcon } from "~/components/icons"
import type { IconButtonProps } from "~/types/icon-button"

let { variant = "default" }: Omit<IconButtonProps, "onclick"> = $props()

const openGitHub = () => {
window.open(
"https://github.com/Seismix/royalrefresh",
"_blank",
"noopener,noreferrer",
)
}
</script>

<button
aria-label="Open GitHub repository"
class="icon-button github-button"
onclick={openGitHub}>
<GitHubIcon {variant} />
</button>

<style>
@import "~/lib/styles/icon-button.css";
</style>
17 changes: 17 additions & 0 deletions src/components/buttons/PatchNotesButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts">
import { JournalIcon } from "~/components/icons"
import type { IconButtonProps } from "~/types/icon-button"

let { onclick, variant = "default" }: IconButtonProps = $props()
</script>

<button
aria-label="Open patch notes"
class="icon-button patch-notes-button"
{onclick}>
<JournalIcon {variant} />
</button>

<style>
@import "~/lib/styles/icon-button.css";
</style>
22 changes: 22 additions & 0 deletions src/components/buttons/SettingsButton.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script lang="ts">
import { browser } from "wxt/browser"
import { GearIcon } from "~/components/icons"
import type { IconButtonProps } from "~/types/icon-button"

let { variant = "default" }: Omit<IconButtonProps, "onclick"> = $props()

const openSettings = async () => {
await browser.runtime.openOptionsPage()
}
</script>

<button
aria-label="Open settings"
class="icon-button settings-gear"
onclick={openSettings}>
<GearIcon {variant} />
</button>

<style>
@import "~/lib/styles/icon-button.css";
</style>
5 changes: 5 additions & 0 deletions src/components/buttons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as ActionButtons } from "./ActionButtons.svelte"
export { default as BackButton } from "./BackButton.svelte"
export { default as GitHubButton } from "./GitHubButton.svelte"
export { default as PatchNotesButton } from "./PatchNotesButton.svelte"
export { default as SettingsButton } from "./SettingsButton.svelte"
64 changes: 42 additions & 22 deletions src/components/extension/RecapContainer.svelte
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@
<script lang="ts">
import { untrack } from "svelte"
import { recapState } from "~/lib/state/recap-state.svelte"
import { getSettings, watchSettings } from "~/lib/utils/storage-utils"
import { ContentManager } from "~/lib/services/content-manager"
import { getDefaults } from "~/lib/config/defaults"
import { prefersReducedMotion } from "~/lib/utils/platform"
import type { ExtensionSettings } from "~/types/types"

let { id = "recapContainer" }: { id?: string } = $props()

// Track previous settings to detect changes
// Track previous word count to detect changes (derived from settings)
let previousWordCount = $state<number>(getDefaults().wordCount)
let currentSettings = $state<ExtensionSettings | null>(null)

// Load initial settings and watch for changes
// Load initial settings once on mount
const initSettings = async () => {
try {
const settings = await getSettings()
// Use untrack to prevent effect re-runs when updating state
untrack(() => {
currentSettings = settings
previousWordCount = settings.wordCount
})
} catch (error) {
console.error("Failed to load settings:", error)
}
}

initSettings()

// Effect: Watch for external settings changes (from other tabs/popups)
$effect(() => {
const loadSettings = async () => {
try {
currentSettings = await getSettings()
previousWordCount = currentSettings.wordCount
if (!currentSettings) return

// Watch for settings changes
return watchSettings((newSettings) => {
if (newSettings) {
currentSettings = newSettings
}
return watchSettings((newSettings) => {
if (newSettings) {
// Use untrack to prevent infinite loops
untrack(() => {
currentSettings = newSettings
})
} catch (error) {
console.error("Failed to load settings:", error)
}
}

loadSettings()
})
})

// Effect: scroll into view when content becomes visible
// Effect: Scroll into view when content becomes visible
$effect(() => {
if (recapState.isVisible && currentSettings?.enableJump) {
document
.getElementById(id)
?.scrollIntoView({ behavior: currentSettings.scrollBehavior })
// Use scroll behavior, but override to instant if OS prefers reduced motion
const behavior = prefersReducedMotion()
? "instant"
: currentSettings.scrollBehavior

document.getElementById(id)?.scrollIntoView({ behavior })
}
})

// Effect: refresh content when word count changes (uses cache to avoid re-fetching)
// Effect: Refresh content when word count changes (uses cache to avoid re-fetching)
$effect(() => {
if (
recapState.isVisible &&
Expand All @@ -50,6 +64,9 @@
currentSettings &&
currentSettings.wordCount !== previousWordCount
) {
// Store the word count before processing to maintain type safety
const newWordCount = currentSettings.wordCount

// Try to refresh from cache first
const result = ContentManager.refreshRecapFromCache(currentSettings)

Expand All @@ -60,7 +77,10 @@
recapState.setContent(result.content, result.type)
}

previousWordCount = currentSettings.wordCount
// Update tracked value using untrack to prevent effect re-run
untrack(() => {
previousWordCount = newWordCount
})
}
})
</script>
Expand Down
15 changes: 15 additions & 0 deletions src/components/icons/ArrowLeftIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import type { IconButtonProps } from "~/types/icon-button"
import Icon from "./Icon.svelte"

let { variant = "default" }: Omit<IconButtonProps, "onclick"> = $props()
</script>

<Icon {variant}>
{#snippet path()}
<!-- https://icons.getbootstrap.com/icons/arrow-left/ -->
<path
fill-rule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8" />
{/snippet}
</Icon>
18 changes: 18 additions & 0 deletions src/components/icons/ChevronDownIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script lang="ts">
import type { IconButtonProps } from "~/types/icon-button"
import Icon from "./Icon.svelte"

let { variant = "default" }: Omit<IconButtonProps, "onclick"> = $props()
</script>

<Icon {variant}>
{#snippet path()}
<path
d="M4 6L8 10L12 6"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
fill="none" />
{/snippet}
</Icon>
16 changes: 16 additions & 0 deletions src/components/icons/GearIcon.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import type { IconButtonProps } from "~/types/icon-button"
import Icon from "./Icon.svelte"

let { variant = "default" }: Omit<IconButtonProps, "onclick"> = $props()
</script>

<Icon {variant}>
{#snippet path()}
<!-- https://icons.getbootstrap.com/icons/gear/ -->
<path
d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0" />
<path
d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z" />
{/snippet}
</Icon>
Loading