This commit is contained in:
Ollie Taylor
2023-07-10 21:44:55 +01:00
committed by GitHub
parent 16366ec7f3
commit 415d083f24
32 changed files with 443 additions and 297 deletions
@@ -1,4 +1,4 @@
<script lang="ts">
<script>
import { onMount } from 'svelte';
import { get } from 'svelte/store';
import { user_preferences } from '../user';
@@ -6,11 +6,6 @@
import { audio_element } from './stores/audio-element';
$: $audio_element;
/* onMount(() => {
return audio_element.subscribe((el) => {
if (el) info('mounted audio element :: ', el);
});
}); */
onMount(() => {
return user_preferences.subscribe((prefs) => {
+9
View File
@@ -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';
-2
View File
@@ -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 { AudioProgress } from '.';
import { episode_audio, episode_progress } from '../audio';
import type { EpisodeDetails } from '../audio/stores';
import { user_preferences } from '../user';
import { seconds_to_timestamp } from '../utility';
export let src: string | undefined;
export let metadata: EpisodeDetails = {};
/** @type {string|undefined} */
export let src;
/** @type {import('../audio/stores').EpisodeDetails} */
export let metadata = {};
let mounted = false;
$: is_loaded = Boolean($episode_audio?.src);
@@ -33,19 +36,19 @@
* Skips the audio forward by a specified number of seconds.
* @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.
* @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.
* @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.
@@ -1,4 +1,4 @@
<script lang="ts">
<script>
import { episode_audio, episode_progress } from '../audio';
import { announce } from '../utility';
@@ -6,13 +6,19 @@
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);
was_paused = $episode_audio?.is_paused || true;
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);
if (was_paused) return;
else episode_audio.play();
+50
View File
@@ -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;
};
});
-32
View File
@@ -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 { derived, get, type Readable } from 'svelte/store';
import { derived, get } from 'svelte/store';
import { user_preferences, user_progress } from '../../user';
import { announce } from '../../utility';
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;
is_paused: boolean;
duration: number;
src: string;
start_at: number;
details: EpisodeDetails | null;
};
/**
* Episode state object
* @typedef {Object} EpisodeState
* @property {boolean} will_autoplay - Whether the episode will autoplay
* @property {boolean} is_paused - Whether the episode is paused
* @property {number} duration - The duration of the episode
* @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 = {
will_autoplay: false,
is_paused: true,
duration: 0,
start_at: 0,
details: null,
} satisfies Omit<EpisodeState, 'src'>;
};
/**
* Episode state store
* @type {import('svelte/store').Readable<EpisodeState | null>}
*/
const episode_state = derived([audio_element, episode_details], ([$audio, $details], set) => {
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('playing', set_value);
};
}) satisfies Readable<EpisodeState | null>;
});
type HANDLE_TYPE = 'toggle' | 'set';
/**
* @typedef {'toggle' | 'set'} HANDLE_TYPE
*/
/**
* Use audio element
@@ -60,7 +66,7 @@ type HANDLE_TYPE = 'toggle' | 'set';
* @returns {HTMLAudioElement} - Audio element
* @throws {string} - Error message if no audio element exists
*/
const use_element = (action: string): HTMLAudioElement => {
const use_element = (action) => {
const el = get(audio_element);
if (!el) throw announce.warn(`could not ${action} :: no audio element exists yet`);
return el;
@@ -69,9 +75,10 @@ const use_element = (action: string): HTMLAudioElement => {
/**
* Load audio
* @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();
const el = use_element('load');
el.src = src;
@@ -91,8 +98,9 @@ const load_audio = (src: string, details: EpisodeDetails): void => {
/**
* Unload audio
* @returns {void}
*/
const unload_audio = (): void => {
const unload_audio = () => {
user_progress.save();
const el = use_element('unload');
el.src = '';
@@ -102,8 +110,9 @@ const unload_audio = (): void => {
/**
* Play audio
* @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');
if (t === 'toggle') {
@@ -116,8 +125,9 @@ const play_audio = (t: HANDLE_TYPE = 'set'): void => {
/**
* Pause audio
* @param {HANDLE_TYPE} t - Handle type
* @returns {void}
*/
const pause_audio = (t: HANDLE_TYPE = 'set'): void => {
const pause_audio = (t = 'set') => {
user_progress.save();
const el = use_element('pause');
@@ -131,8 +141,9 @@ const pause_audio = (t: HANDLE_TYPE = 'set'): void => {
/**
* Mute audio
* @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');
if (t === 'toggle') {
@@ -145,8 +156,9 @@ const mute_audio = (t: HANDLE_TYPE = 'set'): void => {
/**
* Unmute audio
* @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');
if (t === 'toggle') {
@@ -159,9 +171,10 @@ const unmute_audio = (t: HANDLE_TYPE = 'set'): void => {
/**
* Seek to time audio
* @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');
if (from === 'from-end') {
@@ -174,9 +187,10 @@ const seek_audio = (seconds: number, from: 'from-start' | 'from-end' = 'from-sta
/**
* Skip by about audio
* @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');
if (type === 'backward') {
+12
View File
@@ -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);
-12
View File
@@ -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);
@@ -1,23 +1,28 @@
import { derived, type Readable } from 'svelte/store';
import { seconds_to_timestamp } from '../../utility';
import { seconds_to_timestamp } from 'svelte-podcast/utility';
import { audio_element } from './audio-element';
import { derived } from 'svelte/store';
export type EpisodeProgress = {
current_time: number;
timestamp: string;
has_ended: boolean;
};
/**
* Episode progress object.
* @typedef {Object} EpisodeProgress
* @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 = {
current_time: 0,
timestamp: '00:00',
has_ended: false,
} satisfies EpisodeProgress;
};
/**
* Episode progress store.
*
* @returns {Readable<EpisodeProgress>} A Svelte readable store that contains the current progress of the audio episode.
* The default episode progress object.
* @type {import('svelte/store').Readable<EpisodeProgress>}
*/
export const episode_progress = derived(audio_element, ($audio, set) => {
if (!$audio) return set(default_episode_progress);
@@ -40,4 +45,4 @@ export const episode_progress = derived(audio_element, ($audio, set) => {
* Remove event listener.
*/
return () => $audio.removeEventListener('timeupdate', () => set_value());
}) satisfies Readable<EpisodeProgress>;
});
-2
View File
@@ -1,2 +0,0 @@
export { default as AudioPlayer } from './player.svelte';
export { default as AudioProgress } from './progress.svelte';
+4
View File
@@ -0,0 +1,4 @@
export * from './audio';
export { AudioPlayer as default } from './audio';
export * from './user';
export * from './utility';
-5
View File
@@ -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-progress';
+82
View File
@@ -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,
};
-49
View File
@@ -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,
};
+77
View File
@@ -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,
};
-53
View File
@@ -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,
};
+67
View File
@@ -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),
};
-49
View File
@@ -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 './seconds-to-timestamp';
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.
* @param 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.
* @returns A string timestamp in the format of "hh:mm:ss".
* @param {number} seconds - The number of seconds to convert to a timestamp.
* @param {boolean} [force_hours=false] - Whether to always include hours in the timestamp, even if it's 0.
* @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_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.
* @param num - The number to format.
* @returns A string representation of the number with a leading 0 if it's less than 10.
* @param {number} num - The number to format.
* @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}`;
return num.toString();
}
+31
View File
@@ -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 };
};
-23
View File
@@ -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;
};