feat: markdown support (#165)

* initial markdown support via markdown-it

* sanitize markup

* remove pre-padding

* fix linter errors
This commit is contained in:
Hayden 2022-12-02 16:12:32 -09:00 committed by GitHub
parent d2aa022347
commit 974d6914a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1396 additions and 1049 deletions

View file

@ -17,6 +17,11 @@
</a> </a>
</div> </div>
</template> </template>
<template v-else-if="detail.type === 'markdown'">
<ClientOnly>
<Markdown :source="detail.text" />
</ClientOnly>
</template>
<template v-else> <template v-else>
{{ detail.text }} {{ detail.text }}
</template> </template>
@ -28,11 +33,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { CustomDetail, Detail } from "./types"; import type { AnyDetail, Detail } from "./types";
defineProps({ defineProps({
details: { details: {
type: Object as () => (Detail | CustomDetail)[], type: Object as () => (Detail | AnyDetail)[],
required: true, required: true,
}, },
}); });

View file

@ -21,11 +21,16 @@ type LinkDetail = BaseDetail & {
href: string; href: string;
}; };
export type CustomDetail = DateDetail | CurrencyDetail | LinkDetail; type MarkdownDetail = BaseDetail & {
type: "markdown";
text: string;
};
export type Detail = BaseDetail & { export type Detail = BaseDetail & {
text: StringLike; text: StringLike;
type?: "text"; type?: "text";
}; };
export type Details = Array<Detail | CustomDetail>; export type AnyDetail = DateDetail | CurrencyDetail | LinkDetail | MarkdownDetail | Detail;
export type Details = Array<Detail | AnyDetail>;

View file

@ -0,0 +1,80 @@
<script setup lang="ts">
import MarkdownIt from "markdown-it";
import DOMPurify from "dompurify";
type Props = {
source: string;
};
const props = withDefaults(defineProps<Props>(), {});
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
const raw = computed(() => {
const html = md.render(props.source);
return DOMPurify.sanitize(html);
});
</script>
<template>
<div class="markdown" v-html="raw"></div>
</template>
<style scoped>
* {
--y-gap: 0.65rem;
}
.markdown > :first-child {
margin-top: 0px !important;
}
.markdown :where(p, ul, ol, dl, blockquote, h1, h2, h3, h4, h5, h6) {
margin-top: var(--y-gap);
margin-bottom: var(--y-gap);
}
.markdown :where(ul) {
list-style: disc;
margin-left: 2rem;
}
.markdown :where(ol) {
list-style: decimal;
margin-left: 2rem;
}
/* Heading Styles */
.markdown :where(h1) {
font-size: 2rem;
font-weight: 700;
}
.markdown :where(h2) {
font-size: 1.5rem;
font-weight: 700;
}
.markdown :where(h3) {
font-size: 1.25rem;
font-weight: 700;
}
.markdown :where(h4) {
font-size: 1rem;
font-weight: 700;
}
.markdown :where(h5) {
font-size: 0.875rem;
font-weight: 700;
}
.markdown :where(h6) {
font-size: 0.75rem;
font-weight: 700;
}
</style>

View file

@ -37,6 +37,8 @@
"@vueuse/nuxt": "^9.1.1", "@vueuse/nuxt": "^9.1.1",
"autoprefixer": "^10.4.8", "autoprefixer": "^10.4.8",
"daisyui": "^2.24.0", "daisyui": "^2.24.0",
"dompurify": "^2.4.1",
"markdown-it": "^13.0.1",
"pinia": "^2.0.21", "pinia": "^2.0.21",
"postcss": "^8.4.16", "postcss": "^8.4.16",
"tailwindcss": "^3.1.8", "tailwindcss": "^3.1.8",

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { CustomDetail, Detail, Details } from "~~/components/global/DetailsSection/types"; import { AnyDetail, Detail, Details } from "~~/components/global/DetailsSection/types";
import { ItemAttachment } from "~~/lib/api/types/data-contracts"; import { ItemAttachment } from "~~/lib/api/types/data-contracts";
definePageMeta({ definePageMeta({
@ -87,6 +87,7 @@
return [ return [
{ {
name: "Description", name: "Description",
type: "markdown",
text: item.value?.description, text: item.value?.description,
}, },
{ {
@ -111,6 +112,7 @@
}, },
{ {
name: "Notes", name: "Notes",
type: "markdown",
text: item.value?.notes, text: item.value?.notes,
}, },
...assetID.value, ...assetID.value,
@ -125,7 +127,7 @@
name: field.name, name: field.name,
text: url.text, text: url.text,
href: url.url, href: url.url,
} as CustomDetail; } as AnyDetail;
} }
return { return {
@ -214,6 +216,7 @@
details.push({ details.push({
name: "Warranty Details", name: "Warranty Details",
type: "markdown",
text: item.value?.warrantyDetails || "", text: item.value?.warrantyDetails || "",
}); });

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type { CustomDetail, Detail } from "~~/components/global/DetailsSection/types"; import type { AnyDetail, Details } from "~~/components/global/DetailsSection/types";
definePageMeta({ definePageMeta({
middleware: ["auth"], middleware: ["auth"],
@ -23,7 +23,7 @@
return data; return data;
}); });
const details = computed<(Detail | CustomDetail)[]>(() => { const details = computed<Details>(() => {
const details = [ const details = [
{ {
name: "Name", name: "Name",
@ -31,8 +31,9 @@
}, },
{ {
name: "Description", name: "Description",
type: "markdown",
text: label.value?.description, text: label.value?.description,
}, } as AnyDetail,
]; ];
if (preferences.value.showDetails) { if (preferences.value.showDetails) {

View file

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Detail, CustomDetail } from "~~/components/global/DetailsSection/types"; import { AnyDetail, Details } from "~~/components/global/DetailsSection/types";
import { LocationSummary, LocationUpdate } from "~~/lib/api/types/data-contracts"; import { LocationSummary, LocationUpdate } from "~~/lib/api/types/data-contracts";
import { useLocationStore } from "~~/stores/locations"; import { useLocationStore } from "~~/stores/locations";
@ -30,16 +30,17 @@
return data; return data;
}); });
const details = computed<(Detail | CustomDetail)[]>(() => { const details = computed<Details>(() => {
const details = [ const details = [
{ {
name: "Name", name: "Name",
text: location.value?.name, text: location.value?.name ?? "",
}, },
{ {
name: "Description", name: "Description",
text: location.value?.description, type: "markdown",
}, text: location.value?.description ?? "",
} as AnyDetail,
]; ];
if (preferences.value.showDetails) { if (preferences.value.showDetails) {

File diff suppressed because it is too large Load diff