From 7f8eece848525445fe884346e29b8bc9d46c1076 Mon Sep 17 00:00:00 2001 From: Noro Studio Date: Tue, 30 Jun 2026 20:58:09 +0330 Subject: [PATCH] Add image upload fields, media edit/delete, blog seeds --- src/pages/blog/[slug].vue | 154 ++++++++++++++++++++++++++++---------- src/utils/blogContent.ts | 83 ++++++++++++++++++++ 2 files changed, 199 insertions(+), 38 deletions(-) create mode 100644 src/utils/blogContent.ts diff --git a/src/pages/blog/[slug].vue b/src/pages/blog/[slug].vue index 7149405..1ac8afc 100644 --- a/src/pages/blog/[slug].vue +++ b/src/pages/blog/[slug].vue @@ -1,9 +1,14 @@ @@ -53,19 +101,13 @@ useHead({ بازگشت به وبلاگ -

+

{{ formatDate(post.publishedAt) }}

{{ post.title }}

-

+

{{ post.excerpt }}

@@ -73,13 +115,10 @@ useHead({
-
-
+
+
-

- {{ paragraph }} -

+
-
+

- برای پروژه بعدی آماده‌اید؟ + برای طراحی لوگوی حرفه‌ای آماده‌اید؟

- با ما در تماس باشید تا درباره نیازهای برند و محتوای شما صحبت کنیم. + با استودیو نورو در تماس باشید تا لوگویی متناسب با هویت برند شما طراحی کنیم.

تماس با ما diff --git a/src/utils/blogContent.ts b/src/utils/blogContent.ts new file mode 100644 index 0000000..7f62d8d --- /dev/null +++ b/src/utils/blogContent.ts @@ -0,0 +1,83 @@ +export type BlogContentBlock = + | { type: 'h2'; text: string } + | { type: 'h3'; text: string } + | { type: 'h4'; text: string } + | { type: 'p'; text: string } + | { type: 'ul'; items: string[] } + | { type: 'img'; src: string; alt: string } + +const imagePattern = /^!\[(.*?)\]\((.*?)\)$/ +const headingPattern = /^(#{2,4})\s+(.*)$/ + +export function parseBlogContent(content: string, apiBase = ''): BlogContentBlock[] { + const blocks: BlogContentBlock[] = [] + const lines = content.replace(/\r\n/g, '\n').split('\n') + let paragraph: string[] = [] + let listItems: string[] = [] + + const resolve = (url: string) => { + if (!url) return '' + if (/^https?:\/\//.test(url) || url.startsWith('data:')) return url + const base = apiBase.replace(/\/$/, '') + return url.startsWith('/') ? `${base}${url}` : `${base}/${url}` + } + + const flushParagraph = () => { + const text = paragraph.join('\n').trim() + if (text) blocks.push({ type: 'p', text }) + paragraph = [] + } + + const flushList = () => { + if (listItems.length) blocks.push({ type: 'ul', items: [...listItems] }) + listItems = [] + } + + for (const rawLine of lines) { + const line = rawLine.trimEnd() + const trimmed = line.trim() + + if (!trimmed) { + flushParagraph() + flushList() + continue + } + + const imageMatch = trimmed.match(imagePattern) + if (imageMatch) { + flushParagraph() + flushList() + blocks.push({ + type: 'img', + alt: imageMatch[1] || '', + src: resolve(imageMatch[2] || ''), + }) + continue + } + + const headingMatch = trimmed.match(headingPattern) + if (headingMatch) { + flushParagraph() + flushList() + const level = headingMatch[1].length + const text = headingMatch[2].trim() + if (level === 2) blocks.push({ type: 'h2', text }) + else if (level === 3) blocks.push({ type: 'h3', text }) + else blocks.push({ type: 'h4', text }) + continue + } + + if (trimmed.startsWith('* ')) { + flushParagraph() + listItems.push(trimmed.slice(2).trim()) + continue + } + + flushList() + paragraph.push(trimmed) + } + + flushParagraph() + flushList() + return blocks +}