Initial Docs (#39)
@@ -3,7 +3,7 @@
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte"],
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"svelteSortOrder": "options-scripts-styles-markup",
|
||||
"svelteBracketNewLine": true,
|
||||
"svelteIndentScriptAndStyle": true,
|
||||
|
||||
@@ -24,5 +24,6 @@
|
||||
"problems.sortOrder": "severity",
|
||||
"todo-tree.tree.scanMode": "workspace only",
|
||||
"javascript.preferences.importModuleSpecifier": "relative",
|
||||
"typescript.preferences.importModuleSpecifier": "relative"
|
||||
"typescript.preferences.importModuleSpecifier": "relative",
|
||||
"files.associations": { ".env*": "dotenv", "*.svx": "mdx", "*.md": "mdx" }
|
||||
}
|
||||
|
||||
@@ -39,13 +39,13 @@ In no particular order, here are some of the things I'm confident will be added
|
||||
|
||||
```bash
|
||||
# with npm
|
||||
npm install svelte-podcast
|
||||
npm install svelte-podcast@latest
|
||||
|
||||
# with yarn
|
||||
yarn add svelte-podcast
|
||||
yarn add svelte-podcast@latest
|
||||
|
||||
# with pnpm
|
||||
pnpm add svelte-podcast
|
||||
pnpm add svelte-podcast@latest
|
||||
```
|
||||
|
||||
You must add one instance of the `<AudioLoader />` to your app. This should be places as high as possible, ideally in your root `+layout,svelte` file.
|
||||
@@ -62,129 +62,10 @@ You must add one instance of the `<AudioLoader />` to your app. This should be p
|
||||
|
||||
#### Basic usage
|
||||
|
||||
You will likely be interacting with svelte-podcast via one of the stores it exports.
|
||||
|
||||
```html
|
||||
<script>
|
||||
import {
|
||||
// a custom store for subscribing to the state of the audio player and controlling audio element
|
||||
audio,
|
||||
|
||||
// a custom store for subscribing to the users preferences and setting a users preference
|
||||
user_preferences,
|
||||
|
||||
// a utility for saving and loading the progress of an episode
|
||||
episode_progress,
|
||||
|
||||
// a utility for saving and loading both the progress and preferences
|
||||
save_podcast_state,
|
||||
} from 'svelte-podcast';
|
||||
</script>
|
||||
```
|
||||
|
||||
#### `audio` store
|
||||
|
||||
##### `subscribe`
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { audio } from 'svelte-podcast';
|
||||
|
||||
// shorthand
|
||||
$audio;
|
||||
|
||||
// longhand
|
||||
audio.subscribe((value) => {
|
||||
// value is the current state of the audio store
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
##### methods
|
||||
|
||||
###### play / pause
|
||||
|
||||
`audio.play()` to play the currently loaded audio, or `audio.play("toggle")` to toggle play/pause.
|
||||
|
||||
`audio.pause()` to pause the currently loaded audio, or `audio.pause("toggle")` to toggle play/pause.
|
||||
|
||||
###### mute / unmute
|
||||
|
||||
`audio.mute()` to mute the audio for the `<audio />` element, or `audio.mute("toggle")` to toggle mute/unmute.
|
||||
`audio.unmute()` to unmute the audio for the `<audio />` element, or `audio.unmute("toggle")` to toggle mute/unmute.
|
||||
|
||||
###### load / unload
|
||||
|
||||
`audio.load(data: AudioLoadData, opts: AudioLoadOptions)` to load a new episode into the `<audio />` element.
|
||||
|
||||
- AudioLoadData is an object with the following properties:
|
||||
- src: `string` - the URL or local path to the audio file
|
||||
- title: `string` - the title of the episode
|
||||
- artwork: `string` - the URL or local path to the artwork for the episode
|
||||
- AudioLoadOptions is an object with the following properties:
|
||||
- autoplay: `boolean` - whether or not to autoplay the audio when it is loaded. Typically this should be set to `true`
|
||||
- start_at: `number | undefined` - the time in seconds to start the audio at. Useful for resuming playback from a previous session. This will be overridden by the users episode progress if it exists.
|
||||
|
||||
`audio.unload()` - unloads the currently loaded audio file from the `<audio />` element.
|
||||
|
||||
###### seek / skip
|
||||
|
||||
`audio.seek(seconds, from)` - seek to a specific moment in the audio file.
|
||||
|
||||
- seconds: `number` - the time in seconds to seek to
|
||||
- from: `'from-start' | 'from-end'` - whether to seek relative to the start or end of the audio file. Defaults to `'from-start'`
|
||||
|
||||
`audio.skip(seconds, type)` - similar to seek, but always relative to the current time.
|
||||
|
||||
- seconds: `number` - the time in seconds to skip
|
||||
- type: `'forward' | 'backward'` - whether to skip forward or backward. Defaults to `'forward'`
|
||||
|
||||
Example payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"current_time": 35.624346,
|
||||
"duration": 3378.756,
|
||||
"ended": false,
|
||||
"loading": false,
|
||||
"paused": true,
|
||||
"start_at": 27.37508,
|
||||
"autoplay": true,
|
||||
"muted": false,
|
||||
"src": "/example-syntax.mp3",
|
||||
"metadata": {
|
||||
"title": "Supper Club × Rich Harris, Author of Svelte",
|
||||
"artwork": "https://ssl-static.libsyn.com/p/assets/b/3/c/d/b3cdf28da11ad39fe5bbc093207a2619/Syntax_-_499.jpg"
|
||||
},
|
||||
"playback_rate": 2,
|
||||
"volume": 1,
|
||||
"timestamp": "00:35"
|
||||
}
|
||||
```
|
||||
|
||||
#### `user_preferences` store
|
||||
|
||||
##### `subscribe`
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { user_preferences } from 'svelte-podcast';
|
||||
|
||||
// shorthand
|
||||
$user_preferences;
|
||||
|
||||
// longhand
|
||||
user_preferences.subscribe((value) => {
|
||||
// value is the current state of the user_preferences store
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
#### `episode_progress` utilities
|
||||
|
||||
#### `save_podcast_state` utilities
|
||||
|
||||
## Contributing
|
||||
You can find docs and guides for getting started on [svelte-podcast-docs.vercel.app](https://svelte-podcast-docs.vercel.app/)
|
||||
|
||||
> **Warning**
|
||||
> This project is very early in development. Contributions are welcome but I do not plan to provide detailed docs for contributions until the project is more stable.
|
||||
> This project is early in development. API changes are likely until we reach v1.0.0.
|
||||
|
||||
> **Info**
|
||||
> Contributions are welcome but I do not plan to provide guides for contributing until the project is more stable.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OllieJT/svelte-podcast/pulls"
|
||||
"url": "https://github.com/OllieJT/svelte-podcast.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -39,7 +39,7 @@
|
||||
"svelte": "^3.55.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inqling/svelte-icons": "^3.0.2",
|
||||
"@inqling/svelte-icons": "^3.1.0",
|
||||
"clsx": "^1.2.1",
|
||||
"just-clamp": "^4.2.0",
|
||||
"svelte-local-storage-store": "^0.4.0"
|
||||
@@ -47,21 +47,45 @@
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.0",
|
||||
"@playwright/test": "^1.31.2",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.10.0",
|
||||
"@sveltejs/adapter-static": "^2.0.1",
|
||||
"@sveltejs/kit": "^1.11.0",
|
||||
"@sveltejs/package": "^2.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.0",
|
||||
"@typescript-eslint/parser": "^5.54.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@typescript-eslint/eslint-plugin": "^5.54.1",
|
||||
"@typescript-eslint/parser": "^5.54.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-prettier": "^8.7.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.4",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"prettier-plugin-tailwindcss": "^0.2.4",
|
||||
"publint": "^0.1.10",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^3.0.4",
|
||||
"rehype-add-classes": "^1.0.0",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
"rehype-external-links": "^2.0.1",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-github": "^11.2.4",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"remark-toc": "^8.0.1",
|
||||
"svelte": "^3.56.0",
|
||||
"svelte-check": "^3.1.2",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"svhighlight": "^0.7.1",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"unified": "^10.1.2",
|
||||
"vite": "^4.1.4"
|
||||
},
|
||||
"svelte": "./dist/index.js",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
'tailwindcss/nesting': {},
|
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||
tailwindcss: {},
|
||||
//But others, like autoprefixer, need to run after,
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,12 +1,10 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
|
||||
/*
|
||||
declare module './lib/types' {
|
||||
interface EpisodeDetails {
|
||||
title: string;
|
||||
artwork: string;
|
||||
}
|
||||
interface EpisodeDetails {}
|
||||
}
|
||||
*/
|
||||
|
||||
declare global {
|
||||
namespace App {
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="scroll-smooth hover:scroll-auto">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="icon" href="%sveltekit.assets%/icon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
"
|
||||
>
|
||||
<body class="scroll-smooth hover:scroll-auto">
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/* Write your global styles here, in PostCSS syntax */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
article.section-content p {
|
||||
@apply my-2 text-lg leading-normal;
|
||||
}
|
||||
article.section-content li p {
|
||||
@apply my-0;
|
||||
}
|
||||
article.section-content mark {
|
||||
@apply bg-primary-100 text-primary-900;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.prose code {
|
||||
@apply inline rounded bg-primary-100 py-1 px-2 font-mono leading-none text-primary-800;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.prose code::before,
|
||||
.prose code::after {
|
||||
content: '';
|
||||
}
|
||||
h1.anchor,
|
||||
h2.anchor,
|
||||
h3.anchor,
|
||||
h4.anchor,
|
||||
h5.anchor,
|
||||
h6.anchor {
|
||||
@apply m-0 text-3xl font-semibold leading-10 text-mono-900;
|
||||
}
|
||||
|
||||
.prose h1 a,
|
||||
.prose h2 a,
|
||||
.prose h3 a,
|
||||
.prose h4 a,
|
||||
.prose h5 a,
|
||||
.prose h6 a,
|
||||
h1.anchor a,
|
||||
h2.anchor a,
|
||||
h3.anchor a,
|
||||
h4.anchor a,
|
||||
h5.anchor a,
|
||||
h6.anchor a {
|
||||
@apply relative font-medium underline decoration-transparent decoration-solid decoration-2 underline-offset-2;
|
||||
&::before {
|
||||
@apply absolute -left-[1.25ch] inline-block font-light leading-normal text-mono-400;
|
||||
content: '#';
|
||||
}
|
||||
&:hover::before {
|
||||
@apply text-primary-500;
|
||||
}
|
||||
&:hover {
|
||||
@apply decoration-primary-500;
|
||||
}
|
||||
}
|
||||
|
||||
.prose h3 a {
|
||||
@apply text-primary-600;
|
||||
}
|
||||
|
||||
.prose #table-of-contents + ol {
|
||||
list-style: decimal-leading-zero;
|
||||
}
|
||||
.prose #table-of-contents + ol,
|
||||
.prose #table-of-contents + ul {
|
||||
ul,
|
||||
ol,
|
||||
li {
|
||||
@apply my-0.5;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: lower-alpha;
|
||||
ol {
|
||||
list-style: disc;
|
||||
ol {
|
||||
list-style: circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.prose #features + table td:first-child > * {
|
||||
@apply sm:block sm:w-max;
|
||||
}
|
||||
}
|
||||
|
||||
/***** prism-theme *****/
|
||||
/*
|
||||
* Laserwave Theme originally by Jared Jones for Visual Studio Code
|
||||
* https://github.com/Jaredk3nt/laserwave
|
||||
*
|
||||
* Ported for PrismJS by Simon Jespersen [https://github.com/simjes]
|
||||
*/
|
||||
|
||||
code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
background: #27212e;
|
||||
color: #ffffff;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; /* this is the default */
|
||||
/* The following properties are standard, please leave them as they are */
|
||||
font-size: 1em;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 2;
|
||||
-o-tab-size: 2;
|
||||
tab-size: 2;
|
||||
/* The following properties are also standard */
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
code[class*='language-']::-moz-selection,
|
||||
code[class*='language-'] ::-moz-selection,
|
||||
pre[class*='language-']::-moz-selection,
|
||||
pre[class*='language-'] ::-moz-selection {
|
||||
background: #eb64b927;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code[class*='language-']::selection,
|
||||
code[class*='language-'] ::selection,
|
||||
pre[class*='language-']::selection,
|
||||
pre[class*='language-'] ::selection {
|
||||
background: #eb64b927;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Properties specific to code blocks */
|
||||
pre[class*='language-'] {
|
||||
padding: 1em; /* this is standard */
|
||||
margin: 0.5em 0; /* this is the default */
|
||||
overflow: auto; /* this is standard */
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
/* Properties specific to inline code */
|
||||
:not(pre) > code[class*='language-'] {
|
||||
padding: 0.2em 0.3em;
|
||||
border-radius: 0.5rem;
|
||||
white-space: normal; /* this is standard */
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.cdata {
|
||||
color: #91889b;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #7b6995;
|
||||
}
|
||||
|
||||
.token.builtin,
|
||||
.token.constant,
|
||||
.token.boolean {
|
||||
color: #ffe261;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #b381c5;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.atrule,
|
||||
.token.property,
|
||||
.token.keyword {
|
||||
color: #40b4c4;
|
||||
}
|
||||
|
||||
.token.doctype,
|
||||
.token.operator,
|
||||
.token.inserted,
|
||||
.token.tag,
|
||||
.token.class-name,
|
||||
.token.symbol {
|
||||
color: #74dfc4;
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.function,
|
||||
.token.deleted,
|
||||
.token.selector {
|
||||
color: #eb64b9;
|
||||
}
|
||||
|
||||
.token.attr-value,
|
||||
.token.regex,
|
||||
.token.char,
|
||||
.token.string {
|
||||
color: #b4dce7;
|
||||
}
|
||||
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.token.variable {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* The following rules are pretty similar across themes, but feel free to adjust them */
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Hello World
|
||||
|
||||
this is some content from @OllieJT on pr #32
|
||||
|
||||
this is on https://github.com/OllieJT/svelte-podcast/pull/39
|
||||
|
||||
> this is a quote
|
||||
|
||||
## this is a subheading
|
||||
|
||||
`const this_is = "code"`
|
||||
|
||||
```js
|
||||
const this_is = 'code_block';
|
||||
console.log(this_is);
|
||||
```
|
||||
|
||||
```js {1,3-4} showLineNumbers
|
||||
function fancyAlert(arg) {
|
||||
if (arg) {
|
||||
$.facebox({ div: '#foo' });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```svelte {1,3-4} showLineNumbers
|
||||
<script>
|
||||
import { episode_audio } from 'svelte-podcast';
|
||||
</script>
|
||||
|
||||
<!-- load the episode on click -->
|
||||
<button
|
||||
on:click={() =>
|
||||
episode_audio.load('/episode-audio.mp3', {
|
||||
/* optional metadata */
|
||||
})}
|
||||
>
|
||||
Load Episode
|
||||
</button>
|
||||
|
||||
<!-- unload the episode on click -->
|
||||
<button on:click={() => episode_audio.unload()}>Unload Episode</button>
|
||||
```
|
||||
|
||||
[This is a link](https://www.google.com)
|
||||
|
||||
- this is a list
|
||||
- this is a list
|
||||
- this is a list
|
||||
|
||||
1. this is a numbered list
|
||||
2. this is a numbered list
|
||||
3. this is a numbered list
|
||||
|
||||
| this | is | a | table |
|
||||
| ---- | --- | --- | ----- |
|
||||
| this | is | a | table |
|
||||
| this | is | a | table |
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { topics } from '$content/utility/anchor-registry';
|
||||
import { Hashtag } from '@inqling/svelte-icons/heroicon-24-outline';
|
||||
|
||||
export let value: string;
|
||||
$: id = topics.slugify(value || '');
|
||||
topics.add(value);
|
||||
</script>
|
||||
|
||||
<a href="/#{id}" class="group relative -ml-[1em] hover:text-black">
|
||||
<Hashtag class="h-[1em] w-[1em] -translate-x-1 text-mono-300 group-hover:text-primary-600" /><span
|
||||
class="underline decoration-primary-50/0 decoration-4 group-hover:decoration-primary-500"
|
||||
>{value}</span
|
||||
>
|
||||
<span class="pointer-events-none absolute -top-10 h-px w-px" {id} />
|
||||
</a>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import Anchor from './anchor.svelte';
|
||||
|
||||
export let title: string;
|
||||
export let anchor: boolean = false;
|
||||
</script>
|
||||
|
||||
<li class="list-item list-disc text-primary-800">
|
||||
<h4 class="text-lg font-medium leading-normal text-primary-600">
|
||||
{#if anchor}
|
||||
<Anchor value={title} />
|
||||
{:else}
|
||||
{title}
|
||||
{/if}
|
||||
</h4>
|
||||
<p class="mt-0 text-mono-600">
|
||||
<slot />
|
||||
</p>
|
||||
</li>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Anchor from './anchor.svelte';
|
||||
|
||||
export let title: string;
|
||||
export let subtitle: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<h3 class="text-xl font-medium leading-relaxed text-mono-900"><Anchor value={title} /></h3>
|
||||
{#if subtitle}
|
||||
<p class="text-xl font-normal leading-normal text-mono-500">{subtitle}</p>
|
||||
{/if}
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<article
|
||||
class="section-content my-4 w-full border-t-2 border-mono-100 py-4 text-mono-600 first-of-type:border-t-0 first-of-type:pt-0"
|
||||
>
|
||||
<slot />
|
||||
</article>
|
||||
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import type { SvelteIcon } from '@inqling/svelte-icons';
|
||||
|
||||
type Feature = {
|
||||
icon: SvelteIcon;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export let features: Feature[];
|
||||
</script>
|
||||
|
||||
<dl
|
||||
class="mx-auto grid max-w-2xl grid-cols-1 gap-6 py-6 text-base leading-7 text-mono-600 sm:grid-cols-2 sm:gap-y-10 lg:mx-0 lg:max-w-none"
|
||||
>
|
||||
{#each features as feature}
|
||||
<div class="relative pl-9">
|
||||
<dt class="inline font-semibold text-mono-900">
|
||||
<svelte:component
|
||||
this={feature.icon}
|
||||
class="absolute top-1 left-1 h-5 w-5 text-primary-600"
|
||||
/>
|
||||
|
||||
{feature.title}
|
||||
</dt>
|
||||
<dd class="inline">{feature.description}</dd>
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as ContentListItem } from './content-list-item.svelte';
|
||||
export { default as ContentTitle } from './content-title.svelte';
|
||||
export { default as Content } from './content.svelte';
|
||||
export { default as Features } from './features.svelte';
|
||||
export { default as SectionTitle } from './section-title.svelte';
|
||||
export { default as Section } from './section.svelte';
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Anchor from './anchor.svelte';
|
||||
|
||||
export let title: string;
|
||||
</script>
|
||||
|
||||
<h2 class="text-3xl font-normal leading-loose text-primary-600">
|
||||
<Anchor value={title} />
|
||||
</h2>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<!-- <div class="relative mx-auto w-full max-w-7xl p-4 sm:px-6 lg:px-8"> -->
|
||||
<div class="relative mx-auto w-full max-w-prose p-4 text-lg sm:px-6 lg:px-8">
|
||||
<section class="w-full">
|
||||
<slot />
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,184 @@
|
||||
## Table of contents
|
||||
|
||||
## What is svelte-podcast?
|
||||
|
||||
### Motivation
|
||||
|
||||
At it's core svelte-podcast provides tooling to make the following tasks easier:
|
||||
|
||||
#### I want to build a custom audio player
|
||||
|
||||
The default audio element is quite nice, but there are 2 main reasons why people want to build their own;
|
||||
|
||||
1. to customize the look and feel
|
||||
2. to add custom functionality.
|
||||
|
||||
svelte-podcast provides a simple API enabling you to resolve both of these desires.
|
||||
|
||||
#### I find managing the state of audio difficult
|
||||
|
||||
It's one thing to load, play, and pause audio on a single page, but what if you want to build more custom or advanced behaviours like
|
||||
|
||||
> Can audio continue playing when the user navigates to a new page.
|
||||
|
||||
> Can I play/pause an episode from content like an article?
|
||||
|
||||
> Can I highlight show notes relevant to the current timestamp?
|
||||
|
||||
svelte-podcast makes solving these challenges easier by simplifying your interactions with the audio element.
|
||||
|
||||
#### I find RSS frustrating to work with
|
||||
|
||||
We're still working on this, it'll be coming soon™️!
|
||||
|
||||
### Features
|
||||
|
||||
| Feature | Description |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| **Simple API** | Get essential audio data like the current timestamp, duration, play state and more... |
|
||||
| **Simple Controls** | Control your active audio source with play, pause, seek, and more methods... |
|
||||
| **Extensible** | Define your own metadata for each episode to easily association content with the current audio |
|
||||
| **Save Progress** | Save and load a users progress and preferences to localStorage, or your own database |
|
||||
| **Persistence** | If your site makes use of client side routing, audio will continue playing when users navigate |
|
||||
| **Typescript** | 1st class types, with type overrides for defining your own metadata requirements |
|
||||
|
||||
## Get Started
|
||||
|
||||
### Install
|
||||
|
||||
Install the latest version of svelte-podcast with your preferred package manager.
|
||||
|
||||
```sh
|
||||
# with npm
|
||||
npm install svelte-podcast@latest
|
||||
|
||||
# with yarn
|
||||
yarn add svelte-podcast@latest
|
||||
|
||||
# with pnpm
|
||||
pnpm add svelte-podcast@latest
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
Add the AudioLoader component to your root layout. You must have one of these for svelte-podcast to work. You should also only load one instance of this at a time, and so we recommend you loading it at the base of your app.
|
||||
|
||||
```svelte
|
||||
<!-- /routes/+layout.svelte -->
|
||||
<script>
|
||||
import { AudioLoader } from 'svelte-podcast';
|
||||
</script>
|
||||
|
||||
<AudioLoader />
|
||||
|
||||
<!-- your layout -->
|
||||
|
||||
<slot />
|
||||
```
|
||||
|
||||
### Load an episode
|
||||
|
||||
All you need to load an episode is a URL to an audio file. svelte-podcast uses a html audio element under the hood, so any audio file compatible with the autio element is also compatible with this package.
|
||||
|
||||
#### Using a URL (Most Common)
|
||||
|
||||
An **audio url** could be a URL to an MP3 file from an RSS feed, like this: `https://media.transistor.fm/27a058c9/27b595e2.mp3`. It could also be a path to a static file on your server.
|
||||
|
||||
#### Using a static file
|
||||
|
||||
If you're using SvelteKit, you can store **static files** in the /static directory. When your site is built, everything in the static directory will be at the root of your site. If you have a file in `/static/episides/episode-01.mp3` you could load it as `/episides/episode-01.mp3`
|
||||
|
||||
#### Using the AudioLoader component
|
||||
|
||||
Only one piece of audio can be loaded at a time, however a users progress for each audio is saved in localStorage when they play, pause, or skip. You can load audio via the `episode_audio` store from anywhere in your project.
|
||||
|
||||
##### Example: Load audio after page is loaded
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { episode_audio } from 'svelte-podcast';
|
||||
|
||||
onMount(() => {
|
||||
// load the episode on mount without any metadata
|
||||
episode_audio.load('/episode-audio.mp3', {
|
||||
/* optional metadata */
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
##### Example: Load audio after a user clicks a button
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { episode_audio } from 'svelte-podcast';
|
||||
</script>
|
||||
|
||||
<!-- load the episode on click -->
|
||||
<button
|
||||
on:click={() =>
|
||||
episode_audio.load('/episode-audio.mp3', {
|
||||
/* optional metadata */
|
||||
})}
|
||||
>
|
||||
Load Episode
|
||||
</button>
|
||||
|
||||
<!-- unload the episode on click -->
|
||||
<button on:click={() => episode_audio.unload()}>Unload Episode</button>
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### `<PlayerWidget/>`
|
||||
|
||||
Coming soon...
|
||||
|
||||
### `<PlayerStack/>`
|
||||
|
||||
Coming soon...
|
||||
|
||||
### `<HeadlessTimeline/>`
|
||||
|
||||
Coming soon...
|
||||
|
||||
## Utilities
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Coming soon...
|
||||
|
||||
### audio
|
||||
|
||||
Coming soon...
|
||||
|
||||
### episode_progress
|
||||
|
||||
Coming soon...
|
||||
|
||||
### episode_details
|
||||
|
||||
Coming soon...
|
||||
|
||||
### user_progress & user_preferences
|
||||
|
||||
Coming soon...
|
||||
|
||||
### save_podcast_state
|
||||
|
||||
Coming soon...
|
||||
|
||||
### secondsToTimestamp
|
||||
|
||||
Coming soon...
|
||||
|
||||
## Typescript
|
||||
|
||||
### Override types
|
||||
|
||||
Coming soon...
|
||||
|
||||
### Exported types
|
||||
|
||||
Coming soon...
|
||||
@@ -0,0 +1,14 @@
|
||||
import { assets } from '$app/paths';
|
||||
|
||||
export const episodes = {
|
||||
syntax: {
|
||||
src: `${assets}/example-syntax.mp3`,
|
||||
title: `Supper Club × Rich Harris, Author of Svelte`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/b/3/c/d/b3cdf28da11ad39fe5bbc093207a2619/Syntax_-_499.jpg`,
|
||||
},
|
||||
knomii: {
|
||||
src: `${assets}/example-knomii.mp3`,
|
||||
title: `Empowerment starts with letting go of control`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/f/a/8/d/fa8d56d5226884335f2e77a3093c12a1/ep-6.png`,
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,9 @@
|
||||
<script>
|
||||
import { AudioLoader } from 'svelte-podcast';
|
||||
</script>
|
||||
|
||||
<AudioLoader />
|
||||
|
||||
<!-- your layout -->
|
||||
|
||||
<slot />
|
||||
@@ -0,0 +1,5 @@
|
||||
import example_add_loader from './add-loader.svelte?raw';
|
||||
import example_load_audio_click from './load-audio-click.svelte?raw';
|
||||
import example_load_audio_mount from './load-audio-mount.svelte?raw';
|
||||
|
||||
export { example_add_loader, example_load_audio_click, example_load_audio_mount };
|
||||
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { episode_audio } from 'svelte-podcast';
|
||||
</script>
|
||||
|
||||
<!-- load the episode on click -->
|
||||
<button
|
||||
on:click={() =>
|
||||
episode_audio.load('/episode-audio.mp3', {
|
||||
/* optional metadata */
|
||||
})}
|
||||
>
|
||||
Load Episode
|
||||
</button>
|
||||
|
||||
<!-- unload the episode on click -->
|
||||
<button on:click={() => episode_audio.unload()}>Unload Episode</button>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { episode_audio } from 'svelte-podcast';
|
||||
|
||||
onMount(() => {
|
||||
// load the episode on mount without any metadata
|
||||
episode_audio.load('/episode-audio.mp3', {
|
||||
/* optional metadata */
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
type Topic = string;
|
||||
|
||||
const content = new Set<Topic>();
|
||||
|
||||
const ropic_registry = writable<Topic[]>([...content]);
|
||||
|
||||
function add(value: Topic) {
|
||||
ropic_registry.update((prev) => {
|
||||
const content = new Set<Topic>(prev.filter(Boolean));
|
||||
Boolean(value) && content.add(value);
|
||||
return [...content];
|
||||
});
|
||||
}
|
||||
|
||||
function slugify(value: string) {
|
||||
// replace all apaces with dashes
|
||||
const slug = value
|
||||
.replace(/\s+/g, '-')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^\x00-\x7F]/g, '-')
|
||||
.replace(/[^a-z0-9-]/g, '-')
|
||||
.replace(/^-+/, '')
|
||||
.replace(/-+$/, '')
|
||||
.replace(/--+/g, '-');
|
||||
|
||||
return encodeURI(slug);
|
||||
}
|
||||
|
||||
export const topics = {
|
||||
subscribe: ropic_registry.subscribe,
|
||||
add,
|
||||
slugify,
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { CodeBlockProps } from 'svhighlight/code/CodeBlock.svelte';
|
||||
|
||||
export const code_opts = {
|
||||
background: 'bg-mono-900',
|
||||
focusType: 'highlight',
|
||||
highlightColor: 'bg-mono-800',
|
||||
headerClasses: 'bg-mono-800',
|
||||
} satisfies CodeBlockProps;
|
||||
@@ -0,0 +1,8 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
|
||||
// import type { ComponentType, SvelteComponentTyped } from 'svelte';
|
||||
|
||||
declare module '*.md' {
|
||||
export default ComponentType<SvelteComponentTyped>;
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export const audio_element = readable<HTMLAudioElement | null>(null, (set) => {
|
||||
el.id = ID;
|
||||
el.setAttribute('preload', 'metadata');
|
||||
el.muted = false;
|
||||
el.autoplay = true;
|
||||
el.autoplay = false;
|
||||
el.controls = false;
|
||||
|
||||
const preferences = get(user_preferences);
|
||||
|
||||
@@ -74,7 +74,7 @@ export const episode_audio = {
|
||||
el.src = '';
|
||||
episode_details.set(null);
|
||||
},
|
||||
play: (t?: HandleType) => {
|
||||
play: (t: HandleType = 'set') => {
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('play');
|
||||
|
||||
@@ -84,7 +84,7 @@ export const episode_audio = {
|
||||
el.play();
|
||||
}
|
||||
},
|
||||
pause: (t?: HandleType) => {
|
||||
pause: (t: HandleType = 'set') => {
|
||||
user_progress.save();
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('pause');
|
||||
@@ -95,7 +95,7 @@ export const episode_audio = {
|
||||
el.pause();
|
||||
}
|
||||
},
|
||||
mute: (t?: HandleType) => {
|
||||
mute: (t: HandleType = 'set') => {
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('mute');
|
||||
|
||||
@@ -105,7 +105,7 @@ export const episode_audio = {
|
||||
el.muted = true;
|
||||
}
|
||||
},
|
||||
unmute: (t?: HandleType) => {
|
||||
unmute: (t: HandleType = 'set') => {
|
||||
const el = get(audio_element);
|
||||
if (!el) return no_element('unmute');
|
||||
|
||||
|
||||
@@ -1,7 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { assets, base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Github } from '@inqling/svelte-icons/simple-icons';
|
||||
import clsx from 'clsx';
|
||||
import 'highlight.js/styles/github-dark.css';
|
||||
import { AudioLoader } from 'svelte-podcast';
|
||||
import '../app.postcss';
|
||||
|
||||
const page_links = [
|
||||
{ label: 'Docs', href: '/' },
|
||||
{ label: 'Demo', href: '/demo' },
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<header
|
||||
class="sticky top-0 z-50 border-b border-mono-100 bg-white text-lg leading-none lg:overflow-y-visible"
|
||||
id="navigation"
|
||||
>
|
||||
<div class="mx-auto max-w-prose px-4 py-1 sm:py-2 sm:px-6 lg:px-8">
|
||||
<div class="relative grid grid-cols-12 gap-1 text-base md:gap-3">
|
||||
<div class="col-span-2 flex items-center justify-start leading-none">
|
||||
<a href="{base}/#navigation" class="flex-shrink-0 py-1">
|
||||
<img
|
||||
class="block h-8 w-auto"
|
||||
width={60}
|
||||
height={32}
|
||||
src="{assets}/logo.png"
|
||||
alt="Svelte-Podcast"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-span-8 flex items-center justify-center leading-none sm:gap-1">
|
||||
{#each page_links as link}
|
||||
{@const is_homepage = link.href === '/' && $page.url.pathname === link.href}
|
||||
{@const is_match = link.href !== '/' && $page.url.pathname.startsWith(link.href)}
|
||||
<a
|
||||
href={base + link.href}
|
||||
class={clsx(
|
||||
'flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium leading-none ring-inset focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500 focus:ring-offset-2 sm:px-4 sm:text-base',
|
||||
is_homepage || is_match
|
||||
? 'bg-primary-200 text-primary-900'
|
||||
: 'text-mono-500 hover:bg-mono-100 hover:text-mono-900',
|
||||
)}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="col-span-2 flex items-center justify-end leading-none">
|
||||
<a
|
||||
href="https://github.com/OllieJT/svelte-podcast"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="flex-shrink-0 rounded-full bg-white p-1 text-mono-500 ring-2 ring-transparent transition-all ease-out hover:text-mono-900 hover:ring-mono-200 hover:ring-offset-2 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2"
|
||||
>
|
||||
<span class="sr-only">View Repository</span>
|
||||
<Github class="h-6 w-6" aria-hidden="true" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<AudioLoader />
|
||||
|
||||
<slot />
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const trailingSlash = 'always';
|
||||
export const prerender = true;
|
||||
@@ -0,0 +1,8 @@
|
||||
import content from '$content/docs.md?raw';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { use_markdown } from './use-markdown';
|
||||
|
||||
export const load = (async () => {
|
||||
const post = await use_markdown(content);
|
||||
return { html: post.value };
|
||||
}) satisfies PageServerLoad;
|
||||
@@ -1,130 +1,108 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
episode_audio,
|
||||
episode_progress,
|
||||
PlayerStack,
|
||||
PlayerWidget,
|
||||
user_preferences,
|
||||
user_progress,
|
||||
} from 'svelte-podcast';
|
||||
import { base } from '$app/paths';
|
||||
import { Section } from '$content/components';
|
||||
// import Docs from '$content/docs.md';
|
||||
import { episodes } from '$content/episodes';
|
||||
import { onMount } from 'svelte';
|
||||
import { episode_audio, PlayerWidget } from 'svelte-podcast';
|
||||
import type { PageServerData } from './$types';
|
||||
|
||||
const sources = {
|
||||
syntax: {
|
||||
src: `/example-syntax.mp3`,
|
||||
title: `Supper Club × Rich Harris, Author of Svelte`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/b/3/c/d/b3cdf28da11ad39fe5bbc093207a2619/Syntax_-_499.jpg`,
|
||||
},
|
||||
knomii: {
|
||||
src: `/example-knomii.mp3`,
|
||||
title: `Empowerment starts with letting go of control`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/f/a/8/d/fa8d56d5226884335f2e77a3093c12a1/ep-6.png`,
|
||||
},
|
||||
} as const;
|
||||
export let data: PageServerData;
|
||||
|
||||
let current_time = 0;
|
||||
$: current_time = $episode_progress.current_time;
|
||||
|
||||
$: console.log('details :: ', $episode_audio?.details);
|
||||
onMount(() => {
|
||||
// load the episode on mount without any metadata
|
||||
episode_audio.load(episodes.knomii.src, {
|
||||
/* optional metadata */
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<pre>{JSON.stringify(
|
||||
{
|
||||
$episode_audio,
|
||||
$episode_progress,
|
||||
$user_preferences,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}</pre>
|
||||
|
||||
<h1>Demo</h1>
|
||||
<a href="/another-page">Another Page</a>
|
||||
<button type="button" on:click={user_progress.clear}>Clear progress for all episodes</button>
|
||||
<button type="button" on:click={user_preferences.clear}>Clear all preferences</button>
|
||||
|
||||
<h5>Load Audio</h5>
|
||||
<button type="button" on:click={() => episode_audio.load(sources['syntax'].src, sources['syntax'])}
|
||||
>Syntax</button
|
||||
<div class="relative isolate bg-white">
|
||||
<svg
|
||||
class="absolute inset-0 -z-10 h-full w-full stroke-mono-200 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.load(sources['knomii'].src, sources['knomii'])}
|
||||
>Knomii</button
|
||||
<defs>
|
||||
<pattern
|
||||
id="0787a7c5-978c-4f66-83c7-11c213f99cb7"
|
||||
width="200"
|
||||
height="200"
|
||||
x="50%"
|
||||
y="-1"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.unload()}>None</button>
|
||||
|
||||
<h5>Custom audio controls</h5>
|
||||
|
||||
<h6>Play / Pause Actions</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.play()}>Play</button>
|
||||
<button type="button" on:click={() => episode_audio.pause()}>Pause</button>
|
||||
<button type="button" on:click={() => episode_audio.pause('toggle')}>Toggle</button>
|
||||
|
||||
<h6>Audio Actions</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.mute()}>Mute</button>
|
||||
<button type="button" on:click={() => episode_audio.unmute()}>Unmute</button>
|
||||
<button type="button" on:click={() => episode_audio.mute('toggle')}>Toggle</button>
|
||||
|
||||
<h6>Seeking</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.seek(30)}>Go to 30s from start </button>
|
||||
<button type="button" on:click={() => episode_audio.seek(30, 'from-end')}>Go to 30s from end</button
|
||||
<path d="M.5 200V.5H200" fill="none" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect
|
||||
width="100%"
|
||||
height="100%"
|
||||
stroke-width="0"
|
||||
fill="url(#0787a7c5-978c-4f66-83c7-11c213f99cb7)"
|
||||
/>
|
||||
</svg>
|
||||
<main>
|
||||
<div class="relative py-24 sm:py-32 lg:pb-40">
|
||||
<div class="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div class="mx-auto max-w-2xl text-center">
|
||||
<h1>
|
||||
<span class="block text-xl font-medium text-primary-600">
|
||||
svelte-podcast<span class="sr-only">: </span>
|
||||
</span>
|
||||
<span class="text-4xl font-bold tracking-tight text-mono-900 sm:text-6xl">
|
||||
The fastest way to build a podcast site with Svelte.
|
||||
</span>
|
||||
</h1>
|
||||
<p class="mt-6 text-xl leading-8 text-mono-600">
|
||||
A suite of tools and components to build your own podcast players, and work with RSS
|
||||
podcast data in SvelteKit.
|
||||
<span class="mt-3 block text-base leading-none text-primary-800">
|
||||
<span
|
||||
class="inline-block rounded-full bg-primary-50 px-3 py-1.5 text-xs font-medium uppercase tracking-wider text-primary-600"
|
||||
>
|
||||
Coming Soon<span class="sr-only">:</span>
|
||||
</span>
|
||||
<span class="tracking-wide">SSR utilities for consuming RSS podcast feeds</span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="mt-10 flex items-center justify-center gap-x-3">
|
||||
<a
|
||||
href="{base}/#get-started"
|
||||
class="rounded-md bg-primary-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-primary-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
|
||||
>
|
||||
Get started
|
||||
</a>
|
||||
<a
|
||||
href="{base}/demo"
|
||||
class="rounded-md bg-white px-3.5 py-2.5 text-sm font-semibold text-mono-600 hover:bg-primary-50 hover:text-primary-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-600"
|
||||
>
|
||||
Examples <span aria-hidden="true">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-16 flow-root sm:mt-24">
|
||||
<div
|
||||
class="-m-2 rounded-xl border border-mono-100 bg-white p-1 shadow-2xl shadow-mono-200 sm:p-2 lg:-m-4 lg:rounded-2xl lg:p-4"
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.skip(10, 'forward')}>Skip 10s</button>
|
||||
<button type="button" on:click={() => episode_audio.skip(10, 'backward')}>Rewind 10s</button>
|
||||
|
||||
<h6>Playback Rate</h6>
|
||||
|
||||
{#each [0.5, 1, 2, 3] as rate}
|
||||
<button type="button" on:click={() => user_preferences.set.playback_rate(rate)}>
|
||||
{rate}x
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- <PodcastPlayer
|
||||
artwork={$episode_audio?.details?.artwork}
|
||||
title={$episode_audio?.details?.title || 'Podcast Name'}
|
||||
/> -->
|
||||
|
||||
<br />
|
||||
<PlayerWidget />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerWidget include={{ playback_rate: true }} />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerWidget include={{ skip_back: 10, skip_forward: 30 }} />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerWidget
|
||||
style="width: 100%;"
|
||||
class="w-full border border-mono-100"
|
||||
include={{
|
||||
duration: true,
|
||||
current_time: true,
|
||||
playback_rate: true,
|
||||
duration: true,
|
||||
skip_back: 10,
|
||||
skip_forward: 30,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<PlayerStack style="width:400px;" />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerStack include={{ playback_rate: true }} />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerStack include={{ skip_back: 10, skip_forward: 30, timestamps: true }} />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerStack
|
||||
include={{
|
||||
playback_rate: true,
|
||||
timestamps: true,
|
||||
skip_back: 10,
|
||||
skip_forward: 30,
|
||||
}}
|
||||
/>
|
||||
<Section>
|
||||
<div class="prose prose-lg prose-slate">
|
||||
{@html data.html}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
episode_audio,
|
||||
episode_progress,
|
||||
PlayerWidget,
|
||||
user_preferences,
|
||||
user_progress,
|
||||
} from 'svelte-podcast';
|
||||
|
||||
const sources = {
|
||||
syntax: {
|
||||
src: `/example-syntax.mp3`,
|
||||
title: `Supper Club × Rich Harris, Author of Svelte`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/b/3/c/d/b3cdf28da11ad39fe5bbc093207a2619/Syntax_-_499.jpg`,
|
||||
},
|
||||
knomii: {
|
||||
src: `/example-knomii.mp3`,
|
||||
title: `Empowerment starts with letting go of control`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/f/a/8/d/fa8d56d5226884335f2e77a3093c12a1/ep-6.png`,
|
||||
},
|
||||
} as const;
|
||||
|
||||
let current_time = 0;
|
||||
$: current_time = $episode_progress.current_time;
|
||||
|
||||
$: console.log('details :: ', $episode_audio?.details);
|
||||
</script>
|
||||
|
||||
<pre>{JSON.stringify(
|
||||
{
|
||||
$episode_audio,
|
||||
$episode_progress,
|
||||
$user_preferences,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}</pre>
|
||||
|
||||
<h1>Demo</h1>
|
||||
<a href="/another-page">Another Page</a>
|
||||
<button type="button" on:click={user_progress.clear}>Clear progress for all episodes</button>
|
||||
<button type="button" on:click={user_preferences.clear}>Clear all preferences</button>
|
||||
|
||||
<h5>Load Audio</h5>
|
||||
<button type="button" on:click={() => episode_audio.load(sources['syntax'].src, sources['syntax'])}
|
||||
>Syntax</button
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.load(sources['knomii'].src, sources['knomii'])}
|
||||
>Knomii</button
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.unload()}>None</button>
|
||||
|
||||
<h5>Custom audio controls</h5>
|
||||
|
||||
<h6>Play / Pause Actions</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.play()}>Play</button>
|
||||
<button type="button" on:click={() => episode_audio.pause()}>Pause</button>
|
||||
<button type="button" on:click={() => episode_audio.pause('toggle')}>Toggle</button>
|
||||
|
||||
<h6>Audio Actions</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.mute()}>Mute</button>
|
||||
<button type="button" on:click={() => episode_audio.unmute()}>Unmute</button>
|
||||
<button type="button" on:click={() => episode_audio.mute('toggle')}>Toggle</button>
|
||||
|
||||
<h6>Seeking</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.seek(30)}>Go to 30s from start </button>
|
||||
<button type="button" on:click={() => episode_audio.seek(30, 'from-end')}>Go to 30s from end</button
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.skip(10, 'forward')}>Skip 10s</button>
|
||||
<button type="button" on:click={() => episode_audio.skip(10, 'backward')}>Rewind 10s</button>
|
||||
|
||||
<h6>Playback Rate</h6>
|
||||
|
||||
{#each [0.5, 1, 2, 3] as rate}
|
||||
<button type="button" on:click={() => user_preferences.set.playback_rate(rate)}>
|
||||
{rate}x
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<hr />
|
||||
|
||||
<!-- <PodcastPlayer
|
||||
artwork={$episode_audio?.details?.artwork}
|
||||
title={$episode_audio?.details?.title || 'Podcast Name'}
|
||||
/> -->
|
||||
|
||||
<br />
|
||||
<PlayerWidget />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerWidget include={{ playback_rate: true }} />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerWidget include={{ skip_back: 10, skip_forward: 30 }} />
|
||||
<br />
|
||||
<br />
|
||||
<PlayerWidget
|
||||
include={{
|
||||
current_time: true,
|
||||
playback_rate: true,
|
||||
duration: true,
|
||||
skip_back: 10,
|
||||
skip_forward: 30,
|
||||
}}
|
||||
/>
|
||||
@@ -0,0 +1,262 @@
|
||||
<script lang="ts">
|
||||
import { Section } from '$content/components';
|
||||
import {
|
||||
episode_audio,
|
||||
episode_progress,
|
||||
PlayerStack,
|
||||
PlayerWidget,
|
||||
user_preferences,
|
||||
user_progress,
|
||||
} from 'svelte-podcast';
|
||||
import PreviewComponent from './preview-component.svelte';
|
||||
import PreviewDataCode from './preview-data-code.svelte';
|
||||
import PreviewData from './preview-data.svelte';
|
||||
|
||||
const sources = {
|
||||
syntax: {
|
||||
src: `/example-syntax.mp3`,
|
||||
title: `Supper Club × Rich Harris, Author of Svelte`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/b/3/c/d/b3cdf28da11ad39fe5bbc093207a2619/Syntax_-_499.jpg`,
|
||||
},
|
||||
knomii: {
|
||||
src: `/example-knomii.mp3`,
|
||||
title: `Empowerment starts with letting go of control`,
|
||||
artwork: `https://ssl-static.libsyn.com/p/assets/f/a/8/d/fa8d56d5226884335f2e77a3093c12a1/ep-6.png`,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// let current_time = 0;
|
||||
// $: current_time = $episode_progress.current_time;
|
||||
|
||||
$: console.log('details :: ', $episode_audio?.details);
|
||||
|
||||
let player_widget_current_time = true;
|
||||
$: player_widget = {
|
||||
current_time: true,
|
||||
playback_rate: true,
|
||||
duration: true,
|
||||
skip_back: 10,
|
||||
skip_forward: 30,
|
||||
};
|
||||
|
||||
$: player_stack = {
|
||||
playback_rate: true,
|
||||
timestamps: true,
|
||||
skip_back: 10,
|
||||
skip_forward: 30,
|
||||
};
|
||||
</script>
|
||||
|
||||
<Section>
|
||||
<div class="flex flex-col items-stretch space-y-6">
|
||||
<h1>This page is a work in progress</h1>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<PreviewComponent name="PlayerWidget">
|
||||
<svelte:fragment slot="options">
|
||||
<div>
|
||||
<label for="pw_current_time">current_time</label>
|
||||
<input id="pw_current_time" type="checkbox" bind:checked={player_widget.current_time} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_playback_rate">playback_rate</label>
|
||||
<input id="pw_playback_rate" type="checkbox" bind:checked={player_widget.playback_rate} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_duration">duration</label>
|
||||
<input id="pw_duration" type="checkbox" bind:checked={player_widget.duration} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_skip_back">skip_back</label>
|
||||
<input id="pw_skip_back" type="number" bind:value={player_widget.skip_back} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_skip_forward">skip_forward</label>
|
||||
<input id="pw_skip_forward" type="number" bind:value={player_widget.skip_forward} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<PlayerWidget include={player_widget} />
|
||||
</PreviewComponent>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<PreviewComponent name="PlayerStack">
|
||||
<svelte:fragment slot="options">
|
||||
<div>
|
||||
<label for="pw_playback_rate">playback_rate</label>
|
||||
<input id="pw_playback_rate" type="checkbox" bind:checked={player_stack.playback_rate} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_timestamps">timestamps</label>
|
||||
<input id="pw_timestamps" type="checkbox" bind:checked={player_stack.timestamps} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_skip_back">skip_back</label>
|
||||
<input id="pw_skip_back" type="number" bind:value={player_stack.skip_back} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="pw_skip_forward">skip_forward</label>
|
||||
<input id="pw_skip_forward" type="number" bind:value={player_stack.skip_forward} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<PlayerStack class="max-w-sm" include={player_stack} />
|
||||
</PreviewComponent>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<div class="prose prose-lg prose-slate">
|
||||
<h2>Data</h2>
|
||||
<p>
|
||||
The following are the stores that are available to you based on the active episode being
|
||||
played in the above examples.
|
||||
</p>
|
||||
<PreviewData name="episode_audio" data={episode_audio}>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.subscribe()`}
|
||||
description="subscribes to the episode_audio data"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.load(\n\tsrc: string,\n\tdetails: { [key: string]: string | number | boolean] }\n)`}
|
||||
description="loads an episode into the player"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.load(src: string, details: { [key: string]: string | number | boolean] })`}
|
||||
description="loads an episode into the player"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.unload()`}
|
||||
description="unloads the episode from the player"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.seek(seconds: number, from: 'from-start' | 'from-end' = 'from-start')`}
|
||||
description="seeks to a specific part of the audio"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.skip(seconds: number, type: 'forward' | 'backward' = 'forward')`}
|
||||
description="seeks forward or backward relative to the current time"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.play(action: "toggle" | "set" = "set")`}
|
||||
description="plays the audio currently loaded in the player"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.pause(action: "toggle" | "set" = "set")`}
|
||||
description="pauses the audio currently loaded in the player"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.mute(action: "toggle" | "set" = "set")`}
|
||||
description="mutes the audio player"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`episode_audio.unmute(action: "toggle" | "set" = "set")`}
|
||||
description="unmutes the audio player"
|
||||
/>
|
||||
</PreviewData>
|
||||
<PreviewData name="episode_progress" data={episode_progress}>
|
||||
<PreviewDataCode
|
||||
code={`episode_progress.subscribe()`}
|
||||
description="subscribes to the episode_progress data"
|
||||
/>
|
||||
</PreviewData>
|
||||
<PreviewData name="user_preferences" data={user_preferences}>
|
||||
<PreviewDataCode
|
||||
code={`user_preferences.subscribe()`}
|
||||
description="subscribes to the user_preferences data"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`user_preferences.clear()`}
|
||||
description="clears all user preferences and resets them to default values"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`user_preferences.set.playback_rate(value: number)`}
|
||||
description="sets the users prefered playback_rate (speed)"
|
||||
/>
|
||||
<PreviewDataCode
|
||||
code={`user_preferences.set.volume(value: number)`}
|
||||
description="sets the users prefered volume"
|
||||
/>
|
||||
</PreviewData>
|
||||
<PreviewData name="user_progress" data={user_progress}>
|
||||
<PreviewDataCode
|
||||
code={`user_progress.subscribe()`}
|
||||
description="subscribes to the user_progress data"
|
||||
/>
|
||||
<button type="button" on:click={user_progress.clear}>user_progress.clear()</button>
|
||||
<PreviewDataCode
|
||||
code={`user_progress.clear()`}
|
||||
description="clears all user progress for all episodes"
|
||||
/>
|
||||
<button type="button" on:click={user_progress.clear}>user_progress.clear()</button>
|
||||
<PreviewDataCode
|
||||
code={`user_progress.get(src:string)`}
|
||||
description="gets the users progress (seconds) for a specific episode"
|
||||
/>
|
||||
<button type="button" on:click={user_progress.save}>user_progress.save()</button>
|
||||
<PreviewDataCode
|
||||
code={`user_progress.save()`}
|
||||
description="saves all user progress for all episodes they have interacted with in the current session"
|
||||
/>
|
||||
</PreviewData>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<h1>Demo</h1>
|
||||
<button type="button" on:click={user_preferences.clear}>Clear all preferences</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5>Load Audio</h5>
|
||||
<button type="button" on:click={() => episode_audio.load(sources['syntax'].src, sources['syntax'])}>
|
||||
Syntax
|
||||
</button>
|
||||
<button type="button" on:click={() => episode_audio.load(sources['knomii'].src, sources['knomii'])}
|
||||
>Knomii</button
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.unload()}>None</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h5>Custom audio controls</h5>
|
||||
|
||||
<h6>Play / Pause Actions</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.play()}>Play</button>
|
||||
<button type="button" on:click={() => episode_audio.pause()}>Pause</button>
|
||||
<button type="button" on:click={() => episode_audio.pause('toggle')}>Toggle</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h6>Audio Actions</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.mute()}>Mute</button>
|
||||
<button type="button" on:click={() => episode_audio.unmute()}>Unmute</button>
|
||||
<button type="button" on:click={() => episode_audio.mute('toggle')}>Toggle</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h6>Seeking</h6>
|
||||
|
||||
<button type="button" on:click={() => episode_audio.seek(30)}>Go to 30s from start </button>
|
||||
<button type="button" on:click={() => episode_audio.seek(30, 'from-end')}>Go to 30s from end</button
|
||||
>
|
||||
<button type="button" on:click={() => episode_audio.skip(10, 'forward')}>Skip 10s</button>
|
||||
<button type="button" on:click={() => episode_audio.skip(10, 'backward')}>Rewind 10s</button>
|
||||
|
||||
<hr />
|
||||
|
||||
<h6>Playback Rate</h6>
|
||||
|
||||
{#each [0.5, 1, 2, 3] as rate}
|
||||
<button type="button" on:click={() => user_preferences.set.playback_rate(rate)}>
|
||||
{rate}x
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<!-- <PodcastPlayer
|
||||
artwork={$episode_audio?.details?.artwork}
|
||||
title={$episode_audio?.details?.title || 'Podcast Name'}
|
||||
/> -->
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
export let name: string;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-stretch space-y-6">
|
||||
<div class="prose prose-lg prose-slate">
|
||||
<h3>{name}</h3>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>{name} options</legend>
|
||||
<slot name="options" />
|
||||
</fieldset>
|
||||
|
||||
<div class="w-full rounded-md border p-3">
|
||||
<div class="mx-auto w-max">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let code: string;
|
||||
export let description: string;
|
||||
</script>
|
||||
|
||||
<pre><code class="hljs language-js">{`//${description}\n\n${code}`}</code></pre>
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
export let name: string;
|
||||
export let data: unknown;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-stretch space-y-6">
|
||||
<h3>{name}</h3>
|
||||
|
||||
<h4>subscription: ${name}</h4>
|
||||
<pre><code class="hljs language-json">{JSON.stringify({ data }, null, 2)}</code></pre>
|
||||
|
||||
<h4>methods</h4>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Language: Svelte.js
|
||||
Requires: xml.js, javascript.js, css.js
|
||||
Author: Alexey Schebelev
|
||||
Description: Components of Svelte Framework
|
||||
Link: https://github.com/AlexxNB/highlightjs-svelte/blob/master/src/svelte.js
|
||||
*/
|
||||
|
||||
import type { HLJSApi } from 'highlight.js';
|
||||
|
||||
export function hljsDefineSvelte(hljs: HLJSApi) {
|
||||
return {
|
||||
subLanguage: 'xml',
|
||||
contains: [
|
||||
hljs.COMMENT('<!--', '-->', { relevance: 10 }),
|
||||
{
|
||||
begin: /^(\s*)(<script(\s*context="module")?>)/gm,
|
||||
end: /^(\s*)(<\/script>)/gm,
|
||||
subLanguage: 'javascript',
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
contains: [
|
||||
{
|
||||
begin: /^(\s*)(\$:)/gm,
|
||||
end: /(\s*)/gm,
|
||||
className: 'keyword',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
begin: /^(\s*)(<style.*>)/gm,
|
||||
end: /^(\s*)(<\/style>)/gm,
|
||||
subLanguage: 'css',
|
||||
excludeBegin: true,
|
||||
excludeEnd: true,
|
||||
},
|
||||
{
|
||||
begin: /\{/gm,
|
||||
end: /\}/gm,
|
||||
subLanguage: 'javascript',
|
||||
contains: [
|
||||
{
|
||||
begin: /[\{]/,
|
||||
end: /[\}]/,
|
||||
skip: true,
|
||||
},
|
||||
{
|
||||
begin: /([#:\/@])(if|else|each|await|then|catch|debug|html)/gm,
|
||||
className: 'keyword',
|
||||
relevance: 10,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
|
||||
import rehypeExternalLinks from 'rehype-external-links';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
// import rehypeAddClasses from 'rehype-add-classes';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkGithub from 'remark-github';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import remarkToc from 'remark-toc';
|
||||
import { unified } from 'unified';
|
||||
import { hljsDefineSvelte } from './hljs-svelte';
|
||||
|
||||
export async function use_markdown(markdown: string) {
|
||||
const pipeline = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkGfm)
|
||||
.use(remarkGithub, { repository: 'https://github.com/OllieJT/svelte-podcast.git' })
|
||||
.use(remarkToc, { tight: true, ordered: true, maxDepth: 4 })
|
||||
.use(remarkRehype)
|
||||
.use(rehypeStringify)
|
||||
.use(rehypeSanitize, {
|
||||
...defaultSchema,
|
||||
attributes: {
|
||||
...defaultSchema.attributes,
|
||||
|
||||
pre: [
|
||||
...(defaultSchema.attributes?.pre || []),
|
||||
[
|
||||
'className',
|
||||
'language-html',
|
||||
'language-typescript',
|
||||
'language-javascript',
|
||||
'language-svelte',
|
||||
'language-sh',
|
||||
],
|
||||
],
|
||||
|
||||
code: [
|
||||
...(defaultSchema.attributes?.code || []),
|
||||
[
|
||||
'className',
|
||||
'language-html',
|
||||
'language-typescript',
|
||||
'language-javascript',
|
||||
'language-svelte',
|
||||
'language-sh',
|
||||
],
|
||||
],
|
||||
|
||||
span: [
|
||||
...(defaultSchema.attributes?.span || []),
|
||||
[
|
||||
'className',
|
||||
'token',
|
||||
'comment',
|
||||
'prolog',
|
||||
'cdata',
|
||||
'punctuation',
|
||||
'builtin',
|
||||
'constant',
|
||||
'boolean',
|
||||
'number',
|
||||
'important',
|
||||
'atrule',
|
||||
'property',
|
||||
'keyword',
|
||||
'doctype',
|
||||
'operator',
|
||||
'inserted',
|
||||
'tag',
|
||||
'class-name',
|
||||
'symbol',
|
||||
'attr-name',
|
||||
'function',
|
||||
'deleted',
|
||||
'selector',
|
||||
'attr-value',
|
||||
'regex',
|
||||
'char',
|
||||
'string',
|
||||
'entity',
|
||||
'url',
|
||||
'variable',
|
||||
'bold',
|
||||
'italic',
|
||||
'namespace',
|
||||
],
|
||||
],
|
||||
},
|
||||
})
|
||||
// .use(rehypeAddClasses, {})
|
||||
.use(rehypeHighlight, { languages: { svelte: hljsDefineSvelte } })
|
||||
.use(rehypeSlug)
|
||||
.use(rehypeAutolinkHeadings, { behavior: 'wrap' })
|
||||
.use(rehypeExternalLinks, { target: '_blank', rel: ['nofollow', 'noopener', 'noreferrer'] });
|
||||
|
||||
const output = await pipeline.process(markdown);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -1,77 +0,0 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13_1060)">
|
||||
<g clip-path="url(#clip1_13_1060)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M460.943 364.935C457.828 370.79 459.637 378.159 465.324 381.571V381.571C471.006 384.98 478.403 383.151 481.544 377.317C533.752 280.363 518.924 156.846 437.058 74.9807C355.193 -6.88471 231.676 -21.7134 134.722 30.4946C128.888 33.6358 127.059 41.0332 130.468 46.7145V46.7145C133.88 52.4018 141.249 54.2111 147.104 51.0956C234.816 4.42589 346.181 18.0444 420.088 91.9512C493.994 165.858 507.613 277.223 460.943 364.935Z" fill="url(#paint0_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M413.862 350.694C419.543 354.103 426.951 352.275 430.007 346.396C468.403 272.551 456.612 179.387 394.631 117.407C332.651 55.4269 239.488 43.6349 165.642 82.0313C159.764 85.0878 157.935 92.495 161.344 98.1763V98.1763C164.756 103.864 172.122 105.662 178.033 102.654C242.623 69.7813 323.639 80.356 377.661 134.378C431.682 188.399 442.257 269.416 409.385 334.006C406.376 339.917 408.175 347.282 413.862 350.694V350.694Z" fill="url(#paint1_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M362.401 319.818C368.082 323.226 375.505 321.405 378.4 315.446C403.031 264.752 394.3 201.928 352.205 159.834C310.111 117.739 247.287 109.008 196.593 133.639C190.634 136.534 188.813 143.957 192.221 149.638V149.638C195.634 155.326 202.994 157.1 209.014 154.317C250.417 135.175 301.101 142.671 335.235 176.804C369.368 210.938 376.864 261.622 357.722 303.025C354.939 309.045 356.713 316.405 362.401 319.818V319.818Z" fill="url(#paint2_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M310.939 288.94C316.621 292.349 324.091 290.53 326.562 284.382C337.575 256.981 331.981 224.461 309.779 202.26C287.578 180.058 255.058 174.464 227.657 185.477C221.509 187.948 219.69 195.418 223.099 201.099V201.099C226.511 206.787 233.881 208.474 240.2 206.459C258.142 200.739 278.575 204.996 292.809 219.23C307.043 233.464 311.3 253.897 305.58 271.839C303.565 278.158 305.252 285.527 310.939 288.94V288.94Z" fill="url(#paint3_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M266.333 262.177C268.227 263.313 270.74 262.702 271.371 260.585C272.995 255.135 271.656 248.99 267.352 244.686C263.049 240.383 256.903 239.043 251.453 240.668C249.337 241.299 248.725 243.811 249.862 245.705V245.705C250.999 247.601 253.51 248.094 255.719 248.006C257.871 247.921 260.052 248.7 261.695 250.343C263.339 251.987 264.118 254.167 264.032 256.32C263.944 258.529 264.438 261.039 266.333 262.177V262.177Z" fill="url(#paint4_linear_13_1060)"/>
|
||||
</g>
|
||||
<g clip-path="url(#clip2_13_1060)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M51.1339 147.065C54.2494 141.21 52.4401 133.841 46.7528 130.429V130.429C41.0715 127.02 33.6741 128.849 30.5329 134.683C-21.6751 231.637 -6.8464 355.154 75.019 437.019C156.884 518.885 280.401 533.713 377.355 481.505C383.189 478.364 385.018 470.967 381.61 465.286V465.286C378.197 459.598 370.828 457.789 364.973 460.904C277.261 507.574 165.896 493.956 91.9895 420.049C18.0827 346.142 4.4642 234.777 51.1339 147.065Z" fill="url(#paint5_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M98.215 161.306C92.5337 157.897 85.1265 159.725 82.0701 165.604C43.6737 239.449 55.4656 332.613 117.446 394.593C179.426 456.573 272.589 468.365 346.435 429.969C352.313 426.912 354.142 419.505 350.733 413.824V413.824C347.321 408.136 339.955 406.338 334.044 409.346C269.454 442.219 188.438 431.644 134.416 377.622C80.3947 323.601 69.8201 242.584 102.692 177.994C105.701 172.083 103.902 164.718 98.215 161.306V161.306Z" fill="url(#paint6_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M149.676 192.182C143.995 188.774 136.573 190.595 133.677 196.554C109.046 247.248 117.777 310.072 159.872 352.166C201.966 394.261 264.79 402.992 315.484 378.361C321.443 375.466 323.264 368.043 319.856 362.362V362.362C316.443 356.674 309.083 354.9 303.063 357.683C261.66 376.825 210.976 369.329 176.842 335.196C142.709 301.062 135.213 250.378 154.355 208.975C157.139 202.955 155.364 195.595 149.676 192.182V192.182Z" fill="url(#paint7_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M201.138 223.06C195.456 219.651 187.986 221.47 185.516 227.618C174.502 255.019 180.097 287.539 202.298 309.74C224.499 331.942 257.019 337.536 284.42 326.523C290.568 324.052 292.387 316.582 288.978 310.901V310.901C285.566 305.213 278.196 303.526 271.877 305.541C253.935 311.261 233.502 307.004 219.268 292.77C205.035 278.536 200.778 258.103 206.498 240.161C208.512 233.842 206.825 226.473 201.138 223.06V223.06Z" fill="url(#paint8_linear_13_1060)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M245.744 249.823C243.85 248.687 241.337 249.298 240.706 251.415C239.082 256.865 240.421 263.01 244.725 267.314C249.029 271.617 255.174 272.957 260.624 271.332C262.74 270.701 263.352 268.189 262.216 266.295V266.295C261.078 264.399 258.567 263.906 256.358 263.994C254.206 264.079 252.025 263.3 250.382 261.657C248.738 260.013 247.959 257.833 248.045 255.68C248.133 253.471 247.639 250.961 245.744 249.823V249.823Z" fill="url(#paint9_linear_13_1060)"/>
|
||||
</g>
|
||||
<path d="M364.198 146.539C336.555 106.975 281.957 95.2476 242.484 120.397L173.171 164.584C163.803 170.474 155.767 178.255 149.577 187.428C143.387 196.602 139.18 206.966 137.225 217.858C133.918 236.202 136.826 255.125 145.486 271.632C139.551 280.633 135.502 290.745 133.586 301.356C131.611 312.466 131.875 323.859 134.362 334.866C136.849 345.873 141.508 356.273 148.067 365.456C175.71 405.025 230.31 416.755 269.778 391.597L339.107 347.411C348.473 341.52 356.506 333.738 362.693 324.565C368.88 315.392 373.085 305.028 375.037 294.137C378.349 275.794 375.449 256.87 366.798 240.36C372.729 231.358 376.774 221.248 378.689 210.639C380.666 199.529 380.404 188.135 377.918 177.128C375.431 166.121 370.771 155.721 364.211 146.539" fill="#FF3E00"/>
|
||||
<path d="M364.198 146.539C336.555 106.975 281.957 95.2476 242.484 120.397L173.171 164.584C163.803 170.474 155.767 178.255 149.577 187.428C143.387 196.602 139.18 206.966 137.225 217.858C133.918 236.202 136.826 255.125 145.486 271.632C139.551 280.633 135.502 290.745 133.586 301.356C131.611 312.466 131.875 323.859 134.362 334.866C136.849 345.873 141.508 356.273 148.067 365.456C175.71 405.025 230.31 416.755 269.778 391.597L339.107 347.411C348.473 341.52 356.506 333.738 362.693 324.565C368.88 315.392 373.085 305.028 375.037 294.137C378.349 275.794 375.449 256.87 366.798 240.36C372.729 231.358 376.774 221.248 378.689 210.639C380.666 199.529 380.404 188.135 377.918 177.128C375.431 166.121 370.771 155.721 364.211 146.539" fill="url(#paint10_linear_13_1060)"/>
|
||||
<path d="M235.751 369.18C224.841 372.016 213.324 371.436 202.754 367.518C192.184 363.599 183.071 356.532 176.646 347.27C172.702 341.749 169.901 335.496 168.405 328.878C166.91 322.26 166.751 315.41 167.938 308.729C168.34 306.536 168.893 304.374 169.594 302.257L170.899 298.27L174.453 300.878C182.653 306.907 191.822 311.491 201.564 314.433L204.14 315.215L203.903 317.788C203.591 321.444 204.582 325.093 206.702 328.088C208.637 330.88 211.382 333.01 214.567 334.192C217.752 335.373 221.223 335.548 224.511 334.693C226.015 334.293 227.449 333.665 228.764 332.832L298.089 288.64C299.786 287.572 301.241 286.163 302.363 284.501C303.485 282.839 304.248 280.962 304.604 278.989C304.96 276.975 304.91 274.911 304.46 272.916C304.009 270.921 303.165 269.036 301.978 267.371C300.043 264.579 297.297 262.448 294.113 261.266C290.928 260.084 287.457 259.908 284.169 260.761C282.665 261.161 281.231 261.788 279.917 262.621L253.462 279.486C249.108 282.252 244.357 284.337 239.374 285.669C228.464 288.505 216.947 287.925 206.377 284.007C195.807 280.089 186.694 273.021 180.269 263.759C176.324 258.238 173.522 251.985 172.026 245.367C170.53 238.749 170.371 231.899 171.558 225.218C172.737 218.668 175.269 212.437 178.993 206.922C182.717 201.407 187.551 196.729 193.186 193.189L262.52 149.006C266.873 146.236 271.623 144.148 276.607 142.815C287.517 139.979 299.035 140.56 309.605 144.478C320.175 148.396 329.287 155.463 335.713 164.725C339.657 170.246 342.459 176.499 343.955 183.117C345.45 189.735 345.61 196.585 344.423 203.266C344.018 205.458 343.464 207.621 342.765 209.738L341.46 213.725L337.908 211.123C329.709 205.093 320.54 200.509 310.797 197.567L308.219 196.786L308.458 194.213C308.77 190.555 307.78 186.904 305.662 183.905C303.727 181.113 300.981 178.983 297.796 177.801C294.612 176.62 291.141 176.445 287.853 177.3C286.349 177.7 284.915 178.327 283.601 179.161L214.256 223.355C212.56 224.422 211.105 225.831 209.984 227.493C208.863 229.154 208.101 231.031 207.747 233.003C207.39 235.017 207.437 237.082 207.887 239.078C208.337 241.073 209.181 242.958 210.368 244.624C212.303 247.416 215.048 249.546 218.233 250.729C221.417 251.911 224.888 252.087 228.176 251.235C229.68 250.834 231.114 250.206 232.429 249.374L258.881 232.517C263.233 229.746 267.984 227.659 272.969 226.329C283.878 223.493 295.395 224.073 305.965 227.991C316.534 231.91 325.647 238.977 332.072 248.239C336.016 253.76 338.819 260.012 340.314 266.631C341.81 273.249 341.969 280.099 340.782 286.78C339.606 293.329 337.077 299.562 333.356 305.078C329.635 310.595 324.804 315.275 319.172 318.819L249.839 362.99C245.486 365.76 240.736 367.848 235.751 369.18Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_13_1060" x1="193.598" y1="-29.0621" x2="541.101" y2="318.441" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF3E00"/>
|
||||
<stop offset="1" stop-color="#FF0697" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_13_1060" x1="208.5" y1="37.8468" x2="473.681" y2="303.458" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF0697"/>
|
||||
<stop offset="1" stop-color="#AC06FF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_13_1060" x1="223.306" y1="106.083" x2="405.956" y2="288.733" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#AC06FF"/>
|
||||
<stop offset="1" stop-color="#5A00CC" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_13_1060" x1="238.284" y1="173.491" x2="338.36" y2="273.72" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF0697"/>
|
||||
<stop offset="1" stop-color="#AC06FF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_13_1060" x1="252.468" y1="239.138" x2="272.901" y2="259.571" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF3E00"/>
|
||||
<stop offset="1" stop-color="#FF0697" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_13_1060" x1="318.479" y1="541.062" x2="-29.0238" y2="193.559" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF3E00"/>
|
||||
<stop offset="1" stop-color="#FF0697" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_13_1060" x1="303.577" y1="474.153" x2="38.3958" y2="208.542" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF0697"/>
|
||||
<stop offset="1" stop-color="#AC06FF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_13_1060" x1="288.771" y1="405.917" x2="106.121" y2="223.267" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#AC06FF"/>
|
||||
<stop offset="1" stop-color="#5A00CC" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_13_1060" x1="273.794" y1="338.509" x2="173.717" y2="238.28" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF0697"/>
|
||||
<stop offset="1" stop-color="#AC06FF" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_13_1060" x1="259.609" y1="272.862" x2="239.176" y2="252.429" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF3E00"/>
|
||||
<stop offset="1" stop-color="#FF0697" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint10_linear_13_1060" x1="379.74" y1="107.15" x2="132.278" y2="404.849" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF3E00"/>
|
||||
<stop offset="0.510417" stop-color="#FF0697"/>
|
||||
<stop offset="1" stop-color="#AC06FF"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_13_1060">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_13_1060">
|
||||
<rect width="512" height="512" fill="white" transform="translate(-106 256) rotate(-45)"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip2_13_1060">
|
||||
<rect width="512" height="512" fill="white" transform="translate(618.077 256) rotate(135)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 309 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 844 KiB After Width: | Height: | Size: 495 KiB |
@@ -1,16 +1,24 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
import preprocess from 'svelte-preprocess';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
extensions: ['.svelte'],
|
||||
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
preprocess: [vitePreprocess(), preprocess({ postcss: true })],
|
||||
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
alias: {
|
||||
'svelte-podcast': 'src/lib',
|
||||
$content: 'src/content/',
|
||||
},
|
||||
|
||||
paths: {
|
||||
relative: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const colors = require('tailwindcss/colors');
|
||||
|
||||
const config = {
|
||||
content: ['./src/**/*.{html,js,md,mdx,svx,svelte,ts}', './node_modules/svhighlight/**/*.svelte'],
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
mono: colors.slate,
|
||||
primary: colors.orange,
|
||||
},
|
||||
transitionTimingFunction: {
|
||||
smooth: 'cubic-bezier(0, 0, 0, 1.5)',
|
||||
whip: `cubic-bezier(0.8, 0.3, 0.15, 1.1)`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/line-clamp'),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||