🚨 Clean up eslint issues

This commit is contained in:
2025-11-08 12:20:58 +08:00
parent 05f8cabb33
commit 063faf4b8e
17 changed files with 145 additions and 171 deletions

View File

@@ -43,3 +43,7 @@ body {
opacity: 0; opacity: 0;
filter: blur(1rem); filter: blur(1rem);
} }
.prose pre {
padding: 0;
}

View File

@@ -58,7 +58,6 @@ const props = defineProps<{
maxHeight?: number maxHeight?: number
}>() }>()
const apiBase = useSolarNetworkUrl()
const isAllImages = computed( const isAllImages = computed(
() => () =>
@@ -194,8 +193,4 @@ function calculateAspectRatio(): number {
? (mostFrequent[mid - 1]! + mostFrequent[mid]!) / 2 ? (mostFrequent[mid - 1]! + mostFrequent[mid]!) / 2
: mostFrequent[mid]! : mostFrequent[mid]!
} }
function getAttachmentUrl(attachment: SnAttachment): string {
return `${apiBase}/drive/files/${attachment.id}`
}
</script> </script>

View File

@@ -4,49 +4,23 @@ import { useSiteConfig } from "#site-config/app/composables"
import { computed, defineComponent, h, resolveComponent } from "vue" import { computed, defineComponent, h, resolveComponent } from "vue"
const props = defineProps({ const props = defineProps({
colorMode: { type: String, required: false }, colorMode: { type: String, required: false, default: "light" },
title: { type: String, required: false, default: "title" }, title: { type: String, required: false, default: "title" },
description: { type: String, required: false }, description: { type: String, required: false, default: null },
icon: { type: [String, Boolean], required: false }, icon: { type: [String, Boolean], required: false, default: null },
siteName: { type: String, required: false }, siteName: { type: String, required: false, default: null },
siteLogo: { type: String, required: false }, siteLogo: { type: String, required: false, default: null },
theme: { type: String, required: false, default: "#3f51b5" }, theme: { type: String, required: false, default: "#3f51b5" },
backgroundImage: { type: String, required: false }, backgroundImage: { type: String, required: false, default: null },
avatarUrl: { type: String, required: false } avatarUrl: { type: String, required: false, default: null }
}) })
const HexRegex = /^#(?:[0-9a-f]{3}){1,2}$/i
const runtimeConfig = useOgImageRuntimeConfig() const runtimeConfig = useOgImageRuntimeConfig()
const colorMode = computed(() => { const colorMode = computed(() => {
return props.colorMode || runtimeConfig.colorPreference || "light" return props.colorMode || runtimeConfig.colorPreference || "light"
}) })
const themeHex = computed(() => {
if (HexRegex.test(props.theme)) return props.theme
if (HexRegex.test(`#${props.theme}`)) return `#${props.theme}`
if (props.theme.startsWith("rgb")) {
const rgb = props.theme
.replace("rgb(", "")
.replace("rgba(", "")
.replace(")", "")
.split(",")
.map((v) => Number.parseInt(v.trim(), 10))
const hex = rgb
.map((v) => {
const hex2 = v.toString(16)
return hex2.length === 1 ? `0${hex2}` : hex2
})
.join("")
return `#${hex}`
}
return "#FFFFFF"
})
const themeRgb = computed(() => {
return themeHex.value
.replace("#", "")
.match(/.{1,2}/g)
?.map((v) => Number.parseInt(v, 16))
.join(", ")
})
const textShadow = computed(() => { const textShadow = computed(() => {
return '2px 2px 8px rgba(0,0,0,0.8)' return '2px 2px 8px rgba(0,0,0,0.8)'
}) })
@@ -54,9 +28,7 @@ const siteConfig = useSiteConfig()
const siteName = computed(() => { const siteName = computed(() => {
return props.siteName || siteConfig.name return props.siteName || siteConfig.name
}) })
const siteLogo = computed(() => {
return props.siteLogo || siteConfig.logo
})
const IconComponent = runtimeConfig.hasNuxtIcon const IconComponent = runtimeConfig.hasNuxtIcon
? resolveComponent("Icon") ? resolveComponent("Icon")
: defineComponent({ : defineComponent({
@@ -67,7 +39,7 @@ const IconComponent = runtimeConfig.hasNuxtIcon
if ( if (
typeof props.icon === "string" && typeof props.icon === "string" &&
!runtimeConfig.hasNuxtIcon && !runtimeConfig.hasNuxtIcon &&
process.dev import.meta.dev
) { ) {
console.warn( console.warn(
"Please install `@nuxt/icon` to use icons with the fallback OG Image component." "Please install `@nuxt/icon` to use icons with the fallback OG Image component."

View File

@@ -3,14 +3,14 @@ import { useOgImageRuntimeConfig } from "#og-image/app/utils"
import { useSiteConfig } from "#site-config/app/composables" import { useSiteConfig } from "#site-config/app/composables"
import { computed, defineComponent, h, resolveComponent } from "vue" import { computed, defineComponent, h, resolveComponent } from "vue"
const props = defineProps({ const props = defineProps({
colorMode: { type: String, required: false }, colorMode: { type: String, required: false, default: "light" },
title: { type: String, required: false, default: "title" }, title: { type: String, required: false, default: "title" },
description: { type: String, required: false }, description: { type: String, required: false, default: null },
icon: { type: [String, Boolean], required: false }, icon: { type: [String, Boolean], required: false, default: null },
siteName: { type: String, required: false }, siteName: { type: String, required: false, default: null },
siteLogo: { type: String, required: false }, siteLogo: { type: String, required: false, default: null },
theme: { type: String, required: false, default: "#3f51b5" }, theme: { type: String, required: false, default: "#3f51b5" },
backgroundImage: { type: String, required: false } backgroundImage: { type: String, required: false, default: null }
}) })
const HexRegex = /^#(?:[0-9a-f]{3}){1,2}$/i const HexRegex = /^#(?:[0-9a-f]{3}){1,2}$/i
const runtimeConfig = useOgImageRuntimeConfig() const runtimeConfig = useOgImageRuntimeConfig()
@@ -62,7 +62,7 @@ const IconComponent = runtimeConfig.hasNuxtIcon
if ( if (
typeof props.icon === "string" && typeof props.icon === "string" &&
!runtimeConfig.hasNuxtIcon && !runtimeConfig.hasNuxtIcon &&
process.dev import.meta.dev
) { ) {
console.warn( console.warn(
"Please install `@nuxt/icon` to use icons with the fallback OG Image component." "Please install `@nuxt/icon` to use icons with the fallback OG Image component."

View File

@@ -20,7 +20,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import * as tus from 'tus-js-client'
import { useSolarNetwork } from '~/composables/useSolarNetwork' import { useSolarNetwork } from '~/composables/useSolarNetwork'
// Interface for uploaded files in the editor // Interface for uploaded files in the editor
@@ -35,7 +34,6 @@ const emits = defineEmits(['posted'])
const publisher = ref<string | undefined>() const publisher = ref<string | undefined>()
const content = ref('') const content = ref('')
const selectedFiles = ref<File[]>([])
const fileList = ref<UploadedFile[]>([]) const fileList = ref<UploadedFile[]>([])
const submitting = ref(false) const submitting = ref(false)
@@ -61,54 +59,4 @@ async function submit() {
fileList.value = [] fileList.value = []
emits('posted') emits('posted')
} }
function handleFileSelect() {
selectedFiles.value.forEach(file => {
uploadFile(file)
})
selectedFiles.value = []
}
function uploadFile(file: File) {
const upload = new tus.Upload(file, {
endpoint: '/cgi/drive/tus',
retryDelays: [0, 3000, 5000, 10000, 20000],
removeFingerprintOnSuccess: false,
uploadDataDuringCreation: false,
metadata: {
filename: file.name,
'content-type': file.type ?? 'application/octet-stream',
},
headers: {
'X-DirectUpload': 'true',
},
onShouldRetry: () => false,
onError: function (error) {
console.error('[DRIVE] Upload failed:', error)
},
onProgress: function (_bytesUploaded, _bytesTotal) {
// Could show progress
},
onSuccess: function (payload) {
const rawInfo = payload.lastResponse.getHeader('x-fileinfo')
const jsonInfo = JSON.parse(rawInfo as string)
console.log('[DRIVE] Upload successful: ', jsonInfo)
fileList.value.push({
name: file.name,
url: `/cgi/drive/files/${jsonInfo.id}`,
type: jsonInfo.mime_type,
})
},
onBeforeRequest: function (req) {
const xhr = req.getUnderlyingObject()
xhr.withCredentials = true
},
})
upload.findPreviousUploads().then(function (previousUploads) {
if (previousUploads.length > 0 && previousUploads[0]) {
upload.resumeFromPreviousUpload(previousUploads[0])
}
upload.start()
})
}
</script> </script>

View File

@@ -62,7 +62,7 @@ const router = useRouter()
const props = defineProps<{ const props = defineProps<{
params: RepliesListParams params: RepliesListParams
hideQuickReply: boolean hideQuickReply?: boolean
}>() }>()
defineEmits<{ defineEmits<{

View File

@@ -1,4 +1,8 @@
import { createMarkdownExit, type PluginWithParams } from "markdown-exit" import {
createMarkdownExit,
type PluginSimple,
type PluginWithParams
} from "markdown-exit"
import hljs from "highlight.js" import hljs from "highlight.js"
import hljsMarkdown from "markdown-it-highlightjs" import hljsMarkdown from "markdown-it-highlightjs"
// @ts-ignore // @ts-ignore
@@ -7,7 +11,11 @@ import katex from "katex"
import "highlight.js/styles/a11y-dark.min.css" import "highlight.js/styles/a11y-dark.min.css"
export function useMarkdownProcessor() { export function useMarkdownProcessor(
{ preserveEmptyLines }: { preserveEmptyLines?: boolean } = {
preserveEmptyLines: true
}
) {
const serverUrl = useSolarNetworkUrl() const serverUrl = useSolarNetworkUrl()
const processor = createMarkdownExit({ const processor = createMarkdownExit({
@@ -25,12 +33,22 @@ export function useMarkdownProcessor() {
.use(hljsMarkdown, { hljs }) .use(hljsMarkdown, { hljs })
.use(imgSolarNetworkPlugin, { serverUrl: serverUrl }) .use(imgSolarNetworkPlugin, { serverUrl: serverUrl })
// Keep the empty lines if (preserveEmptyLines) {
processor.use(preserveEmptyLinesPlugin)
}
return {
render: (content: string) => processor.render(content)
}
}
const preserveEmptyLinesPlugin: PluginSimple = (md) => {
const defaultParagraphRenderer = const defaultParagraphRenderer =
processor.renderer.rules.paragraph_open || md.renderer.rules.paragraph_open ||
((tokens, idx, options, _env, self) => ((tokens, idx, options, _env, self) =>
self.renderToken(tokens, idx, options)) self.renderToken(tokens, idx, options))
processor.renderer.rules.paragraph_open = function (
md.renderer.rules.paragraph_open = function (
tokens, tokens,
idx, idx,
options, options,
@@ -58,10 +76,6 @@ export function useMarkdownProcessor() {
} }
return result + defaultParagraphRenderer(tokens, idx, options, env, self) return result + defaultParagraphRenderer(tokens, idx, options, env, self)
} }
return {
render: (content: string) => processor.render(content)
}
} }
const imgSolarNetworkPlugin: PluginWithParams = ( const imgSolarNetworkPlugin: PluginWithParams = (

View File

@@ -74,18 +74,18 @@
<v-btn <v-btn
color="primary" color="primary"
:loading="isAuthorizing" :loading="isAuthorizing"
@click="handleAuthorize"
class="flex-grow-1" class="flex-grow-1"
size="large" size="large"
@click="handleAuthorize"
> >
Authorize Authorize
</v-btn> </v-btn>
<v-btn <v-btn
variant="outlined" variant="outlined"
:disabled="isAuthorizing" :disabled="isAuthorizing"
@click="handleDeny"
class="flex-grow-1" class="flex-grow-1"
size="large" size="large"
@click="handleDeny"
> >
Deny Deny
</v-btn> </v-btn>
@@ -140,9 +140,10 @@ async function fetchClientInfo() {
const queryString = window.location.search.slice(1) const queryString = window.location.search.slice(1)
clientInfo.value = await api(`/id/auth/open/authorize?${queryString}`) clientInfo.value = await api(`/id/auth/open/authorize?${queryString}`)
checkIfNewApp() checkIfNewApp()
} catch (err: any) { } catch (err) {
error.value = error.value =
err.message || "An error occurred while loading the authorization request" (err instanceof Error ? err.message : String(err)) ||
"An error occurred while loading the authorization request"
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
@@ -171,8 +172,10 @@ async function handleAuthorize(authorize = true) {
if (data.redirectUri) { if (data.redirectUri) {
window.location.href = data.redirectUri window.location.href = data.redirectUri
} }
} catch (err: any) { } catch (err) {
error.value = err.message || "An error occurred during authorization" error.value =
(err instanceof Error ? err.message : String(err)) ||
"An error occurred during authorization"
} finally { } finally {
isAuthorizing.value = false isAuthorizing.value = false
} }

View File

@@ -439,7 +439,7 @@ function handleTouchMove(event: TouchEvent) {
zoomLevel.value = Math.max(0.1, Math.min(5, scale)) zoomLevel.value = Math.max(0.1, Math.min(5, scale))
} }
function handleTouchEnd(event: TouchEvent) { function handleTouchEnd(_event: TouchEvent) {
if (fileType.value !== "image") return if (fileType.value !== "image") return
isPinching.value = false isPinching.value = false

View File

@@ -47,8 +47,6 @@ import type { SnVersion, SnActivity } from "~/types/api"
import PostEditor from "~/components/Post/PostEditor.vue" import PostEditor from "~/components/Post/PostEditor.vue"
import PostItem from "~/components/Post/PostItem.vue" import PostItem from "~/components/Post/PostItem.vue"
import IconLight from "~/assets/images/cloudy-lamb.png"
const router = useRouter() const router = useRouter()
useHead({ useHead({

View File

@@ -7,13 +7,13 @@
class="pa-2" class="pa-2"
> >
<v-card-text> <v-card-text>
<v-alert type="success" v-if="done" class="mb-4"> <v-alert v-if="done" type="success" class="mb-4">
The order has been paid successfully. Now you can close this tab and The order has been paid successfully. Now you can close this tab and
back to the Solar Network! back to the Solar Network!
</v-alert> </v-alert>
<v-alert <v-alert
type="error"
v-else-if="!!error" v-else-if="!!error"
type="error"
title="Something went wrong" title="Something went wrong"
class="mb-4" class="mb-4"
>{{ error }}</v-alert >{{ error }}</v-alert
@@ -31,7 +31,7 @@
<span>Amount</span> <span>Amount</span>
<strong>{{ order.amount }} {{ order.currency }}</strong> <strong>{{ order.amount }} {{ order.currency }}</strong>
</div> </div>
<div class="d-flex align-center gap-2 mb-4" v-if="order.expiredAt"> <div v-if="order.expiredAt" class="d-flex align-center gap-2 mb-4">
<v-icon size="18">mdi-calendar</v-icon> <v-icon size="18">mdi-calendar</v-icon>
<span>Until</span> <span>Until</span>
<strong>{{ new Date(order.expiredAt).toLocaleString() }}</strong> <strong>{{ new Date(order.expiredAt).toLocaleString() }}</strong>
@@ -47,8 +47,8 @@
<v-btn <v-btn
color="primary" color="primary"
:loading="submitting" :loading="submitting"
@click="pay"
class="mt-4" class="mt-4"
@click="pay"
> >
<v-icon left>mdi-check</v-icon> <v-icon left>mdi-check</v-icon>
Pay Pay

View File

@@ -68,7 +68,10 @@
</article> </article>
<!-- Attachments within Content Section --> <!-- Attachments within Content Section -->
<attachment-list v-if="post.type != 1" :attachments="post.attachments || []" /> <attachment-list
v-if="post.type != 1"
:attachments="post.attachments || []"
/>
</v-card> </v-card>
<v-card <v-card
@@ -141,7 +144,8 @@ import { useMarkdownProcessor } from "~/composables/useMarkdownProcessor"
import type { SnPost } from "~/types/api" import type { SnPost } from "~/types/api"
const route = useRoute() const route = useRoute()
const id = route.params.id as string const slugParts = route.params.slug as string[]
const id = slugParts.join("/")
const { render } = useMarkdownProcessor() const { render } = useMarkdownProcessor()
@@ -168,6 +172,16 @@ const {
} }
}) })
if (postData.value?.post) {
const p = postData.value.post
if (p.publisher?.name && p.slug) {
const slugUrl = `/posts/${p.publisher.name}/${p.slug}`
if (route.path !== slugUrl) {
await navigateTo(slugUrl, { redirectCode: 301 })
}
}
}
const post = computed(() => postData.value?.post || null) const post = computed(() => postData.value?.post || null)
const htmlContent = computed(() => postData.value?.html || "") const htmlContent = computed(() => postData.value?.html || "")
@@ -189,6 +203,13 @@ useHead({
return [{ name: "description", content: description }] return [{ name: "description", content: description }]
} }
return [] return []
}),
link: computed(() => {
if (post.value && post.value.publisher?.name && post.value.slug) {
const slugUrl = `/posts/${post.value.publisher.name}/${post.value.slug}`
return [{ rel: "canonical", href: slugUrl }]
}
return []
}) })
}) })
@@ -205,6 +226,8 @@ const userBackground = computed(() => {
) )
return firstImageAttachment return firstImageAttachment
? `${apiBase}/drive/files/${firstImageAttachment.id}` ? `${apiBase}/drive/files/${firstImageAttachment.id}`
: post.value?.publisher.background
? `${apiBase}/drive/files/${post.value?.publisher.background.id}`
: undefined : undefined
}) })
@@ -260,18 +283,18 @@ onMounted(() => {
function makeEmbedImageClickable() { function makeEmbedImageClickable() {
const elements = document.getElementsByClassName("prose-img-solar-network") const elements = document.getElementsByClassName("prose-img-solar-network")
let count = 0; let count = 0
for (const element of elements) { for (const element of elements) {
if (element instanceof HTMLImageElement) { if (element instanceof HTMLImageElement) {
count += 1; count += 1
element.addEventListener("click", (evt) => { element.addEventListener("click", (evt) => {
const targetImg = evt.target as HTMLImageElement const targetImg = evt.target as HTMLImageElement
window.open("/files/" + targetImg.src.split("/").findLast((_) => true)) window.open("/files/" + targetImg.src.split("/").findLast((_) => true))
}) })
element.style['cursor'] = 'pointer'; element.style["cursor"] = "pointer"
} }
} }
console.log(`[Article] Made ${count} image(s) clickable in the article.`); console.log(`[Article] Made ${count} image(s) clickable in the article.`)
} }
</script> </script>

View File

@@ -2,13 +2,13 @@
<div class="d-flex align-center justify-center fill-height"> <div class="d-flex align-center justify-center fill-height">
<v-card max-width="400" title="Magic Spell" prepend-icon="mdi-magic-staff" class="pa-2"> <v-card max-width="400" title="Magic Spell" prepend-icon="mdi-magic-staff" class="pa-2">
<v-card-text> <v-card-text>
<v-alert type="success" v-if="done" class="mb-4"> <v-alert v-if="done" type="success" class="mb-4">
The magic spell has been applied successfully. Now you can close this The magic spell has been applied successfully. Now you can close this
tab and back to the Solar Network! tab and back to the Solar Network!
</v-alert> </v-alert>
<v-alert <v-alert
type="error"
v-else-if="!!error" v-else-if="!!error"
type="error"
title="Something went wrong" title="Something went wrong"
class="mb-4" class="mb-4"
>{{ error }}</v-alert >{{ error }}</v-alert
@@ -28,7 +28,7 @@
new Date(spell.createdAt ?? spell.affectedAt).toLocaleString() new Date(spell.createdAt ?? spell.affectedAt).toLocaleString()
}}</strong> }}</strong>
</div> </div>
<div class="d-flex align-center gap-2 mb-4" v-if="spell.expiredAt"> <div v-if="spell.expiredAt" class="d-flex align-center gap-2 mb-4">
<v-icon size="18">mdi-calendar</v-icon> <v-icon size="18">mdi-calendar</v-icon>
<span>Until</span> <span>Until</span>
<strong>{{ spell.expiredAt.toString() }}</strong> <strong>{{ spell.expiredAt.toString() }}</strong>
@@ -68,7 +68,16 @@ const spellWord: string =
typeof route.params.word === "string" typeof route.params.word === "string"
? route.params.word ? route.params.word
: route.params.word?.join("/") || "" : route.params.word?.join("/") || ""
const spell = ref<any>(null) interface SnSpell {
type: number
account: {
name: string
}
createdAt: string
affectedAt: string
expiredAt?: string
}
const spell = ref<SnSpell | null>(null)
const error = ref<string | null>(null) const error = ref<string | null>(null)
const newPassword = ref<string>() const newPassword = ref<string>()

View File

@@ -13,11 +13,11 @@ export interface SnWalletOrder {
remarks?: string remarks?: string
appIdentifier?: string appIdentifier?: string
productIdentifier?: string productIdentifier?: string
meta?: Record<string, any> meta?: Record<string, unknown>
amount: number amount: number
expiredAt: string expiredAt: string
payeeWalletId?: string payeeWalletId?: string
payeeWallet?: any payeeWallet?: unknown
transactionId?: string transactionId?: string
transaction?: any transaction?: unknown
} }

View File

@@ -1,30 +1,28 @@
import type { SnCloudFile } from './post' import type { SnCloudFile } from "./post"
// Verification interface // Verification interface
export interface SnVerification { export interface SnVerification {
type: number; type: number
title: string; title: string
description: string; description: string
verifiedBy: string; verifiedBy: string
} }
// Publisher interface // Publisher interface
export interface SnPublisher { export interface SnPublisher {
id: string; id: string
type: number; type: number
name: string; name: string
nick: string; nick: string
bio: string; bio: string
pictureId: string; picture: SnCloudFile | null
backgroundId: string; background: SnCloudFile | null
picture: SnCloudFile | null; verification: SnVerification | null
background: SnCloudFile | null; accountId: string
verification: SnVerification | null; realmId: string | null
accountId: string; account: unknown | null
realmId: string | null; resourceIdentifier: string
account: unknown | null; createdAt: string
resourceIdentifier: string; updatedAt: string
createdAt: string; deletedAt: string | null
updatedAt: string;
deletedAt: string | null;
} }

View File

@@ -1,10 +1,14 @@
declare module 'markdown-it-texmath' { import type { PluginSimple } from "markdown-it"
import type katex from "katex"
import type { KatexOptions } from "katex"
declare module "markdown-it-texmath" {
interface TexMathOptions { interface TexMathOptions {
engine?: any engine?: typeof katex
delimiters?: string delimiters?: string
katexOptions?: Record<string, any> katexOptions?: KatexOptions
} }
function texmath(options?: TexMathOptions): any function texmath(options?: TexMathOptions): PluginSimple
export default texmath export default texmath
} }

View File

@@ -1,4 +1,4 @@
declare module 'marked-katex' { declare module "marked-katex" {
interface Options { interface Options {
throwOnError?: boolean throwOnError?: boolean
errorColor?: string errorColor?: string
@@ -7,12 +7,18 @@ declare module 'marked-katex' {
fleqn?: boolean fleqn?: boolean
macros?: Record<string, string> macros?: Record<string, string>
colorIsTextColor?: boolean colorIsTextColor?: boolean
strict?: boolean | 'ignore' | 'warn' | 'error' strict?: boolean | "ignore" | "warn" | "error"
trust?: boolean | ((context: { command: string; url: string; protocol: string }) => boolean) trust?:
output?: 'html' | 'mathml' | 'htmlAndMathml' | boolean
| ((context: {
command: string
url: string
protocol: string
}) => boolean)
output?: "html" | "mathml" | "htmlAndMathml"
} }
function markedKatex(options?: Options): any function markedKatex(options?: Options): object
export default markedKatex export default markedKatex
} }