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

View file

@ -21,11 +21,16 @@ type LinkDetail = BaseDetail & {
href: string;
};
export type CustomDetail = DateDetail | CurrencyDetail | LinkDetail;
type MarkdownDetail = BaseDetail & {
type: "markdown";
text: string;
};
export type Detail = BaseDetail & {
text: StringLike;
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",
"autoprefixer": "^10.4.8",
"daisyui": "^2.24.0",
"dompurify": "^2.4.1",
"markdown-it": "^13.0.1",
"pinia": "^2.0.21",
"postcss": "^8.4.16",
"tailwindcss": "^3.1.8",

View file

@ -1,5 +1,5 @@
<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";
definePageMeta({
@ -87,6 +87,7 @@
return [
{
name: "Description",
type: "markdown",
text: item.value?.description,
},
{
@ -111,6 +112,7 @@
},
{
name: "Notes",
type: "markdown",
text: item.value?.notes,
},
...assetID.value,
@ -125,7 +127,7 @@
name: field.name,
text: url.text,
href: url.url,
} as CustomDetail;
} as AnyDetail;
}
return {
@ -214,6 +216,7 @@
details.push({
name: "Warranty Details",
type: "markdown",
text: item.value?.warrantyDetails || "",
});

View file

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

View file

@ -1,5 +1,5 @@
<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 { useLocationStore } from "~~/stores/locations";
@ -30,16 +30,17 @@
return data;
});
const details = computed<(Detail | CustomDetail)[]>(() => {
const details = computed<Details>(() => {
const details = [
{
name: "Name",
text: location.value?.name,
text: location.value?.name ?? "",
},
{
name: "Description",
text: location.value?.description,
},
type: "markdown",
text: location.value?.description ?? "",
} as AnyDetail,
];
if (preferences.value.showDetails) {

File diff suppressed because it is too large Load diff