97 changed files with 5218 additions and 3593 deletions
@ -1,17 +1,17 @@
@@ -1,17 +1,17 @@
|
||||
version: 39 |
||||
jobs: |
||||
- name: Github Push |
||||
steps: |
||||
- !PushRepository |
||||
name: gc-alexandria |
||||
remoteUrl: https://github.com/ShadowySupercode/gc-alexandria |
||||
passwordSecret: github_access_token |
||||
force: false |
||||
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL |
||||
triggers: |
||||
- !BranchUpdateTrigger {} |
||||
- !TagCreateTrigger {} |
||||
retryCondition: never |
||||
maxRetries: 3 |
||||
retryDelay: 30 |
||||
timeout: 14400 |
||||
- name: Github Push |
||||
steps: |
||||
- !PushRepository |
||||
name: gc-alexandria |
||||
remoteUrl: https://github.com/ShadowySupercode/gc-alexandria |
||||
passwordSecret: github_access_token |
||||
force: false |
||||
condition: ALL_PREVIOUS_STEPS_WERE_SUCCESSFUL |
||||
triggers: |
||||
- !BranchUpdateTrigger {} |
||||
- !TagCreateTrigger {} |
||||
retryCondition: never |
||||
maxRetries: 3 |
||||
retryDelay: 30 |
||||
timeout: 14400 |
||||
|
||||
@ -1,3 +1,3 @@
@@ -1,3 +1,3 @@
|
||||
{ |
||||
"plugins":["prettier-plugin-svelte"] |
||||
"plugins": ["prettier-plugin-svelte"] |
||||
} |
||||
|
||||
@ -1,8 +1,8 @@
@@ -1,8 +1,8 @@
|
||||
identifier: Alexandria |
||||
maintainers: |
||||
- npub1m3xdppkd0njmrqe2ma8a6ys39zvgp5k8u22mev8xsnqp4nh80srqhqa5sf |
||||
- npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z |
||||
- npub1wqfzz2p880wq0tumuae9lfwyhs8uz35xd0kr34zrvrwyh3kvrzuskcqsyn |
||||
- npub1m3xdppkd0njmrqe2ma8a6ys39zvgp5k8u22mev8xsnqp4nh80srqhqa5sf |
||||
- npub1l5sga6xg72phsz5422ykujprejwud075ggrr3z2hwyrfgr7eylqstegx9z |
||||
- npub1wqfzz2p880wq0tumuae9lfwyhs8uz35xd0kr34zrvrwyh3kvrzuskcqsyn |
||||
relays: |
||||
- wss://theforest.nostr1.com |
||||
- wss://thecitadel.nostr1.com |
||||
- wss://theforest.nostr1.com |
||||
- wss://thecitadel.nostr1.com |
||||
|
||||
@ -1,24 +1,28 @@
@@ -1,24 +1,28 @@
|
||||
<script lang="ts"> |
||||
import type { NDKEvent } from '@nostr-dev-kit/ndk'; |
||||
import {nip19} from 'nostr-tools'; |
||||
export let notes: NDKEvent[] = []; |
||||
// check if notes is empty |
||||
if (notes.length === 0) { |
||||
console.debug('notes is empty'); |
||||
} |
||||
import type { NDKEvent } from "@nostr-dev-kit/ndk"; |
||||
import { nip19 } from "nostr-tools"; |
||||
export let notes: NDKEvent[] = []; |
||||
// check if notes is empty |
||||
if (notes.length === 0) { |
||||
console.debug("notes is empty"); |
||||
} |
||||
</script> |
||||
|
||||
<div class="toc"> |
||||
<h2>Table of contents</h2> |
||||
<ul> |
||||
{#each notes as note} |
||||
<li><a href="#{nip19.noteEncode(note.id)}">{note.getMatchingTags('title')[0][1]}</a></li> |
||||
{/each} |
||||
</ul> |
||||
<h2>Table of contents</h2> |
||||
<ul> |
||||
{#each notes as note} |
||||
<li> |
||||
<a href="#{nip19.noteEncode(note.id)}" |
||||
>{note.getMatchingTags("title")[0][1]}</a |
||||
> |
||||
</li> |
||||
{/each} |
||||
</ul> |
||||
</div> |
||||
|
||||
<style> |
||||
.toc h2 { |
||||
text-align: center; |
||||
} |
||||
.toc h2 { |
||||
text-align: center; |
||||
} |
||||
</style> |
||||
|
||||
@ -1,99 +1,111 @@
@@ -1,99 +1,111 @@
|
||||
<script lang='ts'> |
||||
import CopyToClipboard from "$components/util/CopyToClipboard.svelte"; |
||||
import { logout, ndkInstance } from '$lib/ndk'; |
||||
import { ArrowRightToBracketOutline, UserOutline, FileSearchOutline } from "flowbite-svelte-icons"; |
||||
import { Avatar, Popover } from "flowbite-svelte"; |
||||
import type { NDKUserProfile } from "@nostr-dev-kit/ndk"; |
||||
<script lang="ts"> |
||||
import CopyToClipboard from "$components/util/CopyToClipboard.svelte"; |
||||
import { logout, ndkInstance } from "$lib/ndk"; |
||||
import { |
||||
ArrowRightToBracketOutline, |
||||
UserOutline, |
||||
FileSearchOutline, |
||||
} from "flowbite-svelte-icons"; |
||||
import { Avatar, Popover } from "flowbite-svelte"; |
||||
import type { NDKUserProfile } from "@nostr-dev-kit/ndk"; |
||||
|
||||
const externalProfileDestination = './events?id=' |
||||
const externalProfileDestination = "./events?id="; |
||||
|
||||
let { pubkey, isNav = false } = $props(); |
||||
let { pubkey, isNav = false } = $props(); |
||||
|
||||
let profile = $state<NDKUserProfile | null>(null); |
||||
let pfp = $derived(profile?.image); |
||||
let username = $derived(profile?.name); |
||||
let tag = $derived(profile?.name); |
||||
let npub = $state<string | undefined >(undefined); |
||||
let profile = $state<NDKUserProfile | null>(null); |
||||
let pfp = $derived(profile?.image); |
||||
let username = $derived(profile?.name); |
||||
let tag = $derived(profile?.name); |
||||
let npub = $state<string | undefined>(undefined); |
||||
|
||||
$effect(() => { |
||||
const user = $ndkInstance |
||||
.getUser({ pubkey: pubkey ?? undefined }); |
||||
$effect(() => { |
||||
const user = $ndkInstance.getUser({ pubkey: pubkey ?? undefined }); |
||||
|
||||
npub = user.npub; |
||||
npub = user.npub; |
||||
|
||||
user.fetchProfile() |
||||
.then(userProfile => { |
||||
user.fetchProfile().then((userProfile) => { |
||||
profile = userProfile; |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
async function handleSignOutClick() { |
||||
logout($ndkInstance.activeUser!); |
||||
profile = null; |
||||
} |
||||
async function handleSignOutClick() { |
||||
logout($ndkInstance.activeUser!); |
||||
profile = null; |
||||
} |
||||
|
||||
function shortenNpub(long: string|undefined) { |
||||
if (!long) return ''; |
||||
return long.slice(0, 8) + '…' + long.slice(-4); |
||||
} |
||||
function shortenNpub(long: string | undefined) { |
||||
if (!long) return ""; |
||||
return long.slice(0, 8) + "…" + long.slice(-4); |
||||
} |
||||
</script> |
||||
|
||||
<div class="relative"> |
||||
{#if profile} |
||||
<div class="group"> |
||||
<Avatar |
||||
rounded |
||||
class='h-6 w-6 cursor-pointer' |
||||
src={pfp} |
||||
alt={username} |
||||
id="profile-avatar" |
||||
/> |
||||
{#key username || tag} |
||||
<Popover |
||||
placement="bottom" |
||||
triggeredBy="#profile-avatar" |
||||
class='popover-leather w-[180px]' |
||||
trigger='hover' |
||||
> |
||||
<div class='flex flex-row justify-between space-x-4'> |
||||
<div class='flex flex-col'> |
||||
{#if username} |
||||
<h3 class='text-lg font-bold'>{username}</h3> |
||||
{#if isNav}<h4 class='text-base'>@{tag}</h4>{/if} |
||||
{/if} |
||||
<ul class="space-y-2 mt-2"> |
||||
<li> |
||||
<CopyToClipboard displayText={shortenNpub(npub)} copyText={npub} /> |
||||
</li> |
||||
<li> |
||||
<a class='hover:text-primary-400 dark:hover:text-primary-500 text-nowrap mt-3 m-0' href='{externalProfileDestination}{npub}'> |
||||
<UserOutline class='mr-1 !h-6 !w-6 inline !fill-none dark:!fill-none' /><span class='underline'>View profile</span> |
||||
</a> |
||||
</li> |
||||
{#if isNav} |
||||
<div class="group"> |
||||
<Avatar |
||||
rounded |
||||
class="h-6 w-6 cursor-pointer" |
||||
src={pfp} |
||||
alt={username} |
||||
id="profile-avatar" |
||||
/> |
||||
{#key username || tag} |
||||
<Popover |
||||
placement="bottom" |
||||
triggeredBy="#profile-avatar" |
||||
class="popover-leather w-[180px]" |
||||
trigger="hover" |
||||
> |
||||
<div class="flex flex-row justify-between space-x-4"> |
||||
<div class="flex flex-col"> |
||||
{#if username} |
||||
<h3 class="text-lg font-bold">{username}</h3> |
||||
{#if isNav}<h4 class="text-base">@{tag}</h4>{/if} |
||||
{/if} |
||||
<ul class="space-y-2 mt-2"> |
||||
<li> |
||||
<button |
||||
id='sign-out-button' |
||||
class='btn-leather text-nowrap mt-3 flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500' |
||||
onclick={handleSignOutClick} |
||||
<CopyToClipboard |
||||
displayText={shortenNpub(npub)} |
||||
copyText={npub} |
||||
/> |
||||
</li> |
||||
<li> |
||||
<a |
||||
class="hover:text-primary-400 dark:hover:text-primary-500 text-nowrap mt-3 m-0" |
||||
href="{externalProfileDestination}{npub}" |
||||
> |
||||
<ArrowRightToBracketOutline class='mr-1 !h-6 !w-6 inline !fill-none dark:!fill-none' /> Sign out |
||||
</button> |
||||
<UserOutline |
||||
class="mr-1 !h-6 !w-6 inline !fill-none dark:!fill-none" |
||||
/><span class="underline">View profile</span> |
||||
</a> |
||||
</li> |
||||
{:else} |
||||
<!-- li> |
||||
{#if isNav} |
||||
<li> |
||||
<button |
||||
id="sign-out-button" |
||||
class="btn-leather text-nowrap mt-3 flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500" |
||||
onclick={handleSignOutClick} |
||||
> |
||||
<ArrowRightToBracketOutline |
||||
class="mr-1 !h-6 !w-6 inline !fill-none dark:!fill-none" |
||||
/> Sign out |
||||
</button> |
||||
</li> |
||||
{:else} |
||||
<!-- li> |
||||
<button |
||||
class='btn-leather text-nowrap mt-3 flex self-stretch align-middle hover:text-primary-400 dark:hover:text-primary-500' |
||||
> |
||||
<FileSearchOutline class='mr-1 !h-6 inline !fill-none dark:!fill-none' /> More content |
||||
</button> |
||||
</li --> |
||||
{/if} |
||||
</ul> |
||||
{/if} |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</Popover> |
||||
{/key} |
||||
</div> |
||||
</Popover> |
||||
{/key} |
||||
</div> |
||||
{/if} |
||||
</div> |
||||
|
||||
@ -1,40 +1,42 @@
@@ -1,40 +1,42 @@
|
||||
export const wikiKind = 30818; |
||||
export const indexKind = 30040; |
||||
export const zettelKinds = [ 30041, 30818 ]; |
||||
export const communityRelay = [ 'wss://theforest.nostr1.com' ]; |
||||
export const zettelKinds = [30041, 30818]; |
||||
export const communityRelay = ["wss://theforest.nostr1.com"]; |
||||
export const standardRelays = [ |
||||
'wss://thecitadel.nostr1.com',
|
||||
'wss://theforest.nostr1.com', |
||||
'wss://profiles.nostr1.com', |
||||
'wss://gitcitadel.nostr1.com', |
||||
"wss://thecitadel.nostr1.com", |
||||
"wss://theforest.nostr1.com", |
||||
"wss://profiles.nostr1.com", |
||||
"wss://gitcitadel.nostr1.com", |
||||
//'wss://thecitadel.gitcitadel.eu',
|
||||
//'wss://theforest.gitcitadel.eu',
|
||||
]; |
||||
|
||||
// Non-auth relays for anonymous users
|
||||
export const anonymousRelays = [ |
||||
'wss://thecitadel.nostr1.com',
|
||||
'wss://theforest.nostr1.com', |
||||
'wss://profiles.nostr1.com', |
||||
'wss://freelay.sovbit.host', |
||||
"wss://thecitadel.nostr1.com", |
||||
"wss://theforest.nostr1.com", |
||||
"wss://profiles.nostr1.com", |
||||
"wss://freelay.sovbit.host", |
||||
]; |
||||
export const fallbackRelays = [ |
||||
'wss://purplepag.es', |
||||
'wss://indexer.coracle.social', |
||||
'wss://relay.noswhere.com', |
||||
'wss://aggr.nostr.land', |
||||
'wss://nostr.wine', |
||||
'wss://nostr.land', |
||||
'wss://nostr.sovbit.host', |
||||
'wss://freelay.sovbit.host', |
||||
'wss://nostr21.com', |
||||
'wss://greensoul.space', |
||||
"wss://purplepag.es", |
||||
"wss://indexer.coracle.social", |
||||
"wss://relay.noswhere.com", |
||||
"wss://aggr.nostr.land", |
||||
"wss://nostr.wine", |
||||
"wss://nostr.land", |
||||
"wss://nostr.sovbit.host", |
||||
"wss://freelay.sovbit.host", |
||||
"wss://nostr21.com", |
||||
"wss://greensoul.space", |
||||
"wss://relay.damus.io", |
||||
"wss://relay.nostr.band", |
||||
]; |
||||
|
||||
export enum FeedType { |
||||
StandardRelays = 'standard', |
||||
UserRelays = 'user', |
||||
StandardRelays = "standard", |
||||
UserRelays = "user", |
||||
} |
||||
|
||||
export const loginStorageKey = 'alexandria/login/pubkey'; |
||||
export const feedTypeStorageKey = 'alexandria/feed/type'; |
||||
export const loginStorageKey = "alexandria/login/pubkey"; |
||||
export const feedTypeStorageKey = "alexandria/feed/type"; |
||||
|
||||
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
import { writable } from 'svelte/store'; |
||||
import { writable } from "svelte/store"; |
||||
|
||||
// Initialize with empty array, will be populated from user preferences
|
||||
export const userRelays = writable<string[]>([]); |
||||
@ -1,13 +1,23 @@
@@ -1,13 +1,23 @@
|
||||
<script lang="ts"> |
||||
import { goto } from '$app/navigation'; |
||||
import { Button, P } from 'flowbite-svelte'; |
||||
import { goto } from "$app/navigation"; |
||||
import { Button, P } from "flowbite-svelte"; |
||||
</script> |
||||
|
||||
<div class="leather flex flex-col items-center justify-center min-h-screen text-center px-4"> |
||||
<div |
||||
class="leather flex flex-col items-center justify-center min-h-screen text-center px-4" |
||||
> |
||||
<h1 class="h-leather mb-4">404 - Page Not Found</h1> |
||||
<P class="note-leather mb-6">The page you are looking for does not exist or has been moved.</P> |
||||
<P class="note-leather mb-6" |
||||
>The page you are looking for does not exist or has been moved.</P |
||||
> |
||||
<div class="flex space-x-4"> |
||||
<Button class="btn-leather !w-fit" on:click={() => goto('/')}>Return to Home</Button> |
||||
<Button class="btn-leather !w-fit" outline on:click={() => window.history.back()}>Go Back</Button> |
||||
<Button class="btn-leather !w-fit" on:click={() => goto("/")} |
||||
>Return to Home</Button |
||||
> |
||||
<Button |
||||
class="btn-leather !w-fit" |
||||
outline |
||||
on:click={() => window.history.back()}>Go Back</Button |
||||
> |
||||
</div> |
||||
</div> |
||||
|
||||
@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
|
||||
@layer components { |
||||
canvas.qr-code { |
||||
@apply block mx-auto my-4; |
||||
} |
||||
canvas.qr-code { |
||||
@apply block mx-auto my-4; |
||||
} |
||||
} |
||||
@ -1,288 +1,288 @@
@@ -1,288 +1,288 @@
|
||||
@layer components { |
||||
/* AsciiDoc content */ |
||||
.publication-leather p a { |
||||
@apply underline hover:text-primary-600 dark:hover:text-primary-400; |
||||
} |
||||
|
||||
.publication-leather section p { |
||||
@apply w-full; |
||||
} |
||||
|
||||
.publication-leather section p table { |
||||
@apply w-full table-fixed space-x-2 space-y-2; |
||||
} |
||||
|
||||
.publication-leather section p table td { |
||||
@apply p-2; |
||||
} |
||||
|
||||
.publication-leather section p table td .content:has(> .imageblock) { |
||||
@apply flex flex-col items-center; |
||||
} |
||||
|
||||
.publication-leather .imageblock { |
||||
@apply flex flex-col space-y-2; |
||||
} |
||||
|
||||
.publication-leather .imageblock .content { |
||||
@apply flex justify-center; |
||||
} |
||||
.publication-leather .imageblock .title { |
||||
@apply text-center; |
||||
} |
||||
|
||||
.publication-leather .imageblock.left .content { |
||||
@apply justify-start; |
||||
} |
||||
.publication-leather .imageblock.left .title { |
||||
@apply text-left; |
||||
} |
||||
|
||||
.publication-leather .imageblock.right .content { |
||||
@apply justify-end; |
||||
} |
||||
.publication-leather .imageblock.right .title { |
||||
@apply text-right; |
||||
} |
||||
|
||||
.publication-leather section p table td .literalblock { |
||||
@apply my-2 p-2 border rounded border-gray-400 dark:border-gray-600; |
||||
} |
||||
|
||||
.publication-leather .literalblock pre { |
||||
@apply p-3 text-wrap break-words; |
||||
} |
||||
|
||||
.publication-leather .listingblock pre { |
||||
@apply overflow-x-auto; |
||||
} |
||||
|
||||
/* lists */ |
||||
.publication-leather .ulist ul { |
||||
@apply space-y-1 list-disc list-inside; |
||||
} |
||||
|
||||
.publication-leather .olist ol { |
||||
@apply space-y-1 list-inside; |
||||
} |
||||
|
||||
.publication-leather ol.arabic { |
||||
@apply list-decimal; |
||||
} |
||||
|
||||
.publication-leather ol.loweralpha { |
||||
@apply list-lower-alpha; |
||||
} |
||||
|
||||
.publication-leather ol.upperalpha { |
||||
@apply list-upper-alpha; |
||||
} |
||||
|
||||
.publication-leather li ol, |
||||
.publication-leather li ul { |
||||
@apply ps-5 my-2; |
||||
} |
||||
|
||||
.audioblock .title, |
||||
.imageblock .title, |
||||
.literalblock .title, |
||||
.tableblock .title, |
||||
.videoblock .title, |
||||
.olist .title, |
||||
.ulist .title { |
||||
@apply my-2 font-thin text-lg; |
||||
} |
||||
|
||||
.publication-leather li p { |
||||
@apply inline; |
||||
} |
||||
|
||||
/* blockquote; prose and poetry quotes */ |
||||
.publication-leather .quoteblock, |
||||
.publication-leather .verseblock { |
||||
@apply p-4 my-4 border-s-4 rounded border-primary-300 bg-primary-50 dark:border-primary-500 dark:bg-primary-700; |
||||
} |
||||
|
||||
.publication-leather .verseblock pre.content { |
||||
@apply text-base font-sans overflow-x-scroll py-1; |
||||
} |
||||
|
||||
.publication-leather .attribution { |
||||
@apply mt-3 italic clear-both; |
||||
} |
||||
|
||||
.publication-leather cite { |
||||
@apply text-sm; |
||||
} |
||||
|
||||
.leading-normal.first-letter\:text-7xl .quoteblock { |
||||
min-height: 108px; |
||||
} |
||||
|
||||
/* admonition */ |
||||
.publication-leather .admonitionblock .title { |
||||
@apply font-semibold; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock table { |
||||
@apply w-full border-collapse; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock tr { |
||||
@apply flex flex-col border-none; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock td { |
||||
@apply border-none; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock p:has(code) { |
||||
@apply my-3; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock { |
||||
@apply rounded overflow-hidden border; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock .icon, |
||||
.publication-leather .admonitionblock .content { |
||||
@apply p-4; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock .content { |
||||
@apply pt-0; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.tip { |
||||
@apply rounded overflow-hidden border border-success-100 dark:border-success-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.tip .icon, |
||||
.publication-leather .admonitionblock.tip .content { |
||||
@apply bg-success-100 dark:bg-success-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.note { |
||||
@apply rounded overflow-hidden border border-info-100 dark:border-info-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.note .icon, |
||||
.publication-leather .admonitionblock.note .content { |
||||
@apply bg-info-100 dark:bg-info-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.important { |
||||
@apply rounded overflow-hidden border border-primary-200 dark:border-primary-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.important .icon, |
||||
.publication-leather .admonitionblock.important .content { |
||||
@apply bg-primary-200 dark:bg-primary-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.caution { |
||||
@apply rounded overflow-hidden border border-warning-200 dark:border-warning-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.caution .icon, |
||||
.publication-leather .admonitionblock.caution .content { |
||||
@apply bg-warning-200 dark:bg-warning-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.warning { |
||||
@apply rounded overflow-hidden border border-danger-200 dark:border-danger-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.warning .icon, |
||||
.publication-leather .admonitionblock.warning .content { |
||||
@apply bg-danger-200 dark:bg-danger-800; |
||||
} |
||||
|
||||
/* listingblock, literalblock */ |
||||
.publication-leather .listingblock, |
||||
.publication-leather .literalblock { |
||||
@apply p-4 rounded bg-highlight dark:bg-primary-700; |
||||
} |
||||
|
||||
.publication-leather .sidebarblock .title, |
||||
.publication-leather .listingblock .title, |
||||
.publication-leather .literalblock .title { |
||||
@apply font-semibold mb-1; |
||||
} |
||||
|
||||
/* sidebar */ |
||||
.publication-leather .sidebarblock { |
||||
@apply p-4 rounded bg-info-100 dark:bg-info-800; |
||||
} |
||||
|
||||
/* video */ |
||||
.videoblock .content { |
||||
@apply w-full aspect-video; |
||||
} |
||||
|
||||
.videoblock .content iframe, |
||||
.videoblock .content video { |
||||
@apply w-full h-full; |
||||
} |
||||
|
||||
/* audio */ |
||||
.audioblock .content { |
||||
@apply my-3; |
||||
} |
||||
|
||||
.audioblock .content audio { |
||||
@apply w-full; |
||||
} |
||||
|
||||
.coverImage { |
||||
@apply max-h-[230px] overflow-hidden; |
||||
} |
||||
|
||||
.coverImage.depth-0 { |
||||
@apply max-h-[460px] overflow-hidden; |
||||
} |
||||
|
||||
.coverImage img { |
||||
@apply object-contain w-full; |
||||
} |
||||
|
||||
.coverImage.depth-0 img { |
||||
@apply m-auto w-auto; |
||||
} |
||||
|
||||
/** blog */ |
||||
@screen lg { |
||||
@media (hover: hover) { |
||||
.blog .discreet .card-leather:not(:hover) { |
||||
@apply bg-primary-50 dark:bg-primary-1000 opacity-75 transition duration-500 ease-in-out ; |
||||
} |
||||
.blog .discreet .group { |
||||
@apply bg-transparent; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Discrete headers */ |
||||
h3.discrete, |
||||
h4.discrete, |
||||
h5.discrete, |
||||
h6.discrete { |
||||
@apply text-gray-800 dark:text-gray-300; |
||||
} |
||||
|
||||
h3.discrete { |
||||
@apply text-2xl font-bold; |
||||
} |
||||
|
||||
h4.discrete { |
||||
@apply text-xl font-bold; |
||||
} |
||||
|
||||
h5.discrete { |
||||
@apply text-lg font-semibold; |
||||
} |
||||
|
||||
h6.discrete { |
||||
@apply text-base font-semibold; |
||||
} |
||||
/* AsciiDoc content */ |
||||
.publication-leather p a { |
||||
@apply underline hover:text-primary-600 dark:hover:text-primary-400; |
||||
} |
||||
|
||||
.publication-leather section p { |
||||
@apply w-full; |
||||
} |
||||
|
||||
.publication-leather section p table { |
||||
@apply w-full table-fixed space-x-2 space-y-2; |
||||
} |
||||
|
||||
.publication-leather section p table td { |
||||
@apply p-2; |
||||
} |
||||
|
||||
.publication-leather section p table td .content:has(> .imageblock) { |
||||
@apply flex flex-col items-center; |
||||
} |
||||
|
||||
.publication-leather .imageblock { |
||||
@apply flex flex-col space-y-2; |
||||
} |
||||
|
||||
.publication-leather .imageblock .content { |
||||
@apply flex justify-center; |
||||
} |
||||
.publication-leather .imageblock .title { |
||||
@apply text-center; |
||||
} |
||||
|
||||
.publication-leather .imageblock.left .content { |
||||
@apply justify-start; |
||||
} |
||||
.publication-leather .imageblock.left .title { |
||||
@apply text-left; |
||||
} |
||||
|
||||
.publication-leather .imageblock.right .content { |
||||
@apply justify-end; |
||||
} |
||||
.publication-leather .imageblock.right .title { |
||||
@apply text-right; |
||||
} |
||||
|
||||
.publication-leather section p table td .literalblock { |
||||
@apply my-2 p-2 border rounded border-gray-400 dark:border-gray-600; |
||||
} |
||||
|
||||
.publication-leather .literalblock pre { |
||||
@apply p-3 text-wrap break-words; |
||||
} |
||||
|
||||
.publication-leather .listingblock pre { |
||||
@apply overflow-x-auto; |
||||
} |
||||
|
||||
/* lists */ |
||||
.publication-leather .ulist ul { |
||||
@apply space-y-1 list-disc list-inside; |
||||
} |
||||
|
||||
.publication-leather .olist ol { |
||||
@apply space-y-1 list-inside; |
||||
} |
||||
|
||||
.publication-leather ol.arabic { |
||||
@apply list-decimal; |
||||
} |
||||
|
||||
.publication-leather ol.loweralpha { |
||||
@apply list-lower-alpha; |
||||
} |
||||
|
||||
.publication-leather ol.upperalpha { |
||||
@apply list-upper-alpha; |
||||
} |
||||
|
||||
.publication-leather li ol, |
||||
.publication-leather li ul { |
||||
@apply ps-5 my-2; |
||||
} |
||||
|
||||
.audioblock .title, |
||||
.imageblock .title, |
||||
.literalblock .title, |
||||
.tableblock .title, |
||||
.videoblock .title, |
||||
.olist .title, |
||||
.ulist .title { |
||||
@apply my-2 font-thin text-lg; |
||||
} |
||||
|
||||
.publication-leather li p { |
||||
@apply inline; |
||||
} |
||||
|
||||
/* blockquote; prose and poetry quotes */ |
||||
.publication-leather .quoteblock, |
||||
.publication-leather .verseblock { |
||||
@apply p-4 my-4 border-s-4 rounded border-primary-300 bg-primary-50 dark:border-primary-500 dark:bg-primary-700; |
||||
} |
||||
|
||||
.publication-leather .verseblock pre.content { |
||||
@apply text-base font-sans overflow-x-scroll py-1; |
||||
} |
||||
|
||||
.publication-leather .attribution { |
||||
@apply mt-3 italic clear-both; |
||||
} |
||||
|
||||
.publication-leather cite { |
||||
@apply text-sm; |
||||
} |
||||
|
||||
.leading-normal.first-letter\:text-7xl .quoteblock { |
||||
min-height: 108px; |
||||
} |
||||
|
||||
/* admonition */ |
||||
.publication-leather .admonitionblock .title { |
||||
@apply font-semibold; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock table { |
||||
@apply w-full border-collapse; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock tr { |
||||
@apply flex flex-col border-none; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock td { |
||||
@apply border-none; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock p:has(code) { |
||||
@apply my-3; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock { |
||||
@apply rounded overflow-hidden border; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock .icon, |
||||
.publication-leather .admonitionblock .content { |
||||
@apply p-4; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock .content { |
||||
@apply pt-0; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.tip { |
||||
@apply rounded overflow-hidden border border-success-100 dark:border-success-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.tip .icon, |
||||
.publication-leather .admonitionblock.tip .content { |
||||
@apply bg-success-100 dark:bg-success-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.note { |
||||
@apply rounded overflow-hidden border border-info-100 dark:border-info-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.note .icon, |
||||
.publication-leather .admonitionblock.note .content { |
||||
@apply bg-info-100 dark:bg-info-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.important { |
||||
@apply rounded overflow-hidden border border-primary-200 dark:border-primary-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.important .icon, |
||||
.publication-leather .admonitionblock.important .content { |
||||
@apply bg-primary-200 dark:bg-primary-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.caution { |
||||
@apply rounded overflow-hidden border border-warning-200 dark:border-warning-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.caution .icon, |
||||
.publication-leather .admonitionblock.caution .content { |
||||
@apply bg-warning-200 dark:bg-warning-700; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.warning { |
||||
@apply rounded overflow-hidden border border-danger-200 dark:border-danger-800; |
||||
} |
||||
|
||||
.publication-leather .admonitionblock.warning .icon, |
||||
.publication-leather .admonitionblock.warning .content { |
||||
@apply bg-danger-200 dark:bg-danger-800; |
||||
} |
||||
|
||||
/* listingblock, literalblock */ |
||||
.publication-leather .listingblock, |
||||
.publication-leather .literalblock { |
||||
@apply p-4 rounded bg-highlight dark:bg-primary-700; |
||||
} |
||||
|
||||
.publication-leather .sidebarblock .title, |
||||
.publication-leather .listingblock .title, |
||||
.publication-leather .literalblock .title { |
||||
@apply font-semibold mb-1; |
||||
} |
||||
|
||||
/* sidebar */ |
||||
.publication-leather .sidebarblock { |
||||
@apply p-4 rounded bg-info-100 dark:bg-info-800; |
||||
} |
||||
|
||||
/* video */ |
||||
.videoblock .content { |
||||
@apply w-full aspect-video; |
||||
} |
||||
|
||||
.videoblock .content iframe, |
||||
.videoblock .content video { |
||||
@apply w-full h-full; |
||||
} |
||||
|
||||
/* audio */ |
||||
.audioblock .content { |
||||
@apply my-3; |
||||
} |
||||
|
||||
.audioblock .content audio { |
||||
@apply w-full; |
||||
} |
||||
|
||||
.coverImage { |
||||
@apply max-h-[230px] overflow-hidden; |
||||
} |
||||
|
||||
.coverImage.depth-0 { |
||||
@apply max-h-[460px] overflow-hidden; |
||||
} |
||||
|
||||
.coverImage img { |
||||
@apply object-contain w-full; |
||||
} |
||||
|
||||
.coverImage.depth-0 img { |
||||
@apply m-auto w-auto; |
||||
} |
||||
|
||||
/** blog */ |
||||
@screen lg { |
||||
@media (hover: hover) { |
||||
.blog .discreet .card-leather:not(:hover) { |
||||
@apply bg-primary-50 dark:bg-primary-1000 opacity-75 transition duration-500 ease-in-out; |
||||
} |
||||
.blog .discreet .group { |
||||
@apply bg-transparent; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* Discrete headers */ |
||||
h3.discrete, |
||||
h4.discrete, |
||||
h5.discrete, |
||||
h6.discrete { |
||||
@apply text-gray-800 dark:text-gray-300; |
||||
} |
||||
|
||||
h3.discrete { |
||||
@apply text-2xl font-bold; |
||||
} |
||||
|
||||
h4.discrete { |
||||
@apply text-xl font-bold; |
||||
} |
||||
|
||||
h5.discrete { |
||||
@apply text-lg font-semibold; |
||||
} |
||||
|
||||
h6.discrete { |
||||
@apply text-base font-semibold; |
||||
} |
||||
} |
||||
@ -1,20 +1,20 @@
@@ -1,20 +1,20 @@
|
||||
@layer components { |
||||
/* Global scrollbar styles */ |
||||
* { |
||||
scrollbar-color: rgba(87, 66, 41, 0.8) transparent; /* Transparent track, default scrollbar thumb */ |
||||
} |
||||
/* Global scrollbar styles */ |
||||
* { |
||||
scrollbar-color: rgba(87, 66, 41, 0.8) transparent; /* Transparent track, default scrollbar thumb */ |
||||
} |
||||
|
||||
/* Webkit Browsers (Chrome, Safari, Edge) */ |
||||
*::-webkit-scrollbar { |
||||
width: 12px; /* Thin scrollbar */ |
||||
} |
||||
/* Webkit Browsers (Chrome, Safari, Edge) */ |
||||
*::-webkit-scrollbar { |
||||
width: 12px; /* Thin scrollbar */ |
||||
} |
||||
|
||||
*::-webkit-scrollbar-track { |
||||
background: transparent; /* Fully transparent track */ |
||||
} |
||||
*::-webkit-scrollbar-track { |
||||
background: transparent; /* Fully transparent track */ |
||||
} |
||||
|
||||
*::-webkit-scrollbar-thumb { |
||||
@apply bg-primary-500 dark:bg-primary-600 hover:bg-primary-600 dark:hover:bg-primary-800;; |
||||
border-radius: 6px; /* Rounded scrollbar */ |
||||
} |
||||
*::-webkit-scrollbar-thumb { |
||||
@apply bg-primary-500 dark:bg-primary-600 hover:bg-primary-600 dark:hover:bg-primary-800; |
||||
border-radius: 6px; /* Rounded scrollbar */ |
||||
} |
||||
} |
||||
@ -1,112 +1,112 @@
@@ -1,112 +1,112 @@
|
||||
@layer components { |
||||
/* Legend styles - specific to visualization */ |
||||
.legend-list { |
||||
@apply list-disc mt-2 space-y-2 text-gray-800 dark:text-gray-300; |
||||
} |
||||
|
||||
.legend-item { |
||||
@apply flex items-center; |
||||
} |
||||
|
||||
.legend-icon { |
||||
@apply relative w-6 h-6 mr-2; |
||||
} |
||||
|
||||
.legend-circle { |
||||
@apply absolute inset-0 rounded-full border-2 border-black; |
||||
} |
||||
|
||||
.legend-circle.content { |
||||
@apply bg-gray-700 dark:bg-gray-300; |
||||
background-color: #d6c1a8; |
||||
} |
||||
|
||||
.legend-circle.content { |
||||
background-color: var(--content-color, #d6c1a8); |
||||
} |
||||
|
||||
:global(.dark) .legend-circle.content { |
||||
background-color: var(--content-color-dark, #FFFFFF); |
||||
} |
||||
|
||||
.legend-letter { |
||||
@apply absolute inset-0 flex items-center justify-center text-black text-xs font-bold; |
||||
} |
||||
|
||||
.legend-text { |
||||
@apply text-sm; |
||||
} |
||||
|
||||
/* Network visualization styles - specific to visualization */ |
||||
.network-container { |
||||
@apply flex flex-col w-full h-[calc(100vh-138px)] min-h-[400px] max-h-[900px]; |
||||
} |
||||
|
||||
.network-svg-container { |
||||
@apply relative sm:h-[100%]; |
||||
} |
||||
|
||||
.network-svg { |
||||
@apply w-full sm:h-[100%] border; |
||||
@apply border border-primary-200 has-[:hover]:border-primary-700 dark:bg-primary-1000 dark:border-primary-800 dark:has-[:hover]:bg-primary-950 dark:has-[:hover]:border-primary-500 rounded; |
||||
} |
||||
|
||||
.network-error { |
||||
@apply w-full p-4 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 rounded-lg mb-4; |
||||
} |
||||
|
||||
.network-error-title { |
||||
@apply font-bold text-lg; |
||||
} |
||||
|
||||
.network-error-retry { |
||||
@apply mt-2 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700; |
||||
} |
||||
|
||||
.network-debug { |
||||
@apply mt-4 text-sm text-gray-500; |
||||
} |
||||
|
||||
/* Zoom controls */ |
||||
.network-controls { |
||||
@apply absolute bottom-4 right-4 flex flex-col gap-2 z-10; |
||||
} |
||||
|
||||
.network-control-button { |
||||
@apply bg-white; |
||||
} |
||||
|
||||
/* Tooltip styles - specific to visualization tooltips */ |
||||
.tooltip-close-btn { |
||||
@apply absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 |
||||
/* Legend styles - specific to visualization */ |
||||
.legend-list { |
||||
@apply list-disc mt-2 space-y-2 text-gray-800 dark:text-gray-300; |
||||
} |
||||
|
||||
.legend-item { |
||||
@apply flex items-center; |
||||
} |
||||
|
||||
.legend-icon { |
||||
@apply relative w-6 h-6 mr-2; |
||||
} |
||||
|
||||
.legend-circle { |
||||
@apply absolute inset-0 rounded-full border-2 border-black; |
||||
} |
||||
|
||||
.legend-circle.content { |
||||
@apply bg-gray-700 dark:bg-gray-300; |
||||
background-color: #d6c1a8; |
||||
} |
||||
|
||||
.legend-circle.content { |
||||
background-color: var(--content-color, #d6c1a8); |
||||
} |
||||
|
||||
:global(.dark) .legend-circle.content { |
||||
background-color: var(--content-color-dark, #ffffff); |
||||
} |
||||
|
||||
.legend-letter { |
||||
@apply absolute inset-0 flex items-center justify-center text-black text-xs font-bold; |
||||
} |
||||
|
||||
.legend-text { |
||||
@apply text-sm; |
||||
} |
||||
|
||||
/* Network visualization styles - specific to visualization */ |
||||
.network-container { |
||||
@apply flex flex-col w-full h-[calc(100vh-138px)] min-h-[400px] max-h-[900px]; |
||||
} |
||||
|
||||
.network-svg-container { |
||||
@apply relative sm:h-[100%]; |
||||
} |
||||
|
||||
.network-svg { |
||||
@apply w-full sm:h-[100%] border; |
||||
@apply border border-primary-200 has-[:hover]:border-primary-700 dark:bg-primary-1000 dark:border-primary-800 dark:has-[:hover]:bg-primary-950 dark:has-[:hover]:border-primary-500 rounded; |
||||
} |
||||
|
||||
.network-error { |
||||
@apply w-full p-4 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 rounded-lg mb-4; |
||||
} |
||||
|
||||
.network-error-title { |
||||
@apply font-bold text-lg; |
||||
} |
||||
|
||||
.network-error-retry { |
||||
@apply mt-2 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700; |
||||
} |
||||
|
||||
.network-debug { |
||||
@apply mt-4 text-sm text-gray-500; |
||||
} |
||||
|
||||
/* Zoom controls */ |
||||
.network-controls { |
||||
@apply absolute bottom-4 right-4 flex flex-col gap-2 z-10; |
||||
} |
||||
|
||||
.network-control-button { |
||||
@apply bg-white; |
||||
} |
||||
|
||||
/* Tooltip styles - specific to visualization tooltips */ |
||||
.tooltip-close-btn { |
||||
@apply absolute top-2 right-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 |
||||
rounded-full p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200; |
||||
} |
||||
} |
||||
|
||||
.tooltip-content { |
||||
@apply space-y-2 pr-6; |
||||
} |
||||
.tooltip-content { |
||||
@apply space-y-2 pr-6; |
||||
} |
||||
|
||||
.tooltip-title { |
||||
@apply font-bold text-base; |
||||
} |
||||
.tooltip-title { |
||||
@apply font-bold text-base; |
||||
} |
||||
|
||||
.tooltip-title-link { |
||||
@apply text-gray-800 hover:text-blue-600 dark:text-gray-200 dark:hover:text-blue-400; |
||||
} |
||||
.tooltip-title-link { |
||||
@apply text-gray-800 hover:text-blue-600 dark:text-gray-200 dark:hover:text-blue-400; |
||||
} |
||||
|
||||
.tooltip-metadata { |
||||
@apply text-gray-600 dark:text-gray-400 text-sm; |
||||
} |
||||
.tooltip-metadata { |
||||
@apply text-gray-600 dark:text-gray-400 text-sm; |
||||
} |
||||
|
||||
.tooltip-summary { |
||||
@apply mt-2 text-xs bg-gray-100 dark:bg-gray-900 p-2 rounded overflow-auto max-h-40; |
||||
} |
||||
.tooltip-summary { |
||||
@apply mt-2 text-xs bg-gray-100 dark:bg-gray-900 p-2 rounded overflow-auto max-h-40; |
||||
} |
||||
|
||||
.tooltip-content-preview { |
||||
@apply mt-2 text-xs bg-gray-100 dark:bg-gray-900 p-2 rounded overflow-auto max-h-40; |
||||
} |
||||
.tooltip-content-preview { |
||||
@apply mt-2 text-xs bg-gray-100 dark:bg-gray-900 p-2 rounded overflow-auto max-h-40; |
||||
} |
||||
|
||||
.tooltip-help-text { |
||||
@apply mt-2 text-xs text-gray-500 dark:text-gray-400 italic; |
||||
} |
||||
.tooltip-help-text { |
||||
@apply mt-2 text-xs text-gray-500 dark:text-gray-400 italic; |
||||
} |
||||
} |
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,18 +1,20 @@
@@ -1,18 +1,20 @@
|
||||
import { test, expect } from '@playwright/test'; |
||||
import { test, expect } from "@playwright/test"; |
||||
|
||||
test('has title', async ({ page }) => { |
||||
await page.goto('https://playwright.dev/'); |
||||
test("has title", async ({ page }) => { |
||||
await page.goto("https://playwright.dev/"); |
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/Playwright/); |
||||
}); |
||||
|
||||
test('get started link', async ({ page }) => { |
||||
await page.goto('https://playwright.dev/'); |
||||
test("get started link", async ({ page }) => { |
||||
await page.goto("https://playwright.dev/"); |
||||
|
||||
// Click the get started link.
|
||||
await page.getByRole('link', { name: 'Get started' }).click(); |
||||
await page.getByRole("link", { name: "Get started" }).click(); |
||||
|
||||
// Expects page to have a heading with the name of Installation.
|
||||
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); |
||||
await expect( |
||||
page.getByRole("heading", { name: "Installation" }), |
||||
).toBeVisible(); |
||||
}); |
||||
|
||||
@ -1,118 +1,131 @@
@@ -1,118 +1,131 @@
|
||||
import { describe, it, expect } from 'vitest'; |
||||
import { parseAdvancedmarkup } from '../../src/lib/utils/markup/advancedMarkupParser'; |
||||
import { describe, it, expect } from "vitest"; |
||||
import { parseAdvancedmarkup } from "../../src/lib/utils/markup/advancedMarkupParser"; |
||||
|
||||
function stripWS(str: string) { |
||||
return str.replace(/\s+/g, ' ').trim(); |
||||
return str.replace(/\s+/g, " ").trim(); |
||||
} |
||||
|
||||
describe('Advanced Markup Parser', () => { |
||||
it('parses headers (ATX and Setext)', async () => { |
||||
const input = '# H1\nText\n\nH2\n====\n'; |
||||
describe("Advanced Markup Parser", () => { |
||||
it("parses headers (ATX and Setext)", async () => { |
||||
const input = "# H1\nText\n\nH2\n====\n"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(stripWS(output)).toContain('H1'); |
||||
expect(stripWS(output)).toContain('H2'); |
||||
expect(stripWS(output)).toContain("H1"); |
||||
expect(stripWS(output)).toContain("H2"); |
||||
}); |
||||
|
||||
it('parses bold, italic, and strikethrough', async () => { |
||||
const input = '*bold* **bold** _italic_ __italic__ ~strikethrough~ ~~strikethrough~~'; |
||||
it("parses bold, italic, and strikethrough", async () => { |
||||
const input = |
||||
"*bold* **bold** _italic_ __italic__ ~strikethrough~ ~~strikethrough~~"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<strong>bold</strong>'); |
||||
expect(output).toContain('<em>italic</em>'); |
||||
expect(output).toContain("<strong>bold</strong>"); |
||||
expect(output).toContain("<em>italic</em>"); |
||||
expect(output).toContain('<del class="line-through">strikethrough</del>'); |
||||
}); |
||||
|
||||
it('parses blockquotes', async () => { |
||||
const input = '> quote'; |
||||
it("parses blockquotes", async () => { |
||||
const input = "> quote"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<blockquote'); |
||||
expect(output).toContain('quote'); |
||||
expect(output).toContain("<blockquote"); |
||||
expect(output).toContain("quote"); |
||||
}); |
||||
|
||||
it('parses multi-line blockquotes', async () => { |
||||
const input = '> quote\n> quote'; |
||||
it("parses multi-line blockquotes", async () => { |
||||
const input = "> quote\n> quote"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<blockquote'); |
||||
expect(output).toContain('quote'); |
||||
expect(output).toContain('quote'); |
||||
expect(output).toContain("<blockquote"); |
||||
expect(output).toContain("quote"); |
||||
expect(output).toContain("quote"); |
||||
}); |
||||
|
||||
it('parses unordered lists', async () => { |
||||
const input = '* a\n* b'; |
||||
it("parses unordered lists", async () => { |
||||
const input = "* a\n* b"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<ul'); |
||||
expect(output).toContain('a'); |
||||
expect(output).toContain('b'); |
||||
expect(output).toContain("<ul"); |
||||
expect(output).toContain("a"); |
||||
expect(output).toContain("b"); |
||||
}); |
||||
|
||||
it('parses ordered lists', async () => { |
||||
const input = '1. one\n2. two'; |
||||
it("parses ordered lists", async () => { |
||||
const input = "1. one\n2. two"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<ol'); |
||||
expect(output).toContain('one'); |
||||
expect(output).toContain('two'); |
||||
expect(output).toContain("<ol"); |
||||
expect(output).toContain("one"); |
||||
expect(output).toContain("two"); |
||||
}); |
||||
|
||||
it('parses links and images', async () => { |
||||
const input = '[link](https://example.com) '; |
||||
it("parses links and images", async () => { |
||||
const input = "[link](https://example.com) "; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<a'); |
||||
expect(output).toContain('<img'); |
||||
expect(output).toContain("<a"); |
||||
expect(output).toContain("<img"); |
||||
}); |
||||
|
||||
it('parses hashtags', async () => { |
||||
const input = '#hashtag'; |
||||
it("parses hashtags", async () => { |
||||
const input = "#hashtag"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('text-primary-600'); |
||||
expect(output).toContain('#hashtag'); |
||||
expect(output).toContain("text-primary-600"); |
||||
expect(output).toContain("#hashtag"); |
||||
}); |
||||
|
||||
it('parses nostr identifiers', async () => { |
||||
const input = 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; |
||||
it("parses nostr identifiers", async () => { |
||||
const input = |
||||
"npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('./events?id=npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); |
||||
expect(output).toContain( |
||||
"./events?id=npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", |
||||
); |
||||
}); |
||||
|
||||
it('parses emoji shortcodes', async () => { |
||||
const input = 'hello :smile:'; |
||||
it("parses emoji shortcodes", async () => { |
||||
const input = "hello :smile:"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toMatch(/😄|:smile:/); |
||||
}); |
||||
|
||||
it('parses wikilinks', async () => { |
||||
const input = '[[Test Page|display]]'; |
||||
it("parses wikilinks", async () => { |
||||
const input = "[[Test Page|display]]"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('wikilink'); |
||||
expect(output).toContain('display'); |
||||
expect(output).toContain("wikilink"); |
||||
expect(output).toContain("display"); |
||||
}); |
||||
|
||||
it('parses tables (with and without headers)', async () => { |
||||
it("parses tables (with and without headers)", async () => { |
||||
const input = `| Syntax | Description |\n|--------|-------------|\n| Header | Title |\n| Paragraph | Text |\n\n| a | b |\n| c | d |`; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<table'); |
||||
expect(output).toContain('Header'); |
||||
expect(output).toContain('a'); |
||||
expect(output).toContain("<table"); |
||||
expect(output).toContain("Header"); |
||||
expect(output).toContain("a"); |
||||
}); |
||||
|
||||
it('parses code blocks (with and without language)', async () => { |
||||
const input = '```js\nconsole.log(1);\n```\n```\nno lang\n```'; |
||||
it("parses code blocks (with and without language)", async () => { |
||||
const input = "```js\nconsole.log(1);\n```\n```\nno lang\n```"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
const textOnly = output.replace(/<[^>]+>/g, ''); |
||||
expect(output).toContain('<pre'); |
||||
expect(textOnly).toContain('console.log(1);'); |
||||
expect(textOnly).toContain('no lang'); |
||||
const textOnly = output.replace(/<[^>]+>/g, ""); |
||||
expect(output).toContain("<pre"); |
||||
expect(textOnly).toContain("console.log(1);"); |
||||
expect(textOnly).toContain("no lang"); |
||||
}); |
||||
|
||||
it('parses horizontal rules', async () => { |
||||
const input = '---'; |
||||
it("parses horizontal rules", async () => { |
||||
const input = "---"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('<hr'); |
||||
expect(output).toContain("<hr"); |
||||
}); |
||||
|
||||
it('parses footnotes (references and section)', async () => { |
||||
const input = 'Here is a footnote[^1].\n\n[^1]: This is the footnote.'; |
||||
it("parses footnotes (references and section)", async () => { |
||||
const input = "Here is a footnote[^1].\n\n[^1]: This is the footnote."; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain('Footnotes'); |
||||
expect(output).toContain('This is the footnote'); |
||||
expect(output).toContain('fn-1'); |
||||
expect(output).toContain("Footnotes"); |
||||
expect(output).toContain("This is the footnote"); |
||||
expect(output).toContain("fn-1"); |
||||
}); |
||||
|
||||
it("parses unordered lists with '-' as bullet", async () => { |
||||
const input = "- item one\n- item two\n - nested item\n- item three"; |
||||
const output = await parseAdvancedmarkup(input); |
||||
expect(output).toContain("<ul"); |
||||
expect(output).toContain("item one"); |
||||
expect(output).toContain("nested item"); |
||||
expect(output).toContain("item three"); |
||||
}); |
||||
}); |
||||
@ -1,88 +1,92 @@
@@ -1,88 +1,92 @@
|
||||
import { describe, it, expect } from 'vitest'; |
||||
import { parseBasicmarkup } from '../../src/lib/utils/markup/basicMarkupParser'; |
||||
import { describe, it, expect } from "vitest"; |
||||
import { parseBasicmarkup } from "../../src/lib/utils/markup/basicMarkupParser"; |
||||
|
||||
// Helper to strip whitespace for easier comparison
|
||||
function stripWS(str: string) { |
||||
return str.replace(/\s+/g, ' ').trim(); |
||||
return str.replace(/\s+/g, " ").trim(); |
||||
} |
||||
|
||||
describe('Basic Markup Parser', () => { |
||||
it('parses ATX and Setext headers', async () => { |
||||
const input = '# H1\nText\n\nH2\n====\n'; |
||||
describe("Basic Markup Parser", () => { |
||||
it("parses ATX and Setext headers", async () => { |
||||
const input = "# H1\nText\n\nH2\n====\n"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(stripWS(output)).toContain('H1'); |
||||
expect(stripWS(output)).toContain('H2'); |
||||
expect(stripWS(output)).toContain("H1"); |
||||
expect(stripWS(output)).toContain("H2"); |
||||
}); |
||||
|
||||
it('parses bold, italic, and strikethrough', async () => { |
||||
const input = '*bold* **bold** _italic_ __italic__ ~strikethrough~ ~~strikethrough~~'; |
||||
it("parses bold, italic, and strikethrough", async () => { |
||||
const input = |
||||
"*bold* **bold** _italic_ __italic__ ~strikethrough~ ~~strikethrough~~"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('<strong>bold</strong>'); |
||||
expect(output).toContain('<em>italic</em>'); |
||||
expect(output).toContain("<strong>bold</strong>"); |
||||
expect(output).toContain("<em>italic</em>"); |
||||
expect(output).toContain('<del class="line-through">strikethrough</del>'); |
||||
}); |
||||
|
||||
it('parses blockquotes', async () => { |
||||
const input = '> quote'; |
||||
it("parses blockquotes", async () => { |
||||
const input = "> quote"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('<blockquote'); |
||||
expect(output).toContain('quote'); |
||||
expect(output).toContain("<blockquote"); |
||||
expect(output).toContain("quote"); |
||||
}); |
||||
|
||||
it('parses multi-line blockquotes', async () => { |
||||
const input = '> quote\n> quote'; |
||||
it("parses multi-line blockquotes", async () => { |
||||
const input = "> quote\n> quote"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('<blockquote'); |
||||
expect(output).toContain('quote'); |
||||
expect(output).toContain('quote'); |
||||
expect(output).toContain("<blockquote"); |
||||
expect(output).toContain("quote"); |
||||
expect(output).toContain("quote"); |
||||
}); |
||||
|
||||
it('parses unordered lists', async () => { |
||||
const input = '* a\n* b'; |
||||
it("parses unordered lists", async () => { |
||||
const input = "* a\n* b"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('<ul'); |
||||
expect(output).toContain('a'); |
||||
expect(output).toContain('b'); |
||||
expect(output).toContain("<ul"); |
||||
expect(output).toContain("a"); |
||||
expect(output).toContain("b"); |
||||
}); |
||||
|
||||
it('parses ordered lists', async () => { |
||||
const input = '1. one\n2. two'; |
||||
it("parses ordered lists", async () => { |
||||
const input = "1. one\n2. two"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('<ol'); |
||||
expect(output).toContain('one'); |
||||
expect(output).toContain('two'); |
||||
expect(output).toContain("<ol"); |
||||
expect(output).toContain("one"); |
||||
expect(output).toContain("two"); |
||||
}); |
||||
|
||||
it('parses links and images', async () => { |
||||
const input = '[link](https://example.com) '; |
||||
it("parses links and images", async () => { |
||||
const input = "[link](https://example.com) "; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('<a'); |
||||
expect(output).toContain('<img'); |
||||
expect(output).toContain("<a"); |
||||
expect(output).toContain("<img"); |
||||
}); |
||||
|
||||
it('parses hashtags', async () => { |
||||
const input = '#hashtag'; |
||||
it("parses hashtags", async () => { |
||||
const input = "#hashtag"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('text-primary-600'); |
||||
expect(output).toContain('#hashtag'); |
||||
expect(output).toContain("text-primary-600"); |
||||
expect(output).toContain("#hashtag"); |
||||
}); |
||||
|
||||
it('parses nostr identifiers', async () => { |
||||
const input = 'npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; |
||||
it("parses nostr identifiers", async () => { |
||||
const input = |
||||
"npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('./events?id=npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'); |
||||
expect(output).toContain( |
||||
"./events?id=npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", |
||||
); |
||||
}); |
||||
|
||||
it('parses emoji shortcodes', async () => { |
||||
const input = 'hello :smile:'; |
||||
it("parses emoji shortcodes", async () => { |
||||
const input = "hello :smile:"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toMatch(/😄|:smile:/); |
||||
}); |
||||
|
||||
it('parses wikilinks', async () => { |
||||
const input = '[[Test Page|display]]'; |
||||
it("parses wikilinks", async () => { |
||||
const input = "[[Test Page|display]]"; |
||||
const output = await parseBasicmarkup(input); |
||||
expect(output).toContain('wikilink'); |
||||
expect(output).toContain('display'); |
||||
expect(output).toContain("wikilink"); |
||||
expect(output).toContain("display"); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
import { describe, it, expect } from "vitest"; |
||||
import { parseAdvancedmarkup } from "../../src/lib/utils/markup/advancedMarkupParser"; |
||||
import { readFileSync } from "fs"; |
||||
import { join } from "path"; |
||||
|
||||
describe("LaTeX Math Rendering", () => { |
||||
const mdPath = join(__dirname, "../../test_data/latex_markdown.md"); |
||||
const raw = readFileSync(mdPath, "utf-8"); |
||||
// Extract the markdown content field from the JSON
|
||||
const content = JSON.parse(raw).content; |
||||
|
||||
it('renders inline math as <span class="math-inline">', async () => { |
||||
const html = await parseAdvancedmarkup(content); |
||||
expect(html).toMatch(/<span class="math-inline">\$P \\neq NP\$<\/span>/); |
||||
expect(html).toMatch( |
||||
/<span class="math-inline">\$x_1 = \\text\{True\}\$<\/span>/, |
||||
); |
||||
}); |
||||
|
||||
it('renders display math as <div class="math-block', async () => { |
||||
const html = await parseAdvancedmarkup(content); |
||||
// Representative display math
|
||||
expect(html).toMatch( |
||||
/<div class="math-block my-4 text-center">\$\$\s*P_j = \\bigotimes/, |
||||
); |
||||
expect(html).toMatch( |
||||
/<div class="math-block my-4 text-center">\$\$[\s\S]*?\\begin\{pmatrix\}/, |
||||
); |
||||
expect(html).toMatch( |
||||
/<div class="math-block my-4 text-center">\$\$\\boxed\{P \\neq NP\}\$\$<\/div>/, |
||||
); |
||||
}); |
||||
|
||||
it("does not wrap display math in <p> or <blockquote>", async () => { |
||||
const html = await parseAdvancedmarkup(content); |
||||
// No <p> or <blockquote> directly wrapping math-block
|
||||
expect(html).not.toMatch(/<p[^>]*>\s*<div class="math-block/); |
||||
expect(html).not.toMatch(/<blockquote[^>]*>\s*<div class="math-block/); |
||||
}); |
||||
|
||||
it("renders LaTeX environments (pmatrix) within display math blocks", async () => { |
||||
const html = await parseAdvancedmarkup(content); |
||||
// Check that pmatrix is properly rendered within a display math block
|
||||
expect(html).toMatch( |
||||
/<div class="math-block my-4 text-center">\$\$[\s\S]*?\\begin\{pmatrix\}[\s\S]*?\\end\{pmatrix\}[\s\S]*?\$\$<\/div>/, |
||||
); |
||||
}); |
||||
|
||||
it('renders all math as math (no unwrapped $...$, $$...$$, \\(...\\), \\[...\\], or environments left)', async () => { |
||||
const html = await parseAdvancedmarkup(content); |
||||
// No unwrapped $...$ outside math-inline or math-block
|
||||
// Remove all math-inline and math-block tags and check for stray $...$
|
||||
const htmlNoMath = html |
||||
.replace(/<span class="math-inline">\$[^$]+\$<\/span>/g, '') |
||||
.replace(/<div class="math-block[^"]*">\$\$[\s\S]*?\$\$<\/div>/g, '') |
||||
.replace(/<div class="math-block[^"]*">[\s\S]*?<\/div>/g, ''); |
||||
expect(htmlNoMath).not.toMatch(/\$[^\$\n]+\$/); // inline math
|
||||
expect(htmlNoMath).not.toMatch(/\$\$[\s\S]*?\$\$/); // display math
|
||||
expect(htmlNoMath).not.toMatch(/\\\([^)]+\\\)/); // \(...\)
|
||||
expect(htmlNoMath).not.toMatch(/\\\[[^\]]+\\\]/); // \[...\]
|
||||
expect(htmlNoMath).not.toMatch(/\\begin\{[a-zA-Z*]+\}[\s\S]*?\\end\{[a-zA-Z*]+\}/); // environments
|
||||
// No math inside code or pre
|
||||
expect(html).not.toMatch(/<code[\s\S]*?\$[\s\S]*?\$[\s\S]*?<\/code>/); |
||||
expect(html).not.toMatch(/<pre[\s\S]*?\$[\s\S]*?\$[\s\S]*?<\/pre>/); |
||||
}); |
||||
|
||||
it('renders every line of the document: all math is wrapped', async () => { |
||||
const lines = content.split(/\r?\n/); |
||||
for (let i = 0; i < lines.length; i++) { |
||||
const line = lines[i]; |
||||
if (!line.trim()) continue; |
||||
const html = await parseAdvancedmarkup(line); |
||||
// If the line contains $...$, $$...$$, \(...\), \[...\], or bare LaTeX commands, it should be wrapped
|
||||
const hasMath = /\$[^$]+\$|\$\$[\s\S]*?\$\$|\\\([^)]+\\\)|\\\[[^\]]+\\\]|\\[a-zA-Z]+(\{[^}]*\})*/.test(line); |
||||
if (hasMath) { |
||||
const wrapped = /math-inline|math-block/.test(html); |
||||
if (!wrapped) { |
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Line ${i + 1} failed:`, line); |
||||
// eslint-disable-next-line no-console
|
||||
console.error('Rendered HTML:', html); |
||||
} |
||||
expect(wrapped).toBe(true); |
||||
} |
||||
// Should not have any unwrapped $...$, $$...$$, \(...\), \[...\], or bare LaTeX commands
|
||||
const stray = /(^|[^>])\$[^$\n]+\$|\$\$[\s\S]*?\$\$|\\\([^)]+\\\)|\\\[[^\]]+\\\]|\\[a-zA-Z]+(\{[^}]*\})*/.test(html); |
||||
expect(stray).toBe(false); |
||||
} |
||||
}); |
||||
|
||||
it('renders standalone math lines as display math blocks', async () => { |
||||
const mdPath = require('path').join(__dirname, '../../test_data/latex_markdown.md'); |
||||
const raw = require('fs').readFileSync(mdPath, 'utf-8'); |
||||
const content = JSON.parse(raw).content || raw; |
||||
const html = await parseAdvancedmarkup(content); |
||||
// Example: Bures distance line
|
||||
expect(html).toMatch(/<div class="math-block my-4 text-center">\$\$d_B\([^$]+\) = [^$]+\$\$<\/div>/); |
||||
// Example: P(\rho) = ...
|
||||
expect(html).toMatch(/<div class="math-block my-4 text-center">\$\$P\([^$]+\) = [^$]+\$\$<\/div>/); |
||||
}); |
||||
}); |
||||
Loading…
Reference in new issue