JSDoc (#43)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'svelte-podcast': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
switch to jsdoc from ts
|
||||||
@@ -75,12 +75,14 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Package svelte src/lib
|
||||||
|
run: yarn run package
|
||||||
|
|
||||||
- name: Create Release Pull Request or Publish to npm
|
- name: Create Release Pull Request or Publish to npm
|
||||||
id: changesets
|
id: changesets
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
with:
|
with:
|
||||||
# This expects you to have a script called release which does a build for your packages and calls changeset publish
|
publish: yarn run release
|
||||||
publish: yarn release
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
Vendored
-1
@@ -25,7 +25,6 @@
|
|||||||
"**/*.map": true,
|
"**/*.map": true,
|
||||||
"**/.turbo": true,
|
"**/.turbo": true,
|
||||||
"**/.vercel": true,
|
"**/.vercel": true,
|
||||||
"**/dist": true,
|
|
||||||
"**/node_modules": true,
|
"**/node_modules": true,
|
||||||
"**/tsconfig.tsbuildinfo": true,
|
"**/tsconfig.tsbuildinfo": true,
|
||||||
"**/.npmrc": true
|
"**/.npmrc": true
|
||||||
|
|||||||
+5
-3
@@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
"build": "vite build && yarn package",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"package": "svelte-kit sync && svelte-package && publint",
|
"package": "svelte-kit sync && svelte-package && publint",
|
||||||
"sync": "svelte-kit sync",
|
"sync": "svelte-kit sync",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"lint:eslint": "TIMING=1 eslint . --ext .ts,.tsx,.svelte --cache",
|
"lint:eslint": "TIMING=1 eslint . --ext .ts,.tsx,.svelte --cache",
|
||||||
"lint": "yarn lint:prettier && yarn lint:eslint",
|
"lint": "yarn lint:prettier && yarn lint:eslint",
|
||||||
"format": "prettier --write --plugin-search-dir=. .",
|
"format": "prettier --write --plugin-search-dir=. .",
|
||||||
"release": "yarn package && changeset publish",
|
"release": "changeset publish",
|
||||||
"ts": "tsc --pretty --noImplicitAny --noEmit"
|
"ts": "tsc --pretty --noImplicitAny --noEmit"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -36,11 +36,13 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": ">=3.00.0"
|
"@sveltejs/kit": ">=1.0.0",
|
||||||
|
"svelte": ">=3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inqling/svelte-icons": "^3.3.2",
|
"@inqling/svelte-icons": "^3.3.2",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
"esm-env": "^1.0.0",
|
||||||
"just-clamp": "^4.2.0",
|
"just-clamp": "^4.2.0",
|
||||||
"svelte-local-storage-store": "^0.5.0"
|
"svelte-local-storage-store": "^0.5.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Audio } from 'svelte-podcast';
|
import { AudioContext } from 'svelte-podcast';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Audio />
|
<AudioContext />
|
||||||
|
|
||||||
<!-- your layout -->
|
<!-- your layout -->
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script lang="ts">
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { user_preferences } from '../user';
|
import { user_preferences } from '../user';
|
||||||
@@ -6,11 +6,6 @@
|
|||||||
import { audio_element } from './stores/audio-element';
|
import { audio_element } from './stores/audio-element';
|
||||||
|
|
||||||
$: $audio_element;
|
$: $audio_element;
|
||||||
/* onMount(() => {
|
|
||||||
return audio_element.subscribe((el) => {
|
|
||||||
if (el) info('mounted audio element :: ', el);
|
|
||||||
});
|
|
||||||
}); */
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
return user_preferences.subscribe((prefs) => {
|
return user_preferences.subscribe((prefs) => {
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Audio context module.
|
||||||
|
* @module audio
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default as AudioContext } from './context.svelte';
|
||||||
|
export { default as AudioPlayer } from './player.svelte';
|
||||||
|
export { default as AudioProgress } from './progress.svelte';
|
||||||
|
export { episode_audio, episode_details, episode_progress } from './stores';
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { default as Audio } from './component.svelte';
|
|
||||||
export { episode_audio, episode_details, episode_progress } from './stores';
|
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
<script lang="ts">
|
<script>
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { AudioProgress } from '.';
|
import { AudioProgress } from '.';
|
||||||
import { episode_audio, episode_progress } from '../audio';
|
import { episode_audio, episode_progress } from '../audio';
|
||||||
import type { EpisodeDetails } from '../audio/stores';
|
|
||||||
import { user_preferences } from '../user';
|
import { user_preferences } from '../user';
|
||||||
import { seconds_to_timestamp } from '../utility';
|
import { seconds_to_timestamp } from '../utility';
|
||||||
|
|
||||||
export let src: string | undefined;
|
/** @type {string|undefined} */
|
||||||
export let metadata: EpisodeDetails = {};
|
export let src;
|
||||||
|
|
||||||
|
/** @type {import('../audio/stores').EpisodeDetails} */
|
||||||
|
export let metadata = {};
|
||||||
|
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
|
|
||||||
$: is_loaded = Boolean($episode_audio?.src);
|
$: is_loaded = Boolean($episode_audio?.src);
|
||||||
@@ -33,19 +36,19 @@
|
|||||||
* Skips the audio forward by a specified number of seconds.
|
* Skips the audio forward by a specified number of seconds.
|
||||||
* @param {number} seconds - The number of seconds to skip forward.
|
* @param {number} seconds - The number of seconds to skip forward.
|
||||||
*/
|
*/
|
||||||
const skip_forward = (seconds: number) => episode_audio.skip(seconds, 'forward');
|
const skip_forward = (seconds) => episode_audio.skip(seconds, 'forward');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips the audio backward by a specified number of seconds.
|
* Skips the audio backward by a specified number of seconds.
|
||||||
* @param {number} seconds - The number of seconds to skip backward.
|
* @param {number} seconds - The number of seconds to skip backward.
|
||||||
*/
|
*/
|
||||||
const skip_back = (seconds: number) => episode_audio.skip(seconds, 'backward');
|
const skip_back = (seconds) => episode_audio.skip(seconds, 'backward');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeks to a specific time in the audio.
|
* Seeks to a specific time in the audio.
|
||||||
* @param {number} value - The time to seek to, in seconds.
|
* @param {number} value - The time to seek to, in seconds.
|
||||||
*/
|
*/
|
||||||
const seek_to = (value: number) => episode_audio.seek(value);
|
const seek_to = (value) => episode_audio.seek(value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles the audio playback between play and pause.
|
* Toggles the audio playback between play and pause.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<script lang="ts">
|
<script>
|
||||||
import { episode_audio, episode_progress } from '../audio';
|
import { episode_audio, episode_progress } from '../audio';
|
||||||
import { announce } from '../utility';
|
import { announce } from '../utility';
|
||||||
|
|
||||||
@@ -6,13 +6,19 @@
|
|||||||
|
|
||||||
let was_paused = true;
|
let was_paused = true;
|
||||||
|
|
||||||
function handle_drag_start(t: string) {
|
/**
|
||||||
|
* @param {string} t - The type of event that triggered the drag end.
|
||||||
|
*/
|
||||||
|
function handle_drag_start(t) {
|
||||||
announce.info('drag_start :: ', t);
|
announce.info('drag_start :: ', t);
|
||||||
was_paused = $episode_audio?.is_paused || true;
|
was_paused = $episode_audio?.is_paused || true;
|
||||||
episode_audio.pause();
|
episode_audio.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_drag_end(t: string) {
|
/**
|
||||||
|
* @param {string} t - The type of event that triggered the drag end.
|
||||||
|
*/
|
||||||
|
function handle_drag_end(t) {
|
||||||
announce.info('drag_end :: ', t);
|
announce.info('drag_end :: ', t);
|
||||||
if (was_paused) return;
|
if (was_paused) return;
|
||||||
else episode_audio.play();
|
else episode_audio.play();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} UserPreferences
|
||||||
|
* @property {number} playback_rate - The playback rate of the audio element.
|
||||||
|
* @property {number} volume - The volume of the audio element.
|
||||||
|
*/
|
||||||
|
import { BROWSER } from 'esm-env';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { get, readable } from 'svelte/store';
|
||||||
|
import { user_preferences } from '../../user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A readable store that provides an HTMLAudioElement instance.
|
||||||
|
* @type {import('svelte/store').Readable<HTMLAudioElement | undefined>}
|
||||||
|
* @exports audio_element
|
||||||
|
*/
|
||||||
|
export const audio_element = readable(undefined, (set) => {
|
||||||
|
if (!BROWSER) return;
|
||||||
|
const ID = 'svpod--generated-audio-element';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
const existing_element = document.getElementById(ID);
|
||||||
|
|
||||||
|
/** @type {HTMLAudioElement} */
|
||||||
|
const el =
|
||||||
|
existing_element instanceof HTMLAudioElement
|
||||||
|
? existing_element
|
||||||
|
: document.createElement('audio');
|
||||||
|
|
||||||
|
el.id = ID;
|
||||||
|
el.setAttribute('preload', 'metadata');
|
||||||
|
el.muted = false;
|
||||||
|
el.autoplay = false;
|
||||||
|
el.controls = false;
|
||||||
|
|
||||||
|
/** @type {UserPreferences} */
|
||||||
|
const preferences = get(user_preferences);
|
||||||
|
el.playbackRate = preferences.playback_rate;
|
||||||
|
el.volume = preferences.volume;
|
||||||
|
|
||||||
|
if (!existing_element) document.body.appendChild(el);
|
||||||
|
|
||||||
|
// @ts-expect-error - I'm not sure how to fix this using JSDOC
|
||||||
|
set(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const element = document.getElementById(ID);
|
||||||
|
element ? element.remove() : null;
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { browser } from '$app/environment';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { get, readable } from 'svelte/store';
|
|
||||||
import { user_preferences } from '../../user';
|
|
||||||
|
|
||||||
export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
|
||||||
if (!browser) return;
|
|
||||||
const ID = 'svpod--generated-audio-element';
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const existing_element = document.getElementById(ID) as HTMLAudioElement | null;
|
|
||||||
const el = existing_element || document.createElement('audio');
|
|
||||||
|
|
||||||
el.id = ID;
|
|
||||||
el.setAttribute('preload', 'metadata');
|
|
||||||
el.muted = false;
|
|
||||||
el.autoplay = false;
|
|
||||||
el.controls = false;
|
|
||||||
|
|
||||||
const preferences = get(user_preferences);
|
|
||||||
el.playbackRate = preferences.playback_rate;
|
|
||||||
el.volume = preferences.volume;
|
|
||||||
|
|
||||||
if (!existing_element) document.body.appendChild(el);
|
|
||||||
set(el);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
const element = document.getElementById(ID);
|
|
||||||
element ? element.remove() : null;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,29 +1,33 @@
|
|||||||
import clamp from 'just-clamp';
|
import clamp from 'just-clamp';
|
||||||
import { derived, get, type Readable } from 'svelte/store';
|
import { derived, get } from 'svelte/store';
|
||||||
import { user_preferences, user_progress } from '../../user';
|
import { user_preferences, user_progress } from '../../user';
|
||||||
import { announce } from '../../utility';
|
import { announce } from '../../utility';
|
||||||
import { audio_element } from './audio-element';
|
import { audio_element } from './audio-element';
|
||||||
import { episode_details, type EpisodeDetails } from './episode-details';
|
import { episode_details } from './episode-details';
|
||||||
|
|
||||||
export type EpisodeState = {
|
/**
|
||||||
will_autoplay: boolean;
|
* Episode state object
|
||||||
is_paused: boolean;
|
* @typedef {Object} EpisodeState
|
||||||
duration: number;
|
* @property {boolean} will_autoplay - Whether the episode will autoplay
|
||||||
src: string;
|
* @property {boolean} is_paused - Whether the episode is paused
|
||||||
start_at: number;
|
* @property {number} duration - The duration of the episode
|
||||||
details: EpisodeDetails | null;
|
* @property {string} src - The source of the episode
|
||||||
};
|
* @property {number} start_at - The starting point of the episode
|
||||||
|
* @property {import('./episode-details').EpisodeDetails | null} details - The details of the episode or null if there are none
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Omit<EpisodeState, 'src'>} */
|
||||||
const default_episode_state = {
|
const default_episode_state = {
|
||||||
will_autoplay: false,
|
will_autoplay: false,
|
||||||
is_paused: true,
|
is_paused: true,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
start_at: 0,
|
start_at: 0,
|
||||||
details: null,
|
details: null,
|
||||||
} satisfies Omit<EpisodeState, 'src'>;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Episode state store
|
* Episode state store
|
||||||
|
* @type {import('svelte/store').Readable<EpisodeState | null>}
|
||||||
*/
|
*/
|
||||||
const episode_state = derived([audio_element, episode_details], ([$audio, $details], set) => {
|
const episode_state = derived([audio_element, episode_details], ([$audio, $details], set) => {
|
||||||
if (!$audio) return set(null);
|
if (!$audio) return set(null);
|
||||||
@@ -50,9 +54,11 @@ const episode_state = derived([audio_element, episode_details], ([$audio, $detai
|
|||||||
$audio.removeEventListener('pause', set_value);
|
$audio.removeEventListener('pause', set_value);
|
||||||
$audio.removeEventListener('playing', set_value);
|
$audio.removeEventListener('playing', set_value);
|
||||||
};
|
};
|
||||||
}) satisfies Readable<EpisodeState | null>;
|
});
|
||||||
|
|
||||||
type HANDLE_TYPE = 'toggle' | 'set';
|
/**
|
||||||
|
* @typedef {'toggle' | 'set'} HANDLE_TYPE
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use audio element
|
* Use audio element
|
||||||
@@ -60,7 +66,7 @@ type HANDLE_TYPE = 'toggle' | 'set';
|
|||||||
* @returns {HTMLAudioElement} - Audio element
|
* @returns {HTMLAudioElement} - Audio element
|
||||||
* @throws {string} - Error message if no audio element exists
|
* @throws {string} - Error message if no audio element exists
|
||||||
*/
|
*/
|
||||||
const use_element = (action: string): HTMLAudioElement => {
|
const use_element = (action) => {
|
||||||
const el = get(audio_element);
|
const el = get(audio_element);
|
||||||
if (!el) throw announce.warn(`could not ${action} :: no audio element exists yet`);
|
if (!el) throw announce.warn(`could not ${action} :: no audio element exists yet`);
|
||||||
return el;
|
return el;
|
||||||
@@ -69,9 +75,10 @@ const use_element = (action: string): HTMLAudioElement => {
|
|||||||
/**
|
/**
|
||||||
* Load audio
|
* Load audio
|
||||||
* @param {string} src - Audio source
|
* @param {string} src - Audio source
|
||||||
* @param {EpisodeDetails} details - Episode details
|
* @param {import('./episode-details').EpisodeDetails } details - Episode details
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const load_audio = (src: string, details: EpisodeDetails): void => {
|
const load_audio = (src, details) => {
|
||||||
user_progress.save();
|
user_progress.save();
|
||||||
const el = use_element('load');
|
const el = use_element('load');
|
||||||
el.src = src;
|
el.src = src;
|
||||||
@@ -91,8 +98,9 @@ const load_audio = (src: string, details: EpisodeDetails): void => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Unload audio
|
* Unload audio
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const unload_audio = (): void => {
|
const unload_audio = () => {
|
||||||
user_progress.save();
|
user_progress.save();
|
||||||
const el = use_element('unload');
|
const el = use_element('unload');
|
||||||
el.src = '';
|
el.src = '';
|
||||||
@@ -102,8 +110,9 @@ const unload_audio = (): void => {
|
|||||||
/**
|
/**
|
||||||
* Play audio
|
* Play audio
|
||||||
* @param {HANDLE_TYPE} t - Handle type
|
* @param {HANDLE_TYPE} t - Handle type
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const play_audio = (t: HANDLE_TYPE = 'set'): void => {
|
const play_audio = (t = 'set') => {
|
||||||
const el = use_element('play');
|
const el = use_element('play');
|
||||||
|
|
||||||
if (t === 'toggle') {
|
if (t === 'toggle') {
|
||||||
@@ -116,8 +125,9 @@ const play_audio = (t: HANDLE_TYPE = 'set'): void => {
|
|||||||
/**
|
/**
|
||||||
* Pause audio
|
* Pause audio
|
||||||
* @param {HANDLE_TYPE} t - Handle type
|
* @param {HANDLE_TYPE} t - Handle type
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const pause_audio = (t: HANDLE_TYPE = 'set'): void => {
|
const pause_audio = (t = 'set') => {
|
||||||
user_progress.save();
|
user_progress.save();
|
||||||
const el = use_element('pause');
|
const el = use_element('pause');
|
||||||
|
|
||||||
@@ -131,8 +141,9 @@ const pause_audio = (t: HANDLE_TYPE = 'set'): void => {
|
|||||||
/**
|
/**
|
||||||
* Mute audio
|
* Mute audio
|
||||||
* @param {HANDLE_TYPE} t - Handle type
|
* @param {HANDLE_TYPE} t - Handle type
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const mute_audio = (t: HANDLE_TYPE = 'set'): void => {
|
const mute_audio = (t = 'set') => {
|
||||||
const el = use_element('mute');
|
const el = use_element('mute');
|
||||||
|
|
||||||
if (t === 'toggle') {
|
if (t === 'toggle') {
|
||||||
@@ -145,8 +156,9 @@ const mute_audio = (t: HANDLE_TYPE = 'set'): void => {
|
|||||||
/**
|
/**
|
||||||
* Unmute audio
|
* Unmute audio
|
||||||
* @param {HANDLE_TYPE} t - Handle type
|
* @param {HANDLE_TYPE} t - Handle type
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const unmute_audio = (t: HANDLE_TYPE = 'set'): void => {
|
const unmute_audio = (t = 'set') => {
|
||||||
const el = use_element('unmute');
|
const el = use_element('unmute');
|
||||||
|
|
||||||
if (t === 'toggle') {
|
if (t === 'toggle') {
|
||||||
@@ -159,9 +171,10 @@ const unmute_audio = (t: HANDLE_TYPE = 'set'): void => {
|
|||||||
/**
|
/**
|
||||||
* Seek to time audio
|
* Seek to time audio
|
||||||
* @param {number} seconds - Seconds to seek
|
* @param {number} seconds - Seconds to seek
|
||||||
* @param {'from-start' | 'from-end'} from - Seek from start or end
|
* @param {'from-start' | 'from-end'} [from="from-start"] - Seek from start or end
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const seek_audio = (seconds: number, from: 'from-start' | 'from-end' = 'from-start'): void => {
|
const seek_audio = (seconds, from = 'from-start') => {
|
||||||
const el = use_element('seek');
|
const el = use_element('seek');
|
||||||
|
|
||||||
if (from === 'from-end') {
|
if (from === 'from-end') {
|
||||||
@@ -174,9 +187,10 @@ const seek_audio = (seconds: number, from: 'from-start' | 'from-end' = 'from-sta
|
|||||||
/**
|
/**
|
||||||
* Skip by about audio
|
* Skip by about audio
|
||||||
* @param {number} seconds - Seconds to skip
|
* @param {number} seconds - Seconds to skip
|
||||||
* @param {'forward' | 'backward'} type - Skip forward or backward
|
* @param {'forward' | 'backward'} [type='forward'] - Skip forward or backward
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const skip_audio = (seconds: number, type: 'forward' | 'backward' = 'forward'): void => {
|
const skip_audio = (seconds, type = 'forward') => {
|
||||||
const el = use_element('skip');
|
const el = use_element('skip');
|
||||||
|
|
||||||
if (type === 'backward') {
|
if (type === 'backward') {
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object.<string, unknown>} EpisodeDetails
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Episode details store.
|
||||||
|
* @type {import('svelte/store').Writable<EpisodeDetails | null>}
|
||||||
|
* @description This store holds the metadata for a single episode.
|
||||||
|
*/
|
||||||
|
export const episode_details = writable(null);
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
export interface EpisodeDetails {
|
|
||||||
[key: string]: string | number | boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Episode details store.
|
|
||||||
*
|
|
||||||
* @returns {Writable<EpisodeDetails>} A Svelte writable store that contains metadata for the current episode.
|
|
||||||
*/
|
|
||||||
export const episode_details = writable<EpisodeDetails | null>(null);
|
|
||||||
+17
-12
@@ -1,23 +1,28 @@
|
|||||||
import { derived, type Readable } from 'svelte/store';
|
import { seconds_to_timestamp } from 'svelte-podcast/utility';
|
||||||
import { seconds_to_timestamp } from '../../utility';
|
|
||||||
import { audio_element } from './audio-element';
|
import { audio_element } from './audio-element';
|
||||||
|
import { derived } from 'svelte/store';
|
||||||
|
|
||||||
export type EpisodeProgress = {
|
/**
|
||||||
current_time: number;
|
* Episode progress object.
|
||||||
timestamp: string;
|
* @typedef {Object} EpisodeProgress
|
||||||
has_ended: boolean;
|
* @property {number} current_time - The current time of the episode in seconds.
|
||||||
};
|
* @property {string} timestamp - The current time of the episode in the format 'mm:ss'.
|
||||||
|
* @property {boolean} has_ended - Whether the episode has ended or not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default episode progress object.
|
||||||
|
* @type {EpisodeProgress}
|
||||||
|
*/
|
||||||
const default_episode_progress = {
|
const default_episode_progress = {
|
||||||
current_time: 0,
|
current_time: 0,
|
||||||
timestamp: '00:00',
|
timestamp: '00:00',
|
||||||
has_ended: false,
|
has_ended: false,
|
||||||
} satisfies EpisodeProgress;
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Episode progress store.
|
* The default episode progress object.
|
||||||
*
|
* @type {import('svelte/store').Readable<EpisodeProgress>}
|
||||||
* @returns {Readable<EpisodeProgress>} A Svelte readable store that contains the current progress of the audio episode.
|
|
||||||
*/
|
*/
|
||||||
export const episode_progress = derived(audio_element, ($audio, set) => {
|
export const episode_progress = derived(audio_element, ($audio, set) => {
|
||||||
if (!$audio) return set(default_episode_progress);
|
if (!$audio) return set(default_episode_progress);
|
||||||
@@ -40,4 +45,4 @@ export const episode_progress = derived(audio_element, ($audio, set) => {
|
|||||||
* Remove event listener.
|
* Remove event listener.
|
||||||
*/
|
*/
|
||||||
return () => $audio.removeEventListener('timeupdate', () => set_value());
|
return () => $audio.removeEventListener('timeupdate', () => set_value());
|
||||||
}) satisfies Readable<EpisodeProgress>;
|
});
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export { default as AudioPlayer } from './player.svelte';
|
|
||||||
export { default as AudioProgress } from './progress.svelte';
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './audio';
|
||||||
|
export { AudioPlayer as default } from './audio';
|
||||||
|
export * from './user';
|
||||||
|
export * from './utility';
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
export * from './audio';
|
|
||||||
export * from './components';
|
|
||||||
export { AudioPlayer as default } from './components';
|
|
||||||
export * from './user';
|
|
||||||
export * from './utility';
|
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* User storage module.
|
||||||
|
* @module user
|
||||||
|
*/
|
||||||
|
|
||||||
export * from './user-preferences';
|
export * from './user-preferences';
|
||||||
export * from './user-progress';
|
export * from './user-progress';
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import clamp from 'just-clamp';
|
||||||
|
import { persisted } from 'svelte-local-storage-store';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} UserPreferences
|
||||||
|
* @property {number} playback_rate - The audio player playback rate (speed)
|
||||||
|
* @property {number} volume - The audio player volume
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default user preferences
|
||||||
|
* @type {UserPreferences}
|
||||||
|
*/
|
||||||
|
const DEFAULT_USER_PREFERENCES = { playback_rate: 1, volume: 1 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user preferences store
|
||||||
|
* @type {import('svelte/store').Writable<UserPreferences>}
|
||||||
|
*/
|
||||||
|
const USER_PREFERENCES_STORE = persisted('USER_PREFERENCE', DEFAULT_USER_PREFERENCES);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the playback rate for the user
|
||||||
|
* @param {number} value - The playback rate value
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const set_playback_rate = (value) => {
|
||||||
|
const playback_rate = clamp(value, 0.5, 5);
|
||||||
|
return USER_PREFERENCES_STORE.update((prefs) => ({ ...prefs, playback_rate }));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume for the user
|
||||||
|
* @param {number} value - The volume value
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const set_volume = (value) => {
|
||||||
|
const volume = clamp(value, 0, 1);
|
||||||
|
return USER_PREFERENCES_STORE.update((prefs) => ({ ...prefs, volume }));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears user preferences store
|
||||||
|
*/
|
||||||
|
const clear_store = () => USER_PREFERENCES_STORE.set(DEFAULT_USER_PREFERENCES);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} UserPreferencesObject
|
||||||
|
* @property {typeof USER_PREFERENCES_STORE.subscribe} subscribe - Subscribes to the user preferences store
|
||||||
|
* @property {typeof set_playback_rate} set_playback_rate - Sets the playback rate for the user
|
||||||
|
* @property {typeof set_volume} set_volume - Sets the volume for the user
|
||||||
|
* @property {typeof clear_store} clear - Clears saved user preferences
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user preferences object
|
||||||
|
* @type {UserPreferencesObject}
|
||||||
|
*/
|
||||||
|
export const user_preferences = {
|
||||||
|
/**
|
||||||
|
* Subscribes to user preferences store
|
||||||
|
*/
|
||||||
|
subscribe: USER_PREFERENCES_STORE.subscribe,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the playback rate for the user
|
||||||
|
* @type {typeof set_playback_rate}
|
||||||
|
*/
|
||||||
|
set_playback_rate,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume for the user
|
||||||
|
* @type {typeof set_volume}
|
||||||
|
*/
|
||||||
|
set_volume,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears user preferences store
|
||||||
|
* @type {typeof clear_store}
|
||||||
|
*/
|
||||||
|
clear: clear_store,
|
||||||
|
};
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import clamp from 'just-clamp';
|
|
||||||
import { persisted } from 'svelte-local-storage-store';
|
|
||||||
|
|
||||||
export interface UserPreferences {
|
|
||||||
playback_rate: number;
|
|
||||||
volume: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const default_user_preferences = { playback_rate: 1, volume: 1 };
|
|
||||||
const user_preferences_store = persisted<UserPreferences>(
|
|
||||||
'USER_PREFERENCE',
|
|
||||||
default_user_preferences,
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the playback rate for the user
|
|
||||||
* @param value - The playback rate value
|
|
||||||
* @returns A Promise that resolves with the updated user preferences
|
|
||||||
*/
|
|
||||||
const set_playback_rate = (value: number) => {
|
|
||||||
const playback_rate = clamp(value, 0.5, 5);
|
|
||||||
return user_preferences_store.update((prefs) => ({ ...prefs, playback_rate }));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the volume for the user
|
|
||||||
* @param value - The volume value
|
|
||||||
* @returns A Promise that resolves with the updated user preferences
|
|
||||||
*/
|
|
||||||
const set_volume = (value: number) => {
|
|
||||||
const volume = clamp(value, 0, 1);
|
|
||||||
return user_preferences_store.update((prefs) => ({ ...prefs, volume }));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears user preferences store
|
|
||||||
*/
|
|
||||||
const clear_store = () => user_preferences_store.set(default_user_preferences);
|
|
||||||
|
|
||||||
export const user_preferences = {
|
|
||||||
/**
|
|
||||||
* Subscribes to user preferences store
|
|
||||||
*/
|
|
||||||
subscribe: user_preferences_store.subscribe,
|
|
||||||
|
|
||||||
set_playback_rate,
|
|
||||||
set_volume,
|
|
||||||
clear: clear_store,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { persisted } from 'svelte-local-storage-store';
|
||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { episode_audio, episode_progress } from '../audio';
|
||||||
|
import { announce, use_url } from '../utility';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User progress object type.
|
||||||
|
* This object acts as a dictionary with string keys and number values.
|
||||||
|
* @typedef {Object.<string, number>} UserProgress
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default user progress object
|
||||||
|
* @type {UserProgress}
|
||||||
|
*/
|
||||||
|
const DEFAULT_USER_PROGRESS = {};
|
||||||
|
import {} from 'svelte-local-storage-store';
|
||||||
|
/**
|
||||||
|
* User progress store
|
||||||
|
* @type {import('svelte/store').Writable<UserProgress>}
|
||||||
|
*/
|
||||||
|
const USER_PROGRESS_STORE = persisted('USER_PROGRESS', DEFAULT_USER_PROGRESS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves user progress
|
||||||
|
* @function
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const save_user_progress = () => {
|
||||||
|
const audio = get(episode_audio);
|
||||||
|
if (!audio?.src) return;
|
||||||
|
|
||||||
|
announce.info('saving progress: ', audio.src);
|
||||||
|
|
||||||
|
const pathname = use_url(audio.src).pathname;
|
||||||
|
const current_time = get(episode_progress).current_time;
|
||||||
|
USER_PROGRESS_STORE.update((prev) => ({ ...prev, [pathname]: current_time }));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets user progress for a given audio source
|
||||||
|
* @function
|
||||||
|
* @param {string} src - Audio source
|
||||||
|
* @returns {number|undefined} - User progress for the given audio source, or undefined if not found
|
||||||
|
*/
|
||||||
|
const get_user_progress = (src) => {
|
||||||
|
const pathname = use_url(src).pathname;
|
||||||
|
|
||||||
|
const store = get(USER_PROGRESS_STORE);
|
||||||
|
|
||||||
|
/**@type {number|undefined} */
|
||||||
|
const value = store[pathname];
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears user progress store
|
||||||
|
* @function
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const clear_user_progress = () => USER_PROGRESS_STORE.set(DEFAULT_USER_PROGRESS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User progress object
|
||||||
|
* @namespace
|
||||||
|
* @property {function} subscribe - Subscribes to user progress store
|
||||||
|
* @property {function} get - Gets user progress for a given audio source
|
||||||
|
* @property {function} clear - Clears user progress store
|
||||||
|
* @property {function} save - Saves user progress
|
||||||
|
*/
|
||||||
|
export const user_progress = {
|
||||||
|
subscribe: USER_PROGRESS_STORE.subscribe,
|
||||||
|
get: get_user_progress,
|
||||||
|
clear: clear_user_progress,
|
||||||
|
save: save_user_progress,
|
||||||
|
};
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { persisted } from 'svelte-local-storage-store';
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
import { episode_audio, episode_progress } from '../audio';
|
|
||||||
import { announce, use_url } from '../utility';
|
|
||||||
|
|
||||||
export type UserProgress = {
|
|
||||||
[key: string]: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const default_user_progress = {};
|
|
||||||
const user_progress_store = persisted<UserProgress>('USER_PROGRESS', default_user_progress);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves user progress
|
|
||||||
*/
|
|
||||||
const save = () => {
|
|
||||||
const audio = get(episode_audio);
|
|
||||||
if (!audio?.src) return;
|
|
||||||
|
|
||||||
announce.info('saving progress: ', audio.src);
|
|
||||||
|
|
||||||
const pathname = use_url(audio.src).pathname;
|
|
||||||
const current_time = get(episode_progress).current_time;
|
|
||||||
user_progress_store.update((prev) => ({ ...prev, [pathname]: current_time }));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets user progress for a given audio source
|
|
||||||
* @param {string} src - Audio source
|
|
||||||
* @returns {number} - User progress for the given audio source
|
|
||||||
*/
|
|
||||||
const get_user_progress = (src: string) => {
|
|
||||||
const pathname = use_url(src).pathname;
|
|
||||||
const store = get(user_progress_store) as UserProgress;
|
|
||||||
|
|
||||||
return store[pathname];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears user progress store
|
|
||||||
*/
|
|
||||||
const clear_user_progress = () => user_progress_store.set(default_user_progress);
|
|
||||||
|
|
||||||
export const user_progress = {
|
|
||||||
/**
|
|
||||||
* Subscribes to user preferences store
|
|
||||||
*/
|
|
||||||
subscribe: user_progress_store.subscribe,
|
|
||||||
|
|
||||||
get: get_user_progress,
|
|
||||||
clear: clear_user_progress,
|
|
||||||
save,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { DEV } from 'esm-env';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing logger functions.
|
||||||
|
* @typedef {Object} Announce
|
||||||
|
* @property {function} info - Log info like events and data.
|
||||||
|
* @property {function} warn - Log warnings for developers.
|
||||||
|
* @property {function} error - Log errors for critical failures.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of logger.
|
||||||
|
* @typedef {('info' | 'error' | 'warn')} Logger
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing Logger functions.
|
||||||
|
*
|
||||||
|
* @type {Readonly<{[K in Logger]: Function}>}
|
||||||
|
*/
|
||||||
|
const use_logger = Object.freeze({
|
||||||
|
info: console.info,
|
||||||
|
error: console.error,
|
||||||
|
warn: console.warn,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log function.
|
||||||
|
* @param {Logger} type - Type of logger.
|
||||||
|
* @param {...unknown} content - Content to log.
|
||||||
|
*/
|
||||||
|
function log(type, ...content) {
|
||||||
|
const logger = use_logger[type];
|
||||||
|
|
||||||
|
// If type is info and not in dev mode, return early
|
||||||
|
if (type === 'info' && !DEV) return;
|
||||||
|
|
||||||
|
// Log message
|
||||||
|
logger(
|
||||||
|
'%c🔊 svelte-podcast:',
|
||||||
|
'color: #FF3E00; background-color: rgba(255, 62, 0, 0.15); padding: 4px 8px; border-radius:4px;',
|
||||||
|
...content,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing logger functions.
|
||||||
|
* @type {Announce}
|
||||||
|
* @exports announce
|
||||||
|
*/
|
||||||
|
export const announce = {
|
||||||
|
/**
|
||||||
|
* Log an info message.
|
||||||
|
* @param {...unknown} content - Content to log.
|
||||||
|
*/
|
||||||
|
info: (...content) => log('info', ...content),
|
||||||
|
/**
|
||||||
|
* Log a warning message.
|
||||||
|
* @param {...unknown} content - Content to log.
|
||||||
|
*/
|
||||||
|
warn: (...content) => log('warn', ...content),
|
||||||
|
/**
|
||||||
|
* Log an error message.
|
||||||
|
* @param {...unknown} content - Content to log.
|
||||||
|
*/
|
||||||
|
error: (...content) => log('error', ...content),
|
||||||
|
};
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { dev } from '$app/environment';
|
|
||||||
|
|
||||||
const use_logger = {
|
|
||||||
info: console.info,
|
|
||||||
error: console.error,
|
|
||||||
warn: console.warn,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Logger = keyof typeof use_logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log function.
|
|
||||||
* @param type - Type of logger.
|
|
||||||
* @param content - Content to log.
|
|
||||||
*/
|
|
||||||
function log(type: Logger, ...content: unknown[]) {
|
|
||||||
const logger = use_logger[type];
|
|
||||||
|
|
||||||
// If type is info and not in dev mode, return
|
|
||||||
if (type === 'info' && !dev) return;
|
|
||||||
|
|
||||||
// Log message
|
|
||||||
logger(
|
|
||||||
'%c🔊 svelte-podcast:',
|
|
||||||
'color: #FF3E00; background-color: rgba(255, 62, 0, 0.15); padding: 4px 8px; border-radius:4px;',
|
|
||||||
...content,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object containing logger functions.
|
|
||||||
*/
|
|
||||||
export const announce = {
|
|
||||||
/**
|
|
||||||
* Log info message.
|
|
||||||
* @param content - Content to log.
|
|
||||||
*/
|
|
||||||
info: (...content: unknown[]) => log('info', ...content),
|
|
||||||
/**
|
|
||||||
* Log warning message.
|
|
||||||
* @param content - Content to log.
|
|
||||||
*/
|
|
||||||
warn: (...content: unknown[]) => log('warn', ...content),
|
|
||||||
/**
|
|
||||||
* Log error message.
|
|
||||||
* @param content - Content to log.
|
|
||||||
*/
|
|
||||||
error: (...content: unknown[]) => log('error', ...content),
|
|
||||||
};
|
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* Utility functions module.
|
||||||
|
* @module utility
|
||||||
|
*/
|
||||||
|
|
||||||
export * from './announce';
|
export * from './announce';
|
||||||
export * from './seconds-to-timestamp';
|
export * from './seconds-to-timestamp';
|
||||||
export * from './use-url';
|
export * from './use-url';
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Returns a string timestamp in the format of "hh:mm:ss" from a given number of seconds.
|
* Returns a string timestamp in the format of "hh:mm:ss" from a given number of seconds.
|
||||||
* @param seconds - The number of seconds to convert to a timestamp.
|
* @param {number} seconds - The number of seconds to convert to a timestamp.
|
||||||
* @param force_hours - Whether to always include hours in the timestamp, even if it's 0.
|
* @param {boolean} [force_hours=false] - Whether to always include hours in the timestamp, even if it's 0.
|
||||||
* @returns A string timestamp in the format of "hh:mm:ss".
|
* @returns {string} A string timestamp in the format of "hh:mm:ss".
|
||||||
*/
|
*/
|
||||||
export function seconds_to_timestamp(seconds: number, force_hours = false) {
|
export function seconds_to_timestamp(seconds, force_hours = false) {
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const hours_remainder = seconds % 3600;
|
const hours_remainder = seconds % 3600;
|
||||||
|
|
||||||
@@ -22,10 +22,10 @@ export function seconds_to_timestamp(seconds: number, force_hours = false) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string representation of a number with a leading 0 if it's less than 10.
|
* Returns a string representation of a number with a leading 0 if it's less than 10.
|
||||||
* @param num - The number to format.
|
* @param {number} num - The number to format.
|
||||||
* @returns A string representation of the number with a leading 0 if it's less than 10.
|
* @returns {string} A string representation of the number with a leading 0 if it's less than 10.
|
||||||
*/
|
*/
|
||||||
function section(num: number) {
|
function section(num) {
|
||||||
if (num < 10) return `0${num}`;
|
if (num < 10) return `0${num}`;
|
||||||
return num.toString();
|
return num.toString();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* @typedef {Object} RelativeURL
|
||||||
|
* @property {string} pathname - The pathname of the URL.
|
||||||
|
* @property {string} search - The search string of the URL.
|
||||||
|
* @property {string} hash - The hash string of the URL.
|
||||||
|
* @property {URLSearchParams} searchParams - The search parameters of the URL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string to a URL object. If the string is not a valid URL, it will be appended to the Svelte website URL.
|
||||||
|
* @param {string} href - The string to convert to a URL object.
|
||||||
|
* @returns {URL} A URL object.
|
||||||
|
*/
|
||||||
|
const string_to_url = (href) => {
|
||||||
|
try {
|
||||||
|
return new URL(href);
|
||||||
|
} catch (e) {
|
||||||
|
return new URL(href, 'https://svelte.dev');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a URL string and returns an object containing its pathname, search, hash, and searchParams.
|
||||||
|
* @param {string} href - The URL string to parse.
|
||||||
|
* @returns {RelativeURL} An object containing the pathname, search, hash, and searchParams of the URL.
|
||||||
|
*/
|
||||||
|
export const use_url = (href) => {
|
||||||
|
const { pathname, search, hash, searchParams } = string_to_url(href);
|
||||||
|
|
||||||
|
return { pathname, search, hash, searchParams };
|
||||||
|
};
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Converts a string to a URL object. If the string is not a valid URL, it will be appended to the Svelte website URL.
|
|
||||||
* @param href - The string to convert to a URL object.
|
|
||||||
* @returns A URL object.
|
|
||||||
*/
|
|
||||||
const string_to_url = (href: string) => {
|
|
||||||
try {
|
|
||||||
return new URL(href);
|
|
||||||
} catch (e) {
|
|
||||||
return new URL(href, 'https://svelte.dev');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a URL string and returns an object containing its pathname, search, hash, and searchParams.
|
|
||||||
* @param href - The URL string to parse.
|
|
||||||
* @returns An object containing the pathname, search, hash, and searchParams of the URL.
|
|
||||||
*/
|
|
||||||
export const use_url = (href: string) => {
|
|
||||||
const { pathname, search, hash, searchParams } = string_to_url(href);
|
|
||||||
|
|
||||||
return { pathname, search, hash, searchParams } as const;
|
|
||||||
};
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
import { Github } from '@inqling/svelte-icons/simple-icons';
|
import { Github } from '@inqling/svelte-icons/simple-icons';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import 'highlight.js/styles/github-dark.css';
|
import 'highlight.js/styles/github-dark.css';
|
||||||
import { Audio } from 'svelte-podcast';
|
import { AudioContext } from 'svelte-podcast';
|
||||||
import '../app.postcss';
|
import '../app.postcss';
|
||||||
|
|
||||||
const page_links = [
|
const page_links = [
|
||||||
@@ -62,6 +62,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<Audio />
|
<AudioContext />
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
Reference in New Issue
Block a user