Merge pull request #27 from dsec-hub/feature/share-button

added share button
This commit is contained in:
RythonDev 2026-09-05 16:27:48 +08:00 committed by GitHub
commit 11132d5e86
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 136 additions and 34 deletions

View file

@ -1,6 +1,7 @@
<script lang="ts">
import { previewMarkdown } from "$lib/markdown";
import { unitPath } from "$lib/paths";
import ShareButton from "./ShareButton.svelte";
import VoteStack from "./VoteStack.svelte";
let {
@ -9,6 +10,9 @@
content,
unitCode,
meta,
authorName,
authorId,
extra,
voteCount = 0,
targetType,
targetId,
@ -18,7 +22,10 @@
title: string;
content?: string;
unitCode?: string;
meta: string;
meta?: string;
authorName?: string;
authorId?: string;
extra?: string;
voteCount?: number;
targetType: "note" | "question";
targetId: string;
@ -48,6 +55,23 @@
{previewMarkdown(content)}
</p>
{/if}
<p class="kicker mt-1.5">{meta}</p>
<div class="kicker mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1">
{#if authorName}
{#if authorId}
<a href={`/users/${authorId}`} class="hover:text-primary">{authorName}</a>
{:else}
<span>{authorName}</span>
{/if}
{/if}
{#if extra}
{#if authorName}<span aria-hidden="true">·</span>{/if}
<span>{extra}</span>
{/if}
{#if meta}
{#if authorName || extra}<span aria-hidden="true">·</span>{/if}
<span>{meta}</span>
{/if}
<ShareButton url={href} {title} />
</div>
</div>
</article>

View file

@ -0,0 +1,66 @@
<script lang="ts">
let {
url,
title,
}: {
url?: string;
title?: string;
} = $props();
let copied = $state(false);
let copyTimer: ReturnType<typeof setTimeout> | undefined;
function absoluteUrl(href: string) {
return new URL(href, window.location.origin).href;
}
async function share(event: MouseEvent) {
event.preventDefault();
event.stopPropagation();
const href = absoluteUrl(url ?? window.location.href);
try {
if (navigator.share) {
await navigator.share({ title, url: href });
return;
}
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;
}
try {
await navigator.clipboard.writeText(href);
copied = true;
clearTimeout(copyTimer);
copyTimer = setTimeout(() => (copied = false), 1500);
} catch {
/* ignore */
}
}
</script>
<button
type="button"
onclick={share}
class="text-faint hover:text-primary hover:bg-rule -ml-1 inline-flex h-7 w-7 items-center justify-center rounded-sm transition-colors"
aria-label={copied ? "Link copied" : "Share"}
title={copied ? "Copied" : "Share"}
>
{#if copied}
<svg
class="h-3.5 w-3.5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.75"
aria-hidden="true"
>
<path d="M5 13l4 4L19 7" stroke-linecap="square" />
</svg>
{:else}
<svg class="h-3.5 w-3.5" viewBox="0 0 512 512" fill="currentColor" aria-hidden="true">
<path
d="M512,241.7L273.643,3.343v156.152c-71.41,3.744-138.015,33.337-188.958,84.28C30.075,298.384,0,370.991,0,448.222v60.436l29.069-52.985c45.354-82.671,132.173-134.027,226.573-134.027c5.986,0,12.004,0.212,18.001,0.632v157.779L512,241.7z M255.642,290.666c-84.543,0-163.661,36.792-217.939,98.885c26.634-114.177,129.256-199.483,251.429-199.483h15.489V78.131l163.568,163.568L304.621,405.267V294.531l-13.585-1.683C279.347,291.401,267.439,290.666,255.642,290.666z"
/>
</svg>
{/if}
</button>

View file

@ -5,7 +5,6 @@
import UnitFilter from "$lib/components/UnitFilter.svelte";
import { postPath } from "$lib/paths";
import { unitMatchesQuery } from "$lib/search";
import { timeAgo } from "$lib/time";
import type { NoteDoc, UnitDoc } from "$lib/types";
let notes: (NoteDoc & { unit?: UnitDoc })[] = $state([]);
@ -96,9 +95,11 @@
unitCode={note.unit
? note.unit.code + (note.unit.code2 ? ` / ${note.unit.code2}` : "")
: undefined}
meta="{note.authorName} · {timeAgo(
note.createdAt,
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
authorName={note.authorName}
authorId={note.authorId}
extra={note.commentCount > 0
? `${note.commentCount} comment${note.commentCount === 1 ? "" : "s"}`
: undefined}
voteCount={note.voteCount}
targetType="note"
targetId={note._id}

View file

@ -8,6 +8,7 @@
import MarkdownEditor from "$lib/components/MarkdownEditor.svelte";
import CommentThread from "$lib/components/CommentThread.svelte";
import Seo from "$lib/components/Seo.svelte";
import ShareButton from "$lib/components/ShareButton.svelte";
import { postSeo } from "$lib/seo";
import { timeAgo } from "$lib/time";
import type { NoteDoc, CommentDoc, TopicDoc, UnitDoc } from "$lib/types";
@ -201,11 +202,13 @@
{/if}
{/if}
{#if !editMode}
<p class="kicker mt-3">
<div class="kicker mt-3">
<a href={`/users/${note.authorId}`} class="hover:text-primary"
>{note.authorName}</a
>
· {timeAgo(note.createdAt)}
·
<ShareButton title={note.title} />
{#if isAuthor}
·
<button
@ -225,7 +228,7 @@
{deleteLoading ? "Deleting..." : "Delete"}
</button>
{/if}
</p>
</div>
{/if}
</div>
</div>
@ -255,7 +258,9 @@
{/if}
<section class="border-rule mt-12 border-t pt-8">
<p class="kicker mb-6">Comments ({comments.length})</p>
<p class="kicker mb-6">
Comments{comments.length > 0 ? ` (${comments.length})` : ""}
</p>
{#if get(isAuthenticated)}
<div class="mb-8">

View file

@ -5,7 +5,6 @@
import UnitFilter from "$lib/components/UnitFilter.svelte";
import { postPath } from "$lib/paths";
import { unitMatchesQuery } from "$lib/search";
import { timeAgo } from "$lib/time";
import type { QuestionDoc, UnitDoc } from "$lib/types";
let questions: (QuestionDoc & { unit?: UnitDoc })[] = $state([]);
@ -95,9 +94,11 @@
unitCode={question.unit
? question.unit.code + (question.unit.code2 ? ` / ${question.unit.code2}` : "")
: undefined}
meta="{question.authorName} · {timeAgo(
question.createdAt,
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
authorName={question.authorName}
authorId={question.authorId}
extra={question.answerCount > 0
? `${question.answerCount} answer${question.answerCount === 1 ? "" : "s"}`
: undefined}
voteCount={question.voteCount}
targetType="question"
targetId={question._id}

View file

@ -8,6 +8,7 @@
import MarkdownEditor from "$lib/components/MarkdownEditor.svelte";
import CommentThread from "$lib/components/CommentThread.svelte";
import Seo from "$lib/components/Seo.svelte";
import ShareButton from "$lib/components/ShareButton.svelte";
import { postSeo } from "$lib/seo";
import { timeAgo } from "$lib/time";
import type { QuestionDoc, CommentDoc, TopicDoc, UnitDoc } from "$lib/types";
@ -187,11 +188,13 @@
{/if}
{/if}
{#if !editMode}
<p class="kicker mt-3">
<div class="kicker mt-3">
<a href={`/users/${question.authorId}`} class="hover:text-primary"
>{question.authorName}</a
>
· {timeAgo(question.createdAt)}
·
<ShareButton title={question.title} />
{#if isAuthor}
·
<button
@ -212,7 +215,7 @@
</button>
{/if}
{/if}
</p>
</div>
{/if}
</div>
</div>
@ -242,7 +245,9 @@
{/if}
<section class="border-rule mt-12 border-t pt-8">
<p class="kicker mb-6">Answers ({answers.length})</p>
<p class="kicker mb-6">
Answers{answers.length > 0 ? ` (${answers.length})` : ""}
</p>
{#if get(isAuthenticated)}
<div class="mb-8">

View file

@ -85,9 +85,9 @@
unitCode={result.unit
? result.unit.code + (result.unit.code2 ? ` / ${result.unit.code2}` : "")
: undefined}
meta="{result.authorName} · {timeAgo(
result.createdAt,
)} · {result.commentCount} comment{result.commentCount === 1 ? '' : 's'}"
meta={result.commentCount > 0
? `${result.authorName} · ${timeAgo(result.createdAt)} · ${result.commentCount} comment${result.commentCount === 1 ? "" : "s"}`
: `${result.authorName} · ${timeAgo(result.createdAt)}`}
voteCount={result.voteCount}
targetType="note"
targetId={result._id}
@ -101,9 +101,9 @@
unitCode={result.unit
? result.unit.code + (result.unit.code2 ? ` / ${result.unit.code2}` : "")
: undefined}
meta="{result.authorName} · {timeAgo(
result.createdAt,
)} · {result.answerCount} answer{result.answerCount === 1 ? '' : 's'}"
meta={result.answerCount > 0
? `${result.authorName} · ${timeAgo(result.createdAt)} · ${result.answerCount} answer${result.answerCount === 1 ? "" : "s"}`
: `${result.authorName} · ${timeAgo(result.createdAt)}`}
voteCount={result.voteCount}
targetType="question"
targetId={result._id}

View file

@ -67,9 +67,9 @@
unitCode={note.unit
? note.unit.code + (note.unit.code2 ? ` / ${note.unit.code2}` : "")
: undefined}
meta="{note.authorName} · {timeAgo(
note.createdAt,
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
meta={note.commentCount > 0
? `${note.authorName} · ${timeAgo(note.createdAt)} · ${note.commentCount} comment${note.commentCount === 1 ? "" : "s"}`
: `${note.authorName} · ${timeAgo(note.createdAt)}`}
voteCount={note.voteCount}
targetType="note"
targetId={note._id}
@ -88,9 +88,9 @@
? question.unit.code +
(question.unit.code2 ? ` / ${question.unit.code2}` : "")
: undefined}
meta="{question.authorName} · {timeAgo(
question.createdAt,
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
meta={question.answerCount > 0
? `${question.authorName} · ${timeAgo(question.createdAt)} · ${question.answerCount} answer${question.answerCount === 1 ? "" : "s"}`
: `${question.authorName} · ${timeAgo(question.createdAt)}`}
voteCount={question.voteCount}
targetType="question"
targetId={question._id}

View file

@ -174,9 +174,9 @@
title={note.title}
content={note.content}
unitCode={unit.code + (unit.code2 ? ` / ${unit.code2}` : "")}
meta="{timeAgo(
note.createdAt,
)} · {note.commentCount} comment{note.commentCount === 1 ? '' : 's'}"
meta={note.commentCount > 0
? `${timeAgo(note.createdAt)} · ${note.commentCount} comment${note.commentCount === 1 ? "" : "s"}`
: timeAgo(note.createdAt)}
voteCount={note.voteCount}
targetType="note"
targetId={note._id}
@ -192,9 +192,9 @@
title={question.title}
content={question.content}
unitCode={unit.code + (unit.code2 ? ` / ${unit.code2}` : "")}
meta="{question.authorName} · {timeAgo(
question.createdAt,
)} · {question.answerCount} answer{question.answerCount === 1 ? '' : 's'}"
meta={question.answerCount > 0
? `${question.authorName} · ${timeAgo(question.createdAt)} · ${question.answerCount} answer${question.answerCount === 1 ? "" : "s"}`
: `${question.authorName} · ${timeAgo(question.createdAt)}`}
voteCount={question.voteCount}
targetType="question"
targetId={question._id}