♻️ Refactored the post item
This commit is contained in:
@@ -49,14 +49,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from "vue"
|
import { ref, watch } from "vue"
|
||||||
import { unified } from "unified"
|
import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
|
||||||
import remarkParse from "remark-parse"
|
|
||||||
import remarkMath from "remark-math"
|
|
||||||
import remarkRehype from "remark-rehype"
|
|
||||||
import remarkBreaks from "remark-breaks"
|
|
||||||
import remarkGfm from "remark-gfm"
|
|
||||||
import rehypeKatex from "rehype-katex"
|
|
||||||
import rehypeStringify from "rehype-stringify"
|
|
||||||
import type { SnPost } from "~/types/api"
|
import type { SnPost } from "~/types/api"
|
||||||
|
|
||||||
import PostHeader from "./PostHeader.vue"
|
import PostHeader from "./PostHeader.vue"
|
||||||
@@ -64,50 +57,23 @@ import AttachmentItem from "./AttachmentItem.vue"
|
|||||||
import PostReactionList from "./PostReactionList.vue"
|
import PostReactionList from "./PostReactionList.vue"
|
||||||
|
|
||||||
const props = defineProps<{ item: SnPost }>()
|
const props = defineProps<{ item: SnPost }>()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
react: [symbol: string, attitude: number, delta: number]
|
||||||
|
}>()
|
||||||
|
|
||||||
const processor = unified()
|
const { render } = useMarkdownProcessor()
|
||||||
.use(remarkParse)
|
|
||||||
.use(remarkMath)
|
|
||||||
.use(remarkBreaks)
|
|
||||||
.use(remarkGfm)
|
|
||||||
.use(remarkRehype)
|
|
||||||
.use(rehypeKatex)
|
|
||||||
.use(rehypeStringify)
|
|
||||||
|
|
||||||
const htmlContent = ref<string>("")
|
const htmlContent = ref<string>("")
|
||||||
|
|
||||||
function handleReaction(symbol: string, attitude: number, delta: number) {
|
function handleReaction(symbol: string, attitude: number, delta: number) {
|
||||||
// Update the local item data
|
emit('react', symbol, attitude, delta)
|
||||||
if (!props.item) return
|
|
||||||
|
|
||||||
const reactions = (props.item as any).reactions || {}
|
|
||||||
const currentCount = reactions[symbol] || 0
|
|
||||||
const newCount = Math.max(0, currentCount + delta)
|
|
||||||
|
|
||||||
if (newCount === 0) {
|
|
||||||
delete reactions[symbol]
|
|
||||||
} else {
|
|
||||||
reactions[symbol] = newCount
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the reactionsMade status
|
|
||||||
const reactionsMade = (props.item as any).reactionsMade || {}
|
|
||||||
if (delta > 0) {
|
|
||||||
reactionsMade[symbol] = true
|
|
||||||
} else {
|
|
||||||
delete reactionsMade[symbol]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the item object (this will trigger reactivity)
|
|
||||||
;(props.item as any).reactions = { ...reactions }
|
|
||||||
;(props.item as any).reactionsMade = { ...reactionsMade }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
props.item,
|
props.item,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (value.content)
|
if (value.content)
|
||||||
htmlContent.value = String(processor.processSync(value.content))
|
htmlContent.value = render(value.content)
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
)
|
)
|
||||||
|
|||||||
22
app/composables/useMarkdownProcessor.ts
Normal file
22
app/composables/useMarkdownProcessor.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { createMarkdownExit } from "markdown-exit"
|
||||||
|
// @ts-ignore
|
||||||
|
import texmath from "markdown-it-texmath"
|
||||||
|
import katex from "katex"
|
||||||
|
|
||||||
|
export function useMarkdownProcessor() {
|
||||||
|
const processor = createMarkdownExit({
|
||||||
|
breaks: true,
|
||||||
|
html: true,
|
||||||
|
linkify: true,
|
||||||
|
typographer: true
|
||||||
|
// @ts-ignore
|
||||||
|
}).use(texmath, {
|
||||||
|
engine: katex,
|
||||||
|
delimiters: 'dollars',
|
||||||
|
katexOptions: { macros: { "\\RR": "\\mathbb{R}" } }
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
render: (content: string) => processor.render(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app :theme="colorMode.preference">
|
<v-app :theme="colorMode.preference">
|
||||||
<v-app-bar flat class="app-bar-blur">
|
<v-app-bar elevation="2" color="surface-lighten-5">
|
||||||
<v-container class="mx-auto d-flex align-center justify-center">
|
<v-container class="mx-auto d-flex align-center justify-center">
|
||||||
<img
|
<img
|
||||||
:src="colorMode.value == 'dark' ? IconDark : IconLight"
|
:src="colorMode.value == 'dark' ? IconDark : IconLight"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
|
||||||
<v-menu>
|
<v-menu>
|
||||||
<template v-slot:activator="{ props }">
|
<template #activator="{ props }">
|
||||||
<v-avatar
|
<v-avatar
|
||||||
v-bind="props"
|
v-bind="props"
|
||||||
class="me-4"
|
class="me-4"
|
||||||
@@ -82,22 +82,3 @@ const links: NavLink[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.app-bar-blur {
|
|
||||||
-webkit-mask-image: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
rgba(0, 0, 0, 1) 40%,
|
|
||||||
rgba(0, 0, 0, 0.5) 65%,
|
|
||||||
rgba(0, 0, 0, 0) 100%
|
|
||||||
);
|
|
||||||
mask-image: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
rgba(0, 0, 0, 1) 40%,
|
|
||||||
rgba(0, 0, 0, 0.5) 65%,
|
|
||||||
rgba(0, 0, 0, 0) 100%
|
|
||||||
);
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -206,14 +206,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
import { unified } from "unified"
|
import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
|
||||||
import remarkParse from "remark-parse"
|
|
||||||
import remarkMath from "remark-math"
|
|
||||||
import remarkRehype from "remark-rehype"
|
|
||||||
import rehypeKatex from "rehype-katex"
|
|
||||||
import rehypeStringify from "rehype-stringify"
|
|
||||||
import remarkBreaks from "remark-breaks"
|
|
||||||
import remarkGfm from "remark-gfm"
|
|
||||||
import type { SnPost } from "~/types/api"
|
import type { SnPost } from "~/types/api"
|
||||||
|
|
||||||
import PostHeader from "~/components/PostHeader.vue"
|
import PostHeader from "~/components/PostHeader.vue"
|
||||||
@@ -223,16 +216,9 @@ import PostReactionList from "~/components/PostReactionList.vue"
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const id = route.params.id as string
|
const id = route.params.id as string
|
||||||
|
|
||||||
const processor = unified()
|
const { render } = useMarkdownProcessor()
|
||||||
.use(remarkParse)
|
|
||||||
.use(remarkMath)
|
|
||||||
.use(remarkBreaks)
|
|
||||||
.use(remarkGfm)
|
|
||||||
.use(remarkRehype)
|
|
||||||
.use(rehypeKatex)
|
|
||||||
.use(rehypeStringify)
|
|
||||||
|
|
||||||
const apiServer = useSolarNetwork(true)
|
const apiServer = useSolarNetwork()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: postData,
|
data: postData,
|
||||||
@@ -244,7 +230,7 @@ const {
|
|||||||
const post = resp as SnPost
|
const post = resp as SnPost
|
||||||
let html = ""
|
let html = ""
|
||||||
if (post.content) {
|
if (post.content) {
|
||||||
html = String(processor.processSync(post.content))
|
html = render(post.content)
|
||||||
}
|
}
|
||||||
return { post, html }
|
return { post, html }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -314,7 +300,7 @@ function handleReaction(symbol: string, attitude: number, delta: number) {
|
|||||||
if (!post.value) return
|
if (!post.value) return
|
||||||
|
|
||||||
// Update the reactions count
|
// Update the reactions count
|
||||||
const reactions = (post.value as any).reactions || {}
|
const reactions = post.value.reactionsCount || {}
|
||||||
const currentCount = reactions[symbol] || 0
|
const currentCount = reactions[symbol] || 0
|
||||||
const newCount = Math.max(0, currentCount + delta)
|
const newCount = Math.max(0, currentCount + delta)
|
||||||
|
|
||||||
@@ -325,7 +311,7 @@ function handleReaction(symbol: string, attitude: number, delta: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the reactionsMade status
|
// Update the reactionsMade status
|
||||||
const reactionsMade = (post.value as any).reactionsMade || {}
|
const reactionsMade = post.value.reactionsMade || {}
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
reactionsMade[symbol] = true
|
reactionsMade[symbol] = true
|
||||||
} else {
|
} else {
|
||||||
@@ -333,7 +319,7 @@ function handleReaction(symbol: string, attitude: number, delta: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the post object
|
// Update the post object
|
||||||
;(post.value as any).reactions = reactions
|
post.value.reactionsCount = reactions
|
||||||
;(post.value as any).reactionsMade = reactionsMade
|
post.value.reactionsMade = reactionsMade
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -36,7 +36,13 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
user.value = response
|
user.value = response
|
||||||
console.log(`[UserStore] Logged in as @${user.value.name}`)
|
console.log(`[UserStore] Logged in as @${user.value.name}`)
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
if (e instanceof FetchError && e.statusCode == 401) {
|
// Check for 401 Unauthorized error
|
||||||
|
const is401Error = (e instanceof FetchError && e.statusCode === 401) ||
|
||||||
|
(e && typeof e === 'object' && 'status' in e && (e as { status: number }).status === 401) ||
|
||||||
|
(e && typeof e === 'object' && 'statusCode' in e && (e as { statusCode: number }).statusCode === 401) ||
|
||||||
|
(e instanceof Error && (e.message?.includes('401') || e.message?.includes('Unauthorized')))
|
||||||
|
|
||||||
|
if (is401Error) {
|
||||||
error.value = "Unauthorized"
|
error.value = "Unauthorized"
|
||||||
user.value = null
|
user.value = null
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
10
app/types/markdown-it-texmath.d.ts
vendored
Normal file
10
app/types/markdown-it-texmath.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
declare module 'markdown-it-texmath' {
|
||||||
|
interface TexMathOptions {
|
||||||
|
engine?: any
|
||||||
|
delimiters?: string
|
||||||
|
katexOptions?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
function texmath(options?: TexMathOptions): any
|
||||||
|
export default texmath
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ export default withNuxt(
|
|||||||
"vue/multi-word-component-names": "off",
|
"vue/multi-word-component-names": "off",
|
||||||
"vue/no-v-html": "off",
|
"vue/no-v-html": "off",
|
||||||
"vue/html-self-closing": "off",
|
"vue/html-self-closing": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off"
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
"@typescript-eslint/no-dynamic-delete": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
34
package.json
34
package.json
@@ -19,39 +19,31 @@
|
|||||||
"@nuxtjs/color-mode": "3.5.2",
|
"@nuxtjs/color-mode": "3.5.2",
|
||||||
"@nuxtjs/i18n": "10.1.0",
|
"@nuxtjs/i18n": "10.1.0",
|
||||||
"@pinia/nuxt": "0.11.2",
|
"@pinia/nuxt": "0.11.2",
|
||||||
"@tailwindcss/typography": "^0.5.18",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
"blurhash": "^2.0.5",
|
"blurhash": "^2.0.5",
|
||||||
"cfturnstile-vue3": "^2.0.0",
|
"cfturnstile-vue3": "^2.0.0",
|
||||||
"eslint": "^9.36.0",
|
"eslint": "^9.39.1",
|
||||||
"katex": "^0.16.22",
|
"katex": "^0.16.25",
|
||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"nuxt": "^4.1.2",
|
"markdown-exit": "^1.0.0-beta.6",
|
||||||
"nuxt-og-image": "^5.1.11",
|
"markdown-it-texmath": "^1.0.0",
|
||||||
|
"nuxt": "^4.2.0",
|
||||||
|
"nuxt-og-image": "^5.1.12",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"rehype-katex": "^7.0.1",
|
|
||||||
"rehype-stringify": "^10.0.1",
|
|
||||||
"remark": "^15.0.1",
|
|
||||||
"remark-breaks": "^4.0.0",
|
|
||||||
"remark-gfm": "^4.0.1",
|
|
||||||
"remark-html": "^16.0.1",
|
|
||||||
"remark-math": "^6.0.0",
|
|
||||||
"remark-parse": "^11.0.0",
|
|
||||||
"remark-rehype": "^11.1.2",
|
|
||||||
"sharp": "^0.34.4",
|
"sharp": "^0.34.4",
|
||||||
"swagger-themes": "^1.4.3",
|
"swagger-themes": "^1.4.3",
|
||||||
"swagger-ui-dist": "^5.29.0",
|
"swagger-ui-dist": "^5.30.2",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.16",
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.3.1",
|
||||||
"unified": "^11.0.5",
|
"vue": "^3.5.22",
|
||||||
"vue": "^3.5.21",
|
"vue-router": "^4.6.3",
|
||||||
"vue-router": "^4.5.1",
|
|
||||||
"vuetify-nuxt-module": "0.18.7"
|
"vuetify-nuxt-module": "0.18.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
"@types/luxon": "^3.7.1",
|
"@types/luxon": "^3.7.1",
|
||||||
"@types/node": "^24.5.2"
|
"@types/node": "^24.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user