mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-09 04:22:13 +00:00
update the layout
This commit is contained in:
9
src/lib/components/blog/breadcrumbs.svelte
Normal file
9
src/lib/components/blog/breadcrumbs.svelte
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let title: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="text-caption text-secondary flex gap-3 pt-8 pb-16">
|
||||||
|
<span>Blog</span>
|
||||||
|
<span>/</span>
|
||||||
|
<span class="text-primary line-clamp-1">{title}</span>
|
||||||
|
</div>
|
||||||
93
src/lib/components/blog/post-meta.svelte
Normal file
93
src/lib/components/blog/post-meta.svelte
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { socialSharingOptions, type SocialShareOption } from '$lib/constants';
|
||||||
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
import { handleCopy } from '$lib/utils/copy';
|
||||||
|
import { formatDate } from '$lib/utils/date';
|
||||||
|
import type { AuthorData } from '$routes/blog/content';
|
||||||
|
|
||||||
|
export let date: string = new Date().toISOString();
|
||||||
|
export let timeToRead: string = '0';
|
||||||
|
export let title: string = '';
|
||||||
|
export let description: string = '';
|
||||||
|
export let authorData: Partial<AuthorData> = {};
|
||||||
|
export let currentURL: string = '';
|
||||||
|
|
||||||
|
const getShareLink = (shareOption: SocialShareOption) => {
|
||||||
|
const blogPostUrl = encodeURI(currentURL);
|
||||||
|
return shareOption.link.replace('{TITLE}', title + '.').replace('{URL}', blogPostUrl);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="text-caption flex gap-2">
|
||||||
|
<time datetime={date}>{formatDate(date)}</time>
|
||||||
|
<span>•</span>
|
||||||
|
{#if timeToRead}
|
||||||
|
<span>{timeToRead} min</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<h1 class="text-title font-aeonik-pro text-primary">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
{#if description}
|
||||||
|
<p class="text-description text-secondary mt-2">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-smooth mb-8 flex justify-between border-b py-8">
|
||||||
|
{#if authorData}
|
||||||
|
<a href={authorData.href} class="flex items-center gap-2">
|
||||||
|
{#if authorData.avatar}
|
||||||
|
<img
|
||||||
|
class="size-11 rounded-full"
|
||||||
|
src={authorData.avatar}
|
||||||
|
alt={authorData.name}
|
||||||
|
loading="lazy"
|
||||||
|
width="44"
|
||||||
|
height="44"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h4 class="text-sub-body text-primary">
|
||||||
|
{authorData.name}
|
||||||
|
</h4>
|
||||||
|
<p class="text-caption">{authorData.role}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="mt-4 flex items-center gap-4">
|
||||||
|
<span class="text-micro text-secondary uppercase">SHARE</span>
|
||||||
|
|
||||||
|
<ul class="flex gap-2">
|
||||||
|
{#each socialSharingOptions as sharingOption}
|
||||||
|
<li
|
||||||
|
class="bg-smooth flex size-7 items-center justify-center rounded-lg text-white"
|
||||||
|
>
|
||||||
|
{#if sharingOption.type === 'link'}
|
||||||
|
<a
|
||||||
|
aria-label={sharingOption.label}
|
||||||
|
href={getShareLink(sharingOption)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener, noreferrer"
|
||||||
|
>
|
||||||
|
<span class={sharingOption.icon} aria-hidden="true" />
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
aria-label={sharingOption.label}
|
||||||
|
on:click={() => handleCopy(currentURL)}
|
||||||
|
>
|
||||||
|
<span class={sharingOption.icon} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
49
src/lib/components/blog/table-of-contents.svelte
Normal file
49
src/lib/components/blog/table-of-contents.svelte
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export const extractHeadings = () => {
|
||||||
|
const headings: Array<string> = [];
|
||||||
|
|
||||||
|
return headings;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
|
||||||
|
export let headings: Array<string> = [
|
||||||
|
'Accessibility in design systems',
|
||||||
|
'Use high color contrast',
|
||||||
|
'Not relying on color'
|
||||||
|
];
|
||||||
|
|
||||||
|
const backToTop = () => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
let activeIndex: number = 0;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="border-smooth col-span-3 ml-4 hidden border-l lg:block">
|
||||||
|
<span class="text-micro tracking-loose text-primary pl-8 uppercase">Table of Contents</span>
|
||||||
|
<div class="relative">
|
||||||
|
<ul class="border-smooth mt-11 ml-7 flex flex-col gap-7 border-b pb-11">
|
||||||
|
{#each headings as heading, i}
|
||||||
|
{@const isActive = i === 0}
|
||||||
|
<li class={classNames(isActive ? 'text-primary' : 'text-secondary', 'relative')}>
|
||||||
|
{heading}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
<div
|
||||||
|
class="bg-primary absolute top-0 -left-px h-6 w-px rounded-full transition duration-500 ease-in-out"
|
||||||
|
style:transform={`translateY(${activeIndex * 52}px)`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="text-primary group mt-8 flex cursor-pointer items-center gap-2 pl-7 transition-all active:scale-95"
|
||||||
|
on:click={backToTop}
|
||||||
|
>
|
||||||
|
<span class="web-icon-arrow-up transition group-hover:-translate-y-0.5" />
|
||||||
|
Back to Top
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
@@ -1,47 +1,11 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
import { tryCatch } from './try-catch';
|
||||||
|
|
||||||
async function securedCopy(value: string) {
|
export const copyToClipboard = async (value: string) => {
|
||||||
try {
|
const { data } = await tryCatch(navigator.clipboard.writeText(value));
|
||||||
await navigator.clipboard.writeText(value);
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return data;
|
||||||
}
|
};
|
||||||
|
|
||||||
function unsecuredCopy(value: string) {
|
|
||||||
const textArea = document.createElement('textarea');
|
|
||||||
textArea.value = value;
|
|
||||||
|
|
||||||
// Avoid scrolling to bottom
|
|
||||||
textArea.style.top = '0';
|
|
||||||
textArea.style.left = '0';
|
|
||||||
textArea.style.position = 'fixed';
|
|
||||||
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.focus();
|
|
||||||
textArea.select();
|
|
||||||
|
|
||||||
let success = true;
|
|
||||||
try {
|
|
||||||
document.execCommand('copy');
|
|
||||||
} catch {
|
|
||||||
success = false;
|
|
||||||
} finally {
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function copy(value: string) {
|
|
||||||
// securedCopy works only in HTTPS environment.
|
|
||||||
// unsecuredCopy works in HTTP and only runs if securedCopy fails.
|
|
||||||
const success = (await securedCopy(value)) || unsecuredCopy(value);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createCopy(value: string) {
|
export function createCopy(value: string) {
|
||||||
const copied = writable(false);
|
const copied = writable(false);
|
||||||
@@ -50,7 +14,7 @@ export function createCopy(value: string) {
|
|||||||
function handleCopy() {
|
function handleCopy() {
|
||||||
if (timeout) clearTimeout(timeout);
|
if (timeout) clearTimeout(timeout);
|
||||||
copied.set(true);
|
copied.set(true);
|
||||||
copy(value);
|
copyToClipboard(value);
|
||||||
timeout = setTimeout(() => copied.set(false), 1000);
|
timeout = setTimeout(() => copied.set(false), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,3 +23,23 @@ export function createCopy(value: string) {
|
|||||||
copy: handleCopy
|
copy: handleCopy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const handleCopy = (value: string, duration: number = 1000) => {
|
||||||
|
const copied = writable<boolean>(false);
|
||||||
|
let timeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
copied.set(true);
|
||||||
|
copyToClipboard(value);
|
||||||
|
timeout = setTimeout(() => copied.set(false), duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
copied,
|
||||||
|
copy
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// backward compatibility
|
||||||
|
export { copyToClipboard as copy };
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
export const formatDate = (date: string | Date | number): string => {
|
export const formatDate = (date: string | Date | number): string => {
|
||||||
const dt = new Date(date);
|
const dt = new Date(date);
|
||||||
const month = dt.toLocaleString('en-US', { month: 'short' });
|
|
||||||
const day = dt.getDate();
|
return format(dt, 'MMMM d, yyyy');
|
||||||
const year = dt.getFullYear();
|
|
||||||
return `${month} ${day}, ${year}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addDays = (date: Date, days: number) => {
|
export const addDays = (date: Date, days: number) => {
|
||||||
|
|||||||
20
src/lib/utils/try-catch.ts
Normal file
20
src/lib/utils/try-catch.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
type Success<T> = {
|
||||||
|
data: T;
|
||||||
|
error: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Failure<E> = {
|
||||||
|
data: null;
|
||||||
|
error: E;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Result<T, E = Error> = Success<T> | Failure<E>;
|
||||||
|
|
||||||
|
export const tryCatch = async <T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> => {
|
||||||
|
try {
|
||||||
|
const data = await promise;
|
||||||
|
return { data, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
return { data: null, error: error as E };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Media } from '$lib/UI';
|
import { Media } from '$lib/UI';
|
||||||
import { scroll } from '$lib/animations';
|
|
||||||
import { Article, FooterNav, MainFooter, Newsletter, Tooltip } from '$lib/components';
|
import { Article, FooterNav, MainFooter, Newsletter, Tooltip } from '$lib/components';
|
||||||
import { Main } from '$lib/layouts';
|
import { Main } from '$lib/layouts';
|
||||||
import { formatDate } from '$lib/utils/date';
|
import { formatDate } from '$lib/utils/date';
|
||||||
@@ -13,10 +12,11 @@
|
|||||||
import type { AuthorData, PostsData } from '$routes/blog/content';
|
import type { AuthorData, PostsData } from '$routes/blog/content';
|
||||||
import { TITLE_SUFFIX } from '$routes/titles';
|
import { TITLE_SUFFIX } from '$routes/titles';
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { type SocialShareOption, socialSharingOptions } from '$lib/constants';
|
import { page } from '$app/state';
|
||||||
import { copy } from '$lib/utils/copy';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
import CTA from '$lib/components/BlogCta.svelte';
|
import CTA from '$lib/components/BlogCta.svelte';
|
||||||
|
import PostMeta from '$lib/components/blog/post-meta.svelte';
|
||||||
|
import Breadcrumbs from '$lib/components/blog/breadcrumbs.svelte';
|
||||||
|
import TableOfContents from '$lib/components/blog/table-of-contents.svelte';
|
||||||
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
export let description: string;
|
export let description: string;
|
||||||
@@ -40,31 +40,7 @@
|
|||||||
|
|
||||||
callToAction ??= true;
|
callToAction ??= true;
|
||||||
|
|
||||||
let readPercentage = 0;
|
const currentURL = `https://appwrite.io${page.url.pathname}`;
|
||||||
const currentURL = `https://appwrite.io${$page.url.pathname}`;
|
|
||||||
|
|
||||||
enum CopyStatus {
|
|
||||||
Copy = 'Copy URL',
|
|
||||||
Copied = 'Copied'
|
|
||||||
}
|
|
||||||
|
|
||||||
let copyText = CopyStatus.Copy;
|
|
||||||
|
|
||||||
async function handleCopy() {
|
|
||||||
const blogPostUrl = encodeURI(currentURL);
|
|
||||||
|
|
||||||
await copy(blogPostUrl);
|
|
||||||
|
|
||||||
copyText = CopyStatus.Copied;
|
|
||||||
setTimeout(() => {
|
|
||||||
copyText = CopyStatus.Copy;
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getShareLink(shareOption: SocialShareOption): string {
|
|
||||||
const blogPostUrl = encodeURI(currentURL);
|
|
||||||
return shareOption.link.replace('{TITLE}', title + '.').replace('{URL}', blogPostUrl);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -109,150 +85,40 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Main>
|
<Main>
|
||||||
<div
|
<div class="py-10">
|
||||||
class="web-big-padding-section"
|
<div class="container">
|
||||||
use:scroll
|
<Breadcrumbs {title} />
|
||||||
on:web-scroll={(e) => {
|
<article class="grid grid-cols-1 gap-4 lg:grid-cols-12">
|
||||||
readPercentage = e.detail.percentage;
|
<div class="lg:col-span-9">
|
||||||
}}
|
<PostMeta {authorData} {title} {timeToRead} {currentURL} {date} {description} />
|
||||||
>
|
{#if cover}
|
||||||
<div class="web-big-padding-section">
|
<div>
|
||||||
<div class="py-10">
|
<Media class="block aspect-video" src={cover} />
|
||||||
<div class="web-big-padding-section-level-2">
|
</div>
|
||||||
<div class="container max-w-[42.5rem]">
|
|
||||||
<article class="web-main-article">
|
|
||||||
<header class="web-main-article-header">
|
|
||||||
<a
|
|
||||||
class="web-link is-secondary web-u-color-text-secondary items-baseline"
|
|
||||||
href="/blog"
|
|
||||||
>
|
|
||||||
<span class="web-icon-chevron-left" aria-hidden="true" />
|
|
||||||
<span>Back to blog</span>
|
|
||||||
</a>
|
|
||||||
<ul class="web-metadata text-caption">
|
|
||||||
<li>
|
|
||||||
<time datetime={date}>{formatDate(date)}</time>
|
|
||||||
</li>
|
|
||||||
{#if timeToRead}
|
|
||||||
<li>{timeToRead} min</li>
|
|
||||||
{/if}
|
|
||||||
</ul>
|
|
||||||
<h1 class="text-title font-aeonik-pro text-primary">
|
|
||||||
{title}
|
|
||||||
</h1>
|
|
||||||
{#if description}
|
|
||||||
<p class="text-description mt-2">
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
{#if authorData}
|
|
||||||
<div class="web-author mt-4">
|
|
||||||
<a href={authorData.href} class="flex items-center gap-2">
|
|
||||||
{#if authorData.avatar}
|
|
||||||
<img
|
|
||||||
class="web-author-image"
|
|
||||||
src={authorData.avatar}
|
|
||||||
alt={authorData.name}
|
|
||||||
loading="lazy"
|
|
||||||
width="44"
|
|
||||||
height="44"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<h4 class="text-sub-body text-primary">
|
|
||||||
{authorData.name}
|
|
||||||
</h4>
|
|
||||||
<p class="text-caption">{authorData.role}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="share-post-section mt-4 flex items-center gap-4">
|
|
||||||
<span class="text-micro pr-2 uppercase" style:color="#adadb0">
|
|
||||||
SHARE
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<ul class="flex gap-2">
|
|
||||||
{#each socialSharingOptions as sharingOption}
|
|
||||||
<li class="share-list-item">
|
|
||||||
<Tooltip
|
|
||||||
placement="bottom"
|
|
||||||
disableHoverableContent={true}
|
|
||||||
>
|
|
||||||
{#if sharingOption.type === 'link'}
|
|
||||||
<a
|
|
||||||
class="web-icon-button"
|
|
||||||
aria-label={sharingOption.label}
|
|
||||||
href={getShareLink(sharingOption)}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener, noreferrer"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class={sharingOption.icon}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
class="web-icon-button"
|
|
||||||
aria-label={sharingOption.label}
|
|
||||||
on:click={() => handleCopy()}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class={sharingOption.icon}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<svelte:fragment slot="tooltip">
|
|
||||||
{sharingOption.type === 'copy'
|
|
||||||
? copyText
|
|
||||||
: `Share on ${sharingOption.label}`}
|
|
||||||
</svelte:fragment>
|
|
||||||
</Tooltip>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{#if cover}
|
|
||||||
<div class="web-media-container">
|
|
||||||
<Media class="web-u-media-ratio-16-9 block" src={cover} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="web-article-content mt-8">
|
|
||||||
{#if lastUpdated}
|
|
||||||
<span class="text-body last-updated-text font-medium">
|
|
||||||
Updated:
|
|
||||||
<time dateTime={lastUpdated}>
|
|
||||||
{formatDate(lastUpdated)}
|
|
||||||
</time>
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<!-- {#if categories?.length}
|
|
||||||
<div class="flex gap-4">
|
|
||||||
{#each categories as cat}
|
|
||||||
<a href={cat.href} class="web-tag">{cat.name}</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
</div>
|
|
||||||
{#if typeof callToAction === 'boolean'}
|
|
||||||
<CTA />
|
|
||||||
{:else if typeof callToAction === 'object'}
|
|
||||||
<CTA {...callToAction} />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="text-secondary mt-8 flex flex-col gap-8">
|
||||||
|
{#if lastUpdated}
|
||||||
|
<span class="text-body last-updated-text font-medium">
|
||||||
|
Updated:
|
||||||
|
<time dateTime={lastUpdated}>
|
||||||
|
{formatDate(lastUpdated)}
|
||||||
|
</time>
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<TableOfContents />
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
{#if typeof callToAction === 'boolean'}
|
||||||
|
<CTA />
|
||||||
|
{:else if typeof callToAction === 'object'}
|
||||||
|
<CTA {...callToAction} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="web-u-sep-block-start pt-10">
|
<div class="web-u-sep-block-start pt-10">
|
||||||
@@ -288,43 +154,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Main>
|
</Main>
|
||||||
|
|
||||||
<div class="progress-bar" style:--percentage="{readPercentage * 100}%" />
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.progress-bar {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
height: 2px;
|
|
||||||
width: var(--percentage);
|
|
||||||
background: hsl(var(--web-color-accent));
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.web-main-article-header {
|
|
||||||
padding-block-end: 0;
|
|
||||||
border-block-end: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.share-post-section {
|
|
||||||
padding: 16px 0;
|
|
||||||
border-block-end: solid 0.0625rem hsl(var(--web-color-border));
|
|
||||||
border-block-start: solid 0.0625rem hsl(var(--web-color-border));
|
|
||||||
}
|
|
||||||
|
|
||||||
.web-icon-button {
|
|
||||||
.web-icon-x {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.web-icon-copy {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-updated-text {
|
|
||||||
color: var(--primary, #e4e4e7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
const tag = `h${level + 1}`;
|
const tag = `h${level + 1}`;
|
||||||
const ctx = hasContext('headings') ? getContext<LayoutContext>('headings') : undefined;
|
const ctx = hasContext('headings') ? getContext<LayoutContext>('headings') : undefined;
|
||||||
const classList: Record<typeof level, string> = {
|
const classList: Record<typeof level, string> = {
|
||||||
1: 'text-label mb-4',
|
1: 'text-description mb-4',
|
||||||
2: 'text-description mb-4',
|
2: 'text-description text-primary mb-4',
|
||||||
3: 'text-body font-medium mb-4',
|
3: 'text-body font-medium mb-4',
|
||||||
4: 'text-sub-body font-medium'
|
4: 'text-sub-body font-medium'
|
||||||
};
|
};
|
||||||
@@ -24,6 +24,8 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(element.textContent);
|
||||||
|
|
||||||
$ctx = {
|
$ctx = {
|
||||||
...$ctx,
|
...$ctx,
|
||||||
[id]: {
|
[id]: {
|
||||||
@@ -40,6 +42,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const observer = new IntersectionObserver(callback, {
|
const observer = new IntersectionObserver(callback, {
|
||||||
root: null,
|
root: null,
|
||||||
threshold: 1
|
threshold: 1
|
||||||
@@ -60,7 +63,7 @@
|
|||||||
bind:this={element}
|
bind:this={element}
|
||||||
class:web-snap-location={id && !inReferences}
|
class:web-snap-location={id && !inReferences}
|
||||||
class:web-snap-location-references={id && inReferences}
|
class:web-snap-location-references={id && inReferences}
|
||||||
class="{headingClass} text-primary"
|
class="{headingClass} text-primary font-medium"
|
||||||
>
|
>
|
||||||
<a href={`#${id}`} class=""><slot /></a>
|
<a href={`#${id}`} class=""><slot /></a>
|
||||||
</svelte:element>
|
</svelte:element>
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
<svelte:element
|
<svelte:element
|
||||||
this={tag}
|
this={tag}
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
class="{headingClass} text-primary"
|
class="{headingClass} text-primary font-medium"
|
||||||
class:in-policy={inPolicy}
|
class:in-policy={inPolicy}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
Reference in New Issue
Block a user