mirror of
https://github.com/dsec-hub/dsec-notebook.git
synced 2026-09-22 07:24:27 +00:00
Merge pull request #27 from dsec-hub/feature/share-button
added share button
This commit is contained in:
commit
11132d5e86
9 changed files with 136 additions and 34 deletions
|
|
@ -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>
|
||||
|
|
|
|||
66
src/lib/components/ShareButton.svelte
Normal file
66
src/lib/components/ShareButton.svelte
Normal 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>
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Reference in a new issue