-
-
Notifications
You must be signed in to change notification settings - Fork 16
Just the beginning #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughRenames “welcome” config to “home,” enables i18n with new en/vi resources, adds a new Home page and BackToTop component, introduces a PDF viewer and My CV page, updates navbar with theme/lang switchers and routes, adds multiple content posts, adjusts styles/layouts, updates schema/util/types, and adds PM2 config. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant W as Window
participant B as AwesomeBackToTop
Note over B: On mount
B->>W: addEventListener('scroll', handler)
U->>W: Scroll
W-->>B: pageYOffset
alt pageYOffset > 10
B->>B: Show button (block)
else
B->>B: Hide button (hidden)
end
U->>B: Click button
B->>W: window.scrollTo({ top: 0, behavior: 'smooth' })
sequenceDiagram
autonumber
participant U as User
participant N as Navbar Lang ThemeSwitcher
participant I as i18n (locale)
U->>N: Open language dropdown
N->>I: v-model locale (get current)
U->>N: Select vi/en
N->>I: Update locale
I-->>N: Rerender with new labels
I-->>UI: UI texts update via $t(...)
sequenceDiagram
autonumber
participant U as User
participant P as /mycv Page
participant V as PdfViewer
participant PJ as pdfjs-dist
U->>P: Navigate to /mycv
P->>V: Async import (client-only)
V->>PJ: Set workerSrc
V->>PJ: getDocument(mycv.pdf)
PJ-->>V: PDFDocumentProxy
V->>V: viewer.setDocument(...)
V-->>U: Render PDF pages
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 25
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/layouts/Page/Navbar.vue (1)
147-147: Configure/blog→/postredirect
No/blogreferences detected; add a redirect in your router config so existing bookmarks and external links to/blogcontinue to work.
🧹 Nitpick comments (15)
components/layouts/Page/Content.vue (1)
4-4: Consider retaining the CSS variable for maintainability.Replacing
var(--layout-page-content-min-height)with a hardcodedcalc(100vh - 120px)reduces flexibility and maintainability. If layout heights change, this value must be manually updated across the codebase.If the CSS variable is no longer needed elsewhere, at minimum document the magic number
120px:<!-- 120px = header height (e.g., 64px) + footer height (e.g., 56px) --> <div class="max-w-6xl m-auto pt-[24px] lg:pt-[64px]" :style="{ 'min-height': 'calc(100vh - 120px)' }" >Alternatively, restore the CSS variable approach for better maintainability:
- :style="{ 'min-height': 'calc(100vh - 120px)' }" + :style="{ 'min-height': 'var(--layout-page-content-min-height)' }"components/awesome/BackToTop.vue (1)
15-15: Remove unused variable.The
nuxtAppvariable is declared but never used in this component.Apply this diff to remove it:
-const nuxtApp = useNuxtApp() const scrolltoTop = () => {app/content/feel/3-cach-tro-nen-phien-ban-tot-nhat-cua-chinh-minh.md (1)
9-23: Use proper heading syntax for better document structure.The section titles are currently styled with bold text (
**text**) instead of using proper Markdown heading syntax. This affects document accessibility and semantic structure.Apply this diff to use proper headings:
-**Sống đơn giản và thanh tao** +## Sống đơn giản và thanh tao Ngày nay ít có ai thích thưởng trà và bàn thơ ca... -**Hãy nói lời yêu thương** +## Hãy nói lời yêu thương Bạn biết không cuộc đời này là vô thường... -**Suy nghĩ tích cực** +## Suy nghĩ tích cực Bạn đã bao giờ nghe câu nói...components/layouts/Page/PdfViewer.vue (2)
20-20: Consider making PDF path configurable.The PDF path is hardcoded to
'./mycv.pdf'. Making this a prop would improve component reusability.Consider adding a prop:
+const props = defineProps({ + pdfUrl: { + type: String, + default: './mycv.pdf' + } +}) + const getPdf = async () => { const container = document.getElementById('pageContainer') as any const pdfViewer = new PDFViewer({ container, }) - const loadingTask = pdfjsLib.getDocument('./mycv.pdf') + const loadingTask = pdfjsLib.getDocument(props.pdfUrl) const pdf = await loadingTask.promise
36-37: Fix formatting issue.Extra blank line detected by prettier.
Apply this diff:
.canvasWrapper { width: 100%; - } }README.md (1)
34-34: Fix heading level progression.The heading jumps from h2 (##) to h4 (####), skipping h3. This breaks proper document hierarchy.
Apply this diff:
-#### Using with Direct Clone +### Using with Direct Clonecomponents/awesome/SVGComponent.vue (1)
1-32: Propssizeandcolorare defined but not used.The component defines
sizeandcolorprops with defaults, but these props are never applied to the SVG element. OnlyclassNameandlabelare actually used.Either remove the unused props or apply them to the SVG:
Option 1: Remove unused props
interface IconProps { - // Icon size in pixels - size?: number | string - // Tailwind color class (e.g. 'blue-500') - color?: string // Additional classes className?: string // Accessibility label label?: string } const props = withDefaults(defineProps<IconProps>(), { - size: 24, - color: '', className: '', label: '', })Option 2: Apply props to the SVG
-<svg viewBox="0 0 24 24" :aria-label="props.label" :class="props.className"> +<svg + viewBox="0 0 24 24" + :aria-label="props.label" + :class="[props.className, props.color]" + :width="props.size" + :height="props.size" +>app/content/project/my-portfolio-and-blog.md (2)
47-53: Fix markdownlint MD034: wrap bare URLs with links.Convert bare URLs to proper Markdown links.
4-5: Prefer ISO date; verify hero image extension.
- Use ISO-8601 for Content sorting/consistency.
- Image path likely should be
.webp(site uses webp elsewhere).Please confirm
/public/images/project-1.webpexists; otherwise keep.png.components/awesome/Home.vue (3)
2-2: Avoid timer leaks; clear timeouts on unmount.Store timeout IDs and clear them in
onBeforeUnmount.- import { ref } from 'vue' + import { ref, onBeforeUnmount } from 'vue'- setTimeout(loop, time) + timer = window.setTimeout(loop, time)-setTimeout(() => { - currentPhrase.value = '' - loop() -}, 3000) +startTimer = window.setTimeout(() => { + currentPhrase.value = '' + loop() +}, 3000)Add near the bottom of the script:
let timer: number | undefined let startTimer: number | undefined onBeforeUnmount(() => { if (timer) clearTimeout(timer) if (startTimer) clearTimeout(startTimer) })Also applies to: 42-47
11-11: Make phrases reactive to locale changes.Use a computed array so text updates after switching languages. Based on learnings.
- import { ref } from 'vue' + import { ref, computed } from 'vue'-const phrases = [t('im') + ` Lê Vĩnh Tuyến`, t('iLearnIdeveloper')] +const phrases = computed(() => [t('im') + ` Lê Vĩnh Tuyến`, t('iLearnIdeveloper')])- const currentPhraseText = phrases[currentPhraseIndex.value] + const currentPhraseText = phrases.value[currentPhraseIndex.value]Also applies to: 16-16, 2-2
126-167: Anchor/rel hygiene for external links.
<NuxtLink>renders<a>by default; droptag="a".- For
target="_blank", ensurerel="noopener noreferrer"everywhere.Example change:
-<NuxtLink tag="a" target="_blank" to="https://github.com/..." > +<NuxtLink target="_blank" rel="noopener noreferrer" to="https://github.com/...">Apply similarly across external links in this component.
Also applies to: 168-205, 714-756, 1245-1276, 1284-1317
app.config.ts (1)
2-2: Type-only import for NuxtApp.Avoid emitting a runtime import.
-import { NuxtApp } from '#app' +import type { NuxtApp } from '#app'nuxt.config.ts (1)
82-84: Consider adding more i18n configuration options.The current i18n configuration is functional, but you may want to consider adding additional options for better control:
defaultLocale: to explicitly set the fallback localelocales: array defining available locales with their codes and ISO valuesdetectBrowserLanguage: for automatic locale detectionstrategy: to control URL structure for different localesBased on learnings, these options are commonly used in @nuxtjs/i18n v8 setups.
Example:
i18n: { vueI18n: './i18n.config.ts', defaultLocale: 'vi', locales: [ { code: 'en', iso: 'en-US', name: 'English' }, { code: 'vi', iso: 'vi-VN', name: 'Tiếng Việt' } ], detectBrowserLanguage: { useCookie: true, fallbackLocale: 'vi', } },components/layouts/Page/NavbarDropdownLang/ThemeSwitcher.vue (1)
10-19: Consider renamingavailableThemesto reflect language options.The variable name
availableThemesis misleading since it contains language options (vi, en), not visual themes.-const availableThemes = reactive([ +const availableLanguages = reactive([ { key: 'vi', text: 'VN', }, { key: 'en', text: 'EN', }, ])And update all references throughout the template accordingly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (12)
public/images/feel-10.pngis excluded by!**/*.pngpublic/images/feel-11.pngis excluded by!**/*.pngpublic/images/feel-8.pngis excluded by!**/*.pngpublic/images/feel-9.pngis excluded by!**/*.pngpublic/images/post-6.pngis excluded by!**/*.pngpublic/images/toTop.pngis excluded by!**/*.pngpublic/lang/fr.pngis excluded by!**/*.pngpublic/lang/indo.pngis excluded by!**/*.pngpublic/lang/us.pngis excluded by!**/*.pngpublic/lang/vn.pngis excluded by!**/*.pngpublic/mycv.pdfis excluded by!**/*.pdfyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (35)
README.md(1 hunks)app.config.ts(4 hunks)app/content/feel/3-cach-tro-nen-phien-ban-tot-nhat-cua-chinh-minh.md(1 hunks)app/content/feel/me-oi-con-chenh-venh-mot-ti-thoi.md(1 hunks)app/content/feel/nam-lay-giac-mo-cua-chinh-ban-than-minh.md(1 hunks)app/content/feel/o-pho-thi-hay-ve-voi-que.md(1 hunks)app/content/post/authentication-vs-authorization.md(1 hunks)app/content/project/my-portfolio-and-blog.md(1 hunks)assets/scss/app.scss(4 hunks)components/BackToTop.vue(0 hunks)components/awesome/BackToTop.vue(1 hunks)components/awesome/Content/Doc.vue(1 hunks)components/awesome/Home.vue(1 hunks)components/awesome/SVGComponent.vue(1 hunks)components/awesome/Welcome.vue(4 hunks)components/layouts/Page/Content.vue(1 hunks)components/layouts/Page/Navbar.vue(6 hunks)components/layouts/Page/NavbarDropdownLang/ThemeSwitcher.vue(1 hunks)components/layouts/Page/NavbarDropdownMode/ThemeSwitcher.vue(1 hunks)components/layouts/Page/PdfViewer.vue(1 hunks)ecosystem.config.cjs(1 hunks)i18n.config.ts(1 hunks)lang/en.json(1 hunks)lang/vn.json(1 hunks)layouts/page.vue(1 hunks)nuxt.config.ts(3 hunks)nuxt.schema.ts(1 hunks)package.json(2 hunks)pages/feel/index.vue(2 hunks)pages/index.vue(1 hunks)pages/mycv.vue(1 hunks)pages/post/index.vue(2 hunks)pages/project/index.vue(1 hunks)util/schema.json(2 hunks)utils/types.ts(1 hunks)
💤 Files with no reviewable changes (1)
- components/BackToTop.vue
🧰 Additional context used
🪛 Biome (2.1.2)
ecosystem.config.cjs
[error] 1-2: A global variable should not be reassigned.
Assigning to a global variable can override essential functionality.
(lint/suspicious/noGlobalAssign)
🪛 ESLint
pages/mycv.vue
[error] 2-2: Replace ()·=>·import('@/components/layouts/page/PdfViewer.vue') with ⏎··()·=>·import('@/components/layouts/page/PdfViewer.vue'),⏎
(prettier/prettier)
[error] 10-10: Delete ····
(prettier/prettier)
[error] 11-11: Delete ··
(prettier/prettier)
components/layouts/Page/PdfViewer.vue
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/namespace)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/order)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/default)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/named)
[error] 36-37: Delete ⏎
(prettier/prettier)
components/awesome/Home.vue
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/namespace)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/order)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/named)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/default)
pages/post/index.vue
[error] 72-72: Delete ·
(prettier/prettier)
[error] 81-81: Delete ·
(prettier/prettier)
components/awesome/Welcome.vue
[error] 98-100: Replace ⏎················class="grid-cols-1·md:grid-cols-2·grid·md:flex"⏎·············· with ·class="grid-cols-1·md:grid-cols-2·grid·md:flex"
(prettier/prettier)
i18n.config.ts
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/namespace)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/order)
[error] 1-1: Resolve error: File './app/.nuxt/tsconfig.json' not found.
at resolveExtends (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:6816)
at _parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:7781)
at parseTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:8798)
at Object.getTsconfig (/home/jailuser/git/node_modules/get-tsconfig/dist/index.cjs:7:9346)
at /home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:258:36
at Array.map ()
at initMappers (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:252:26)
at Object.resolve (/home/jailuser/git/node_modules/eslint-import-resolver-typescript/lib/index.cjs:124:3)
at withResolver (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:180:23)
at fullResolve (/home/jailuser/git/node_modules/eslint-module-utils/resolve.js:201:22)
(import/default)
🪛 markdownlint-cli2 (0.18.1)
app/content/project/my-portfolio-and-blog.md
47-47: Bare URL used
(MD034, no-bare-urls)
49-49: Bare URL used
(MD034, no-bare-urls)
51-51: Bare URL used
(MD034, no-bare-urls)
53-53: Bare URL used
(MD034, no-bare-urls)
README.md
34-34: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4
(MD001, heading-increment)
app/content/feel/3-cach-tro-nen-phien-ban-tot-nhat-cua-chinh-minh.md
9-9: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
15-15: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
21-21: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
app/content/post/authentication-vs-authorization.md
37-37: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
62-62: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🔇 Additional comments (20)
package.json (1)
77-77: Consider the trade-offs of pinning the exact Nuxt version.Changing from
^3.8.0to3.8.0removes the ability to receive patch updates automatically. While this provides build stability, it means security patches and bug fixes in 3.8.x releases won't be automatically adopted.If strict version control is required, consider documenting this decision. Otherwise, restore the caret to allow patch updates:
- "nuxt": "3.8.0", + "nuxt": "^3.8.0",components/awesome/Content/Doc.vue (1)
15-18: LGTM!The dark mode styling addition properly handles text contrast in both light and dark themes.
app/content/feel/nam-lay-giac-mo-cua-chinh-ban-than-minh.md (1)
1-23: LGTM!New content file added with proper front matter structure.
util/schema.json (1)
27-27: LGTM!Schema updates align with portfolio content additions and reflect updated contact information.
Also applies to: 54-54
components/awesome/Welcome.vue (2)
657-657: LGTM!Dark mode styling enhancements properly handle theme switching.
Also applies to: 1203-1203
691-691: Verify i18n keys exist in translation files
Ensure 'journey' and 'projects' keys are defined in all JSON files underlang/andpublic/lang/.pages/feel/index.vue (1)
71-116: LGTM! Dark mode styling enhancements.The dark mode classes are consistently applied across the article container, links, and text elements, improving the UI experience in dark mode.
app/content/feel/me-oi-con-chenh-venh-mot-ti-thoi.md (1)
1-39: LGTM! Well-structured narrative content.The content file is properly formatted with appropriate front matter and engaging narrative text.
app.config.ts (1)
96-107: Config rename looks consistent.
homeblock anddisableInfoReplaceIndexInHomePagealign across interface and defaults. LGTM.Also applies to: 116-117
pages/index.vue (1)
10-10: Swap to AwesomeHome is correct.Component auto-import should resolve from components/awesome/Home.vue. LGTM.
layouts/page.vue (1)
8-8: LGTM!The addition of the
<AwesomeBackToTop />component is well-placed after the footer, providing a consistent navigation enhancement across pages.nuxt.schema.ts (1)
23-25: LGTM!The rename from
welcometohomeis consistent and the corresponding flag name has been updated appropriately.lang/vn.json (1)
1-13: LGTM!The Vietnamese translation file is well-structured with all necessary keys defined. The translations appear appropriate for the Vietnamese locale.
app/content/feel/o-pho-thi-hay-ve-voi-que.md (1)
1-15: LGTM!The markdown file is properly structured with valid front matter and appropriate Vietnamese content.
i18n.config.ts (2)
1-2: LGTM!The import statements correctly reference the newly added English and Vietnamese translation files.
5-5: LGTM!The default locale change to Vietnamese (
'vi') aligns with the PR's focus on Vietnamese content and translations.pages/post/index.vue (1)
77-77: LGTM!The dark mode styling additions provide consistent theming across the post index page with appropriate color choices for text and backgrounds.
Also applies to: 96-98, 101-101, 106-106
nuxt.config.ts (1)
67-67: LGTM!The
@nuxtjs/i18nmodule is correctly added to enable internationalization support. Based on learnings, this is the appropriate v8.x module for Nuxt 3 integration.components/layouts/Page/Navbar.vue (2)
2-16: LGTM: i18n integration implemented correctly.The i18n integration follows best practices:
- Proper use of
useI18n()composable from @nuxtjs/i18n- Consistent use of
$t()for translations in the template- Locale-aware menu labels for "My CV" and "My Story"
This aligns well with the i18n configuration and language resources added in this PR.
Based on learnings.
Also applies to: 123-123, 139-139, 227-227, 241-241
114-125: LGTM: New CV menu item with proper i18n and routing.The new "My CV" menu item is well-implemented with:
- Proper i18n binding using
$t('myCV')- Active state styling based on route name
- Consistent structure with other menu items
|
|
||
| # AuthN vs AuthZ: Key Differences and How They Work Together | ||
|
|
||
| ### Verification vs. Permission |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix heading hierarchy violations.
The document jumps from h1 (#) directly to h3 (###) at lines 37 and 62, which violates proper heading structure. Headings should increment by one level at a time for accessibility and document structure.
Apply this diff to fix the heading levels:
-### Verification vs. Permission
+## Verification vs. Permission
Authentication confirms a user's identity ("Who are you?") while authorization focuses on their permissions ("What are you allowed to do?").
-### Sequential Process
+## Sequential Process
Authentication always precedes authorization, as the system must verify a user's identity before determining their permissions.
-### Integrated Security Approach
+## Integrated Security Approachand
-### Scalability and Flexibility
+## Scalability and Flexibility
An authorization system should scale with your application, handling increased traffic and growing permissions without performance loss. Flexibility is equally important, allowing easy permission adjustments as application requirements evolve.
-### Security Best Practices
+## Security Best PracticesBased on static analysis.
Also applies to: 62-62
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
37-37: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3
(MD001, heading-increment)
🤖 Prompt for AI Agents
In app/content/post/authentication-vs-authorization.md around lines 37 and 62
the document jumps from an h1 (#) directly to h3 (###); change those h3 headings
to h2 (##) so headings increment by one level at a time and the document
hierarchy is preserved.
| .hide-scrollbar { | ||
| -ms-overflow-style: none; /* IE and Edge */ | ||
| scrollbar-width: none; /* Firefox */ | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hide scrollbar for WebKit browsers too.
.hide-scrollbar only disables scrollbars in Firefox/Edge; Chrome/Safari still show them because there is no ::-webkit-scrollbar override. Add the WebKit pseudo-element to finish the utility.
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
+.hide-scrollbar::-webkit-scrollbar {
+ display: none; /* Chrome, Safari */
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .hide-scrollbar { | |
| -ms-overflow-style: none; /* IE and Edge */ | |
| scrollbar-width: none; /* Firefox */ | |
| } | |
| .hide-scrollbar { | |
| -ms-overflow-style: none; /* IE and Edge */ | |
| scrollbar-width: none; /* Firefox */ | |
| } | |
| .hide-scrollbar::-webkit-scrollbar { | |
| display: none; /* Chrome, Safari */ | |
| } |
🤖 Prompt for AI Agents
In assets/scss/app.scss around lines 55 to 58, the .hide-scrollbar utility
disables scrollbars for IE/Edge and Firefox but not for WebKit browsers; add a
WebKit pseudo-element rule to hide scrollbars in Chrome/Safari by adding a
.hide-scrollbar::-webkit-scrollbar selector that hides the scrollbar (e.g., set
display: none or width/height: 0) so the utility covers WebKit as well.
| onMounted(() => { | ||
| const topBtn = document.querySelector('#topButton') as any | ||
| window.onscroll = () => { | ||
| if (window.pageYOffset > 10) { | ||
| // unhide | ||
| topBtn.classList.add('block') | ||
| topBtn.classList.remove('hidden') | ||
| } else { | ||
| // hide | ||
| topBtn.classList.remove('block') | ||
| topBtn.classList.add('hidden') | ||
| } | ||
| } | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory leak: scroll listener not cleaned up.
The scroll listener (window.onscroll) is attached in onMounted but never removed when the component unmounts, causing a memory leak. Additionally, directly assigning to window.onscroll overwrites any existing listener.
Apply this diff to properly clean up the listener:
onMounted(() => {
const topBtn = document.querySelector('#topButton') as any
- window.onscroll = () => {
+ const handleScroll = () => {
if (window.pageYOffset > 10) {
// unhide
topBtn.classList.add('block')
topBtn.classList.remove('hidden')
} else {
// hide
topBtn.classList.remove('block')
topBtn.classList.add('hidden')
}
}
+ window.addEventListener('scroll', handleScroll)
+
+ // Cleanup
+ onBeforeUnmount(() => {
+ window.removeEventListener('scroll', handleScroll)
+ })
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| onMounted(() => { | |
| const topBtn = document.querySelector('#topButton') as any | |
| window.onscroll = () => { | |
| if (window.pageYOffset > 10) { | |
| // unhide | |
| topBtn.classList.add('block') | |
| topBtn.classList.remove('hidden') | |
| } else { | |
| // hide | |
| topBtn.classList.remove('block') | |
| topBtn.classList.add('hidden') | |
| } | |
| } | |
| }) | |
| onMounted(() => { | |
| const topBtn = document.querySelector('#topButton') as any | |
| const handleScroll = () => { | |
| if (window.pageYOffset > 10) { | |
| // unhide | |
| topBtn.classList.add('block') | |
| topBtn.classList.remove('hidden') | |
| } else { | |
| // hide | |
| topBtn.classList.remove('block') | |
| topBtn.classList.add('hidden') | |
| } | |
| } | |
| window.addEventListener('scroll', handleScroll) | |
| // Cleanup | |
| onBeforeUnmount(() => { | |
| window.removeEventListener('scroll', handleScroll) | |
| }) | |
| }) |
🤖 Prompt for AI Agents
In components/awesome/BackToTop.vue around lines 23 to 36, the component assigns
window.onscroll in onMounted which overwrites other handlers and is never
removed—causing a memory leak; change this to register a named scroll handler
with window.addEventListener('scroll', onScroll) where onScroll is a const that
queries the #topButton (guarding for null) and toggles 'block'/'hidden' classes,
then remove the listener in onUnmounted using
window.removeEventListener('scroll', onScroll); this preserves other scroll
listeners and ensures proper cleanup on unmount.
| #pagetop { | ||
| z-index: 1000000; | ||
| img { | ||
| border-radius: 50%; | ||
| background-color: #ffffff; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CSS selector mismatch.
The CSS targets #pagetop but the button's id is #topButton. The styles won't apply to the intended element.
Apply this diff to fix the selector:
-#pagetop {
+#topButton {
z-index: 1000000;
img {🤖 Prompt for AI Agents
In components/awesome/BackToTop.vue around lines 39 to 45, the CSS targets
#pagetop but the button uses id #topButton, so the styles are not applied;
update the selector to #topButton (preserving the nested img rules: z-index, img
border-radius and background-color) so the styling applies to the actual
element.
| const spedUp = Math.random() * (80 - 50) + 150 | ||
| const normalSpeed = Math.random() * (300 - 250) + 50 | ||
| const time = isDeleting.value ? spedUp : normalSpeed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typing speed math reversed (deleting slower than typing).
Fix ranges so deleting is faster and naming matches behavior.
- const spedUp = Math.random() * (80 - 50) + 150
- const normalSpeed = Math.random() * (300 - 250) + 50
+ const spedUp = Math.random() * (80 - 50) + 50 // ~50–80ms
+ const normalSpeed = Math.random() * (300 - 250) + 250 // ~250–300ms📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const spedUp = Math.random() * (80 - 50) + 150 | |
| const normalSpeed = Math.random() * (300 - 250) + 50 | |
| const time = isDeleting.value ? spedUp : normalSpeed | |
| const spedUp = Math.random() * (80 - 50) + 50 // ~50–80ms | |
| const normalSpeed = Math.random() * (300 - 250) + 250 // ~250–300ms | |
| const time = isDeleting.value ? spedUp : normalSpeed |
🤖 Prompt for AI Agents
In components/awesome/Home.vue around lines 39–41, the speed math is reversed so
deleting is slower than typing; rename variables to reflect behavior and swap
ranges: create deleteSpeed = Math.random() * (80 - 50) + 50 (50–80ms) and
typeSpeed = Math.random() * (300 - 150) + 150 (150–300ms), then set time =
isDeleting.value ? deleteSpeed : typeSpeed so deleting is faster and naming
matches.
| @@ -0,0 +1,17 @@ | |||
| <script lang="ts" setup> | |||
| const LayoutPagePdfViewer = defineAsyncComponent(() => import('@/components/layouts/page/PdfViewer.vue')) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify the component import path casing.
The import uses '@/components/layouts/page/PdfViewer.vue' (lowercase page). Ensure this matches the actual file system path, as most other layout components in this PR use PascalCase directory names (e.g., Page not page).
Run this script to verify the correct path:
🏁 Script executed:
#!/bin/bash
# Find the PdfViewer component to confirm correct path
fd -t f "PdfViewer.vue" components/Length of output: 37
Update import path casing in pages/mycv.vue
Change the import from
import('@/components/layouts/page/PdfViewer.vue')to
import('@/components/layouts/Page/PdfViewer.vue')to match the actual directory name.
🧰 Tools
🪛 ESLint
[error] 2-2: Replace ()·=>·import('@/components/layouts/page/PdfViewer.vue') with ⏎··()·=>·import('@/components/layouts/page/PdfViewer.vue'),⏎
(prettier/prettier)
🤖 Prompt for AI Agents
In pages/mycv.vue around line 2, the asynchronous import uses the wrong
directory casing ('layouts/page/PdfViewer.vue'); update the import path to match
the actual directory name by changing it to 'layouts/Page/PdfViewer.vue' so the
module resolver can find the component.
Fix formatting to comply with project standards.
The import statement formatting doesn't match the project's Prettier configuration.
Apply this diff:
-const LayoutPagePdfViewer = defineAsyncComponent(() => import('@/components/layouts/page/PdfViewer.vue'))
+const LayoutPagePdfViewer = defineAsyncComponent(
+ () => import('@/components/layouts/page/PdfViewer.vue'),
+)Based on static analysis.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const LayoutPagePdfViewer = defineAsyncComponent(() => import('@/components/layouts/page/PdfViewer.vue')) | |
| const LayoutPagePdfViewer = defineAsyncComponent( | |
| () => import('@/components/layouts/page/PdfViewer.vue'), | |
| ) |
🧰 Tools
🪛 ESLint
[error] 2-2: Replace ()·=>·import('@/components/layouts/page/PdfViewer.vue') with ⏎··()·=>·import('@/components/layouts/page/PdfViewer.vue'),⏎
(prettier/prettier)
🤖 Prompt for AI Agents
In pages/mycv.vue around line 2, the async component import line is not
formatted to project Prettier standards; reformat the statement (e.g., ensure
proper spacing/parentheses and add the missing trailing semicolon) or run the
project's Prettier formatter on the file so the line becomes compliant with the
repo style.
| <client-only> | ||
| <LayoutPagePdfViewer /> | ||
| </client-only> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix indentation for consistent formatting.
The client-only block has inconsistent indentation.
Apply this diff:
<LayoutPageSection>
<client-only>
- <LayoutPagePdfViewer />
- </client-only>
+ <LayoutPagePdfViewer />
+ </client-only>
</LayoutPageSection>Based on static analysis.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <client-only> | |
| <LayoutPagePdfViewer /> | |
| </client-only> | |
| <LayoutPageSection> | |
| <client-only> | |
| <LayoutPagePdfViewer /> | |
| </client-only> | |
| </LayoutPageSection> |
🧰 Tools
🪛 ESLint
[error] 10-10: Delete ····
(prettier/prettier)
[error] 11-11: Delete ··
(prettier/prettier)
🤖 Prompt for AI Agents
In pages/mycv.vue around lines 9 to 11, the <client-only> block is indented
inconsistently; fix it by aligning the <client-only> start tag, its child
<LayoutPagePdfViewer /> and the closing </client-only> so their indentation
matches the surrounding template structure (use the same number of spaces/tabs
used in adjacent blocks) to maintain consistent formatting.
| <div class="min-h-[calc(100vh+1rem)]"> | ||
| <div | ||
| class="grid gap-8 sm:grid-cols-2 sm:gap-12 lg:grid-cols-2 xl:grid-cols-2 xl:gap-16" | ||
| class="grid gap-8 sm:grid-cols-2 sm:gap-12 lg:grid-cols-2 xl:grid-cols-2 xl:gap-16 " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove trailing spaces.
Lines 72 and 81 have trailing spaces that should be removed for code cleanliness.
As per coding guidelines.
Also applies to: 81-81
🧰 Tools
🪛 ESLint
[error] 72-72: Delete ·
(prettier/prettier)
🤖 Prompt for AI Agents
In pages/post/index.vue around lines 72 and 81, there are trailing spaces at the
end of the class attribute strings; remove the trailing spaces so the class
values end immediately after the last character (no extra whitespace), and
save/format the file (or run the project linter/formatter) to ensure no other
trailing spaces remain.
| }) | ||
| // query | ||
| // const query: QueryBuilderParams = { path: '/post' } | ||
| const query: QueryBuilderParams = { path: '/post' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove or utilize the unused query variable.
The query constant is defined but never used in the component. The template uses ContentList path="/project" instead of referencing this query variable.
If this was intended to be used, apply it to the ContentList component:
- <LayoutPageSection>
- <ContentList path="/project">
+ <LayoutPageSection>
+ <ContentList v-bind="query">Otherwise, remove the unused variable:
-// query
-const query: QueryBuilderParams = { path: '/post' }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In pages/project/index.vue around line 59, the constant `query` is declared but
never used; either remove the unused `const query: QueryBuilderParams = { path:
'/post' }` or pass it into the ContentList component (e.g., replace the
hardcoded path prop with the query object or add a prop to accept the query) so
the variable is actually consumed; update the template to use the query or
delete the declaration and run linting to confirm no unused variable remains.
|
|
||
| export interface AwesomeLayoutPageNavbarMenu { | ||
| type?: 'link' | 'button' | 'dropdown' | ||
| type?: 'link' | 'button' | 'dropdown' | any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove | any to preserve type safety.
Adding | any to the type union defeats the purpose of TypeScript's type system and allows any value, removing compile-time safety guarantees.
If additional menu types are needed, add them explicitly to the union:
- type?: 'link' | 'button' | 'dropdown' | any
+ type?: 'link' | 'button' | 'dropdown' | 'divider' | 'custom'Or if the type truly needs to be flexible, use string instead:
- type?: 'link' | 'button' | 'dropdown' | any
+ type?: 'link' | 'button' | 'dropdown' | stringThis maintains some type safety while allowing extensibility.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| type?: 'link' | 'button' | 'dropdown' | any | |
| type?: 'link' | 'button' | 'dropdown' | 'divider' | 'custom' |
🤖 Prompt for AI Agents
In utils/types.ts around line 11, the union type "type?: 'link' | 'button' |
'dropdown' | any" removes type safety by including "any"; replace the "| any"
with explicit additional literal unions for any new menu kinds or with "string"
if you need open extensibility (e.g., add new literals like 'checkbox'|'radio'
or change to string) and update any dependent code to use the chosen narrower
type.
Summary by CodeRabbit
New Features
Content
UI/Style
Refactor
Documentation
Chores