├── .env.example ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── app.vue ├── assets ├── css │ └── styles.scss └── images │ ├── nuxt-content.png │ └── screenshot.png ├── components ├── ContentNotFound.vue ├── FeatureCard.vue ├── Landing.vue ├── LanguageSwitcher.vue ├── MobileSidebar.vue ├── NuxtAnchor.vue ├── TableOfContent.vue ├── ThemeSwitcher.vue ├── VFooter.vue ├── VHeader.vue ├── VLinkChildren.vue └── content │ ├── ApaReference.vue │ ├── Card.vue │ ├── ProseCode.vue │ ├── ProseH2.vue │ └── ProseH3.vue ├── content ├── blogs │ ├── 1.my-first-blog.md │ ├── 2.my-second-blog.md │ ├── 3.my-third-blog.md │ └── _dir.yml ├── demo.md ├── fr │ ├── _dir.yml │ ├── blogs │ │ ├── 1.my-first-blog.md │ │ ├── 2.my-second-blog.md │ │ ├── 3.my-third-blog.md │ │ └── _dir.yml │ ├── demo.md │ └── guide.md └── guide.md ├── error.vue ├── layouts └── default.vue ├── locales ├── en.json └── fr.json ├── logs ├── 3.6.1-generate.txt └── 3.8.1-generate-error.txt ├── nuxt.config.ts ├── package.json ├── pages ├── [...slug].vue └── index.vue ├── pnpm-lock.yaml ├── public ├── images │ ├── demo.png │ ├── demo_landscape.png │ ├── flag-france.svg │ └── flag-united-states.svg └── nuxt.svg ├── tailwind.config.js ├── templates └── frontmatter.yml ├── tsconfig.json └── typos.toml /.env.example: -------------------------------------------------------------------------------- 1 | NUXT_PUBLIC_SITE_URL=https://nuxt-content-template.netlify.app 2 | NUXT_INDEXABLE=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | .lighthouse -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Chong Mum Khong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [![Contributors][contributors-shield]][contributors-url] 6 | [![Forks][forks-shield]][forks-url] 7 | [![Stargazers][stars-shield]][stars-url] 8 | [![Issues][issues-shield]][issues-url] 9 | [![MIT License][license-shield]][license-url] 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 |

Nuxt Content Template

19 | 20 |

21 | An opionionated Nuxt.js Template with Nuxt Content! 22 |
23 | View Demo » 24 |
25 |
26 | Explore the docs 27 | · 28 | Report Bug 29 | · 30 | Request Feature 31 |

32 |
33 | 34 | 35 |
36 | Table of Contents 37 |
    38 |
  1. 39 | About The Project 40 | 44 |
  2. 45 |
  3. 46 | Getting Started 47 | 51 |
  4. 52 |
  5. Usage
  6. 53 |
  7. Roadmap
  8. 54 |
  9. Contributing
  10. 55 |
  11. License
  12. 56 |
  13. Acknowledgments
  14. 57 |
58 |
59 | 60 | 61 | 62 | ## About The Project 63 | 64 | ![Template screenshot](/assets/images/screenshot.png) 65 | 66 | An extremely opinionated template for building Markdown oriented Nuxt websites with Nuxt Content and TailwindCSS. Focus on the development itself rather than low-level configurations. 67 | 68 | Incentives: 69 | 70 | - Wanted to build out my new personal website 71 | - Want Markdown 72 | - Want newest version of Vue, Nuxt and Nuxt Content (at the time). 73 | - Build this multi-purpose template so I can use for other projects. 74 | 75 | Projects that use this template: 76 | 77 | - [notes.mumk.dev](https://notes.mumk.dev) 78 | 79 |

(back to top)

80 | 81 | ### Built With 82 | 83 | The technologies and tools used within this template. 84 | 85 | - Vue 86 | - Nuxt 87 | - Nuxt Content 88 | - TailwindCSS 89 | - TypeScript 90 | 91 | The version for Nuxt (3.6.1) and Nuxt Content (2.7.0) must remain. Updating the version for these two packages will break the static site generation. Regardless, the existing version in my humble opinion is good enough to create a functional documentation/blogs. 92 | 93 |

(back to top)

94 | 95 | ### Features 96 | 97 | This template currently offers the following features. For more info, please read the [guide](https://nuxt-content-template.netlify.app/guide). 98 | 99 | - Markdown articles 100 | - Markdown embeddable LaTeX (`rehype-mathjax`, `remark-math`) 101 | - Math Equations 102 | - [Chemical Equations](https://notes.mumk.dev/articles/general/latex#chemical-equations) 103 | - Internationalization (i18n, `@nuxtjs/i18n`) 104 | - Dark Mode (`@nuxtjs/color-mode`) 105 | - Mobile responsive 106 | - Styling with TailwindCSS 107 | - Static-site generation 108 | - Simple SEO (`nuxt-seo-kit`) 109 | - Auto-generated Sitemap.xml 110 | - robots.txt 111 | - Optimized site metadata 112 | - Image optimization (`@nuxt/image`) 113 | - Vue utilities 114 | - Powerful hooks (`@vueuse/core`) 115 | - Animation (`@vueuse/motion`) 116 | - 404 Page (the `/resource` url does not exist) 117 | - Ultra-fast loading speed 118 | - Support for Node 18 and Node 20 119 | - Typo checking (need to install from [crates-ci/typos](https://github.com/crate-ci/typos)) 120 | - Sass support (although I don't remember using it 😁) 121 | 122 | Again, if you wish to grok what this template is capable of, consider visiting [my technical blog](https://notes.mumk.dev) to see it for yourself. 123 | 124 |

(back to top)

125 | 126 | 127 | 128 | ## Getting Started 129 | 130 | ### Prerequisites 131 | 132 | The list of tools that is used when development. 133 | 134 | - npm 135 | ```sh 136 | npm install npm@latest -g 137 | ``` 138 | - Pnpm 139 | ```sh 140 | npm i -g pnpm 141 | ``` 142 | - [Git](https://git-scm.com/downloads) 143 | 144 | ### Installation 145 | 146 | To run this template project in your local for personal use or contribution, simply perform the following. 147 | 148 | 1. Clone the repo 149 | ```sh 150 | git clone https://github.com/data-miner00/nuxt-content-template.git 151 | ``` 152 | 2. Install Node dependencies 153 | ```sh 154 | pnpm i 155 | ``` 156 | 3. Start the development server 157 | ```sh 158 | pnpm dev 159 | ``` 160 | 4. Copy the `.env.example` to `.env` and replace the value for `NUXT_PUBLIC_SITE_UR`. This is used for static-site generation. 161 | ```sh 162 | NUXT_PUBLIC_SITE_UR=https://www.mydomain.com 163 | ``` 164 | 5. Build/generate static website 165 | ```sh 166 | pnpm generate 167 | ``` 168 | 169 | 170 | 171 | > [!warning] 172 | > **Do not update** the dependencies as it will break due to incompatibilities from the latest Nuxt and Nuxt Content. 173 | 174 | 175 | 176 | > [!note] 177 | > The current lockfile was generated with Node v20.9.0. 178 | 179 |

(back to top)

180 | 181 | 182 | 183 | ## Roadmap 184 | 185 | - [x] Add Dark Mode 186 | - [x] Add i18n 187 | - [x] Add Styling 188 | - [x] Sass 189 | - [x] Tailwind 190 | - [ ] Add PWA Manifest 191 | - [x] Responsive styles 192 | - [ ] DocSearch 193 | - [x] Copy code block button 194 | - [x] Add line numbers to code block 195 | - [x] Add highlight to code block 196 | - [x] Add missing items to mobile navbar 197 | 198 | See the [open issues](https://github.com/data-miner00/nuxt-content-template/issues) for a full list of proposed features (and known issues). 199 | 200 |

(back to top)

201 | 202 | 203 | 204 | ## Contributing 205 | 206 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 207 | 208 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 209 | Don't forget to give the project a star! Thanks again! 210 | 211 | 1. Fork the Project 212 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 213 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 214 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 215 | 5. Open a Pull Request 216 | 217 |

(back to top)

218 | 219 | 220 | 221 | ## License 222 | 223 | Distributed under the MIT License. See `LICENSE` for more information. 224 | 225 |

(back to top)

226 | 227 | 228 | 229 | ## Acknowledgments 230 | 231 | List of resources that are helpful and would like to give credit to. 232 | 233 | - [Vue.js v3](https://vuejs.org/) 234 | - [Nuxt v3](https://nuxt.com/) 235 | - [Nuxt Content](https://content.nuxtjs.org) 236 | - [Robots.txt](https://developers.google.com/search/docs/crawling-indexing/robots/intro) 237 | - [enji.dev](https://www.enji.dev/) 238 | - [How to Create a Custom Code Block With Nuxt Content v2](https://mokkapps.de/blog/how-to-create-a-custom-code-block-with-nuxt-content-v2/) 239 | - [VueUse](https://vueuse.org/) 240 | - [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) 241 | - [Nuxt Tailwind](https://tailwindcss.nuxtjs.org/) 242 | - [How to add class attribute to the body tag in NuxtJs](https://postsrc.com/code-snippets/how-to-add-class-attribute-to-the-body-tag-in-nuxtjs) 243 | - [Nuxt i18n](https://v8.i18n.nuxtjs.org/) 244 | - [How to Build a Multi-Language Application with NuxtJS](https://crowdin.com/blog/2023/01/24/nuxt-js-i18n-tutorial) 245 | - [Vue i18n](https://vue-i18n.intlify.dev/) 246 | - [Nuxt Seo Kit](https://github.com/harlan-zw/nuxt-seo-kit) 247 | - [Nuxt Image](https://image.nuxtjs.org/) 248 | - [Nuxt UI](https://ui.nuxt.com/) 249 | 250 |

(back to top)

251 | 252 | 253 | 254 | [contributors-shield]: https://img.shields.io/github/contributors/data-miner00/nuxt-content-template.svg?style=for-the-badge 255 | [contributors-url]: https://github.com/data-miner00/nuxt-content-template/graphs/contributors 256 | [forks-shield]: https://img.shields.io/github/forks/data-miner00/nuxt-content-template.svg?style=for-the-badge 257 | [forks-url]: https://github.com/data-miner00/nuxt-content-template/network/members 258 | [stars-shield]: https://img.shields.io/github/stars/data-miner00/nuxt-content-template.svg?style=for-the-badge 259 | [stars-url]: https://github.com/data-miner00/nuxt-content-template/stargazers 260 | [issues-shield]: https://img.shields.io/github/issues/data-miner00/nuxt-content-template.svg?style=for-the-badge 261 | [issues-url]: https://github.com/data-miner00/nuxt-content-template/issues 262 | [license-shield]: https://img.shields.io/github/license/data-miner00/nuxt-content-template.svg?style=for-the-badge 263 | [license-url]: https://github.com/data-miner00/nuxt-content-template/blob/master/LICENSE 264 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /assets/css/styles.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | // Mathjax/LaTeX stylings 6 | .math { 7 | &.math-inline { 8 | @apply inline-block; 9 | } 10 | 11 | &.math-display { 12 | @apply overflow-x-auto; 13 | } 14 | 15 | &.math-display svg { 16 | @apply block mx-auto; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /assets/images/nuxt-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-miner00/nuxt-content-template/25c384174298dff24113dd1ff943e00c5e49f3a6/assets/images/nuxt-content.png -------------------------------------------------------------------------------- /assets/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-miner00/nuxt-content-template/25c384174298dff24113dd1ff943e00c5e49f3a6/assets/images/screenshot.png -------------------------------------------------------------------------------- /components/ContentNotFound.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /components/FeatureCard.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 63 | -------------------------------------------------------------------------------- /components/Landing.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 31 | -------------------------------------------------------------------------------- /components/LanguageSwitcher.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 83 | 84 | 88 | -------------------------------------------------------------------------------- /components/MobileSidebar.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 86 | -------------------------------------------------------------------------------- /components/NuxtAnchor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 33 | -------------------------------------------------------------------------------- /components/TableOfContent.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 67 | 68 | 84 | -------------------------------------------------------------------------------- /components/ThemeSwitcher.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 173 | -------------------------------------------------------------------------------- /components/VFooter.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 196 | 197 | 235 | -------------------------------------------------------------------------------- /components/VHeader.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 112 | 113 | 117 | -------------------------------------------------------------------------------- /components/VLinkChildren.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | -------------------------------------------------------------------------------- /components/content/ApaReference.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 59 | -------------------------------------------------------------------------------- /components/content/Card.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | -------------------------------------------------------------------------------- /components/content/ProseCode.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 71 | 72 | 95 | -------------------------------------------------------------------------------- /components/content/ProseH2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | -------------------------------------------------------------------------------- /components/content/ProseH3.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | -------------------------------------------------------------------------------- /content/blogs/1.my-first-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My First Blog 3 | description: A humble subtitle describing the blog in more details, should be written in sentence-case 4 | topic: Blog 5 | category: Category 6 | authors: 7 | - name: Contributor 1 8 | avatar: contrib_1.png 9 | - name: Contributor 2 10 | avatar: contrib_2.png 11 | tags: 12 | - blog 13 | - diary 14 | - example 15 | updatedAt: 2022-11-18T11:37:49.432Z 16 | createdAt: 2022-11-18T11:37:49.432Z 17 | --- 18 | 19 | ## A Great Start 20 | 21 | It was a cold morning. I woke up at 6 o'clock sharp and get ready for hiking. After having the scrumptious French croissant along with a cup of exhilirating iced Americano for my breakfast, I stepped out my house with a strikingly palpable, overzealous mood and energy, ready to pulverize any bold obstacles that dares to get in my way. 22 | 23 | ## Indelible Memories 24 | 25 | It was an incredible and wholesome experience to witness the majestic golden rays in the serene sky that intensifies as the sun rose higher in the sky. It was truly a sight to behold. Topping it up with the chilly breeze embracing my warm body as it flows through, I felt invigorating and overjoyed to say the least. 26 | 27 | ## An Anecdotal Incident 28 | 29 | While I was making my way through the hilltop, I came across a wild black panther sleeping soundly beside the crystal-clear river, shimmering with dazzling sparkles. It was surreal and I managed to capture some photo of the beast before continuing my journey. 30 | 31 | ## References 32 | 33 | ::apa-reference 34 | --- 35 | authors: 36 | - Greenhouse, S 37 | date: 2020, July 30 38 | title: The coronavirus pandemic has intensified systemic economic racism against black Americans 39 | publisher: The New Yorker 40 | url: https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-economic-racism-against-black-americans 41 | source: newspaper 42 | --- 43 | :: 44 | 45 | ::apa-reference 46 | --- 47 | authors: 48 | - Lee, C 49 | date: 2020, February 19 50 | title: A tale of two reference formats 51 | publisher: APA Style Blog 52 | url: https://apastyle.apa.org/blog/two-reference-formats 53 | source: blogs 54 | --- 55 | :: 56 | 57 | ::apa-reference 58 | --- 59 | authors: 60 | - Rowlatt, J 61 | date: 2020, October 19 62 | title: Could cold water hold a clue to a dementia cure? 63 | publisher: BBC News 64 | url: https://www.bbc.com/news/health-54531075 65 | source: online-news 66 | --- 67 | :: 68 | -------------------------------------------------------------------------------- /content/blogs/2.my-second-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Second Blog 3 | description: A humble subtitle describing the blog in more details, should be written in sentence-case 4 | topic: Blog 5 | category: Category 6 | authors: 7 | - name: Contributor 1 8 | avatar: contrib_1.png 9 | - name: Contributor 2 10 | avatar: contrib_2.png 11 | tags: 12 | - blog 13 | - diary 14 | - example 15 | updatedAt: 2022-11-18T11:37:49.432Z 16 | createdAt: 2022-11-18T11:37:49.432Z 17 | --- 18 | 19 | ## An Irksome Morning 20 | 21 | Today is yet another working day but I am excited to work in the office as I am a semi-workaholic that has a lot to take care of today. 22 | 23 | I went to the nearest bus station from my dwelling place as usual to commute to my office. I was waiting patiently in the bus stop along with other overbearing peasants. 24 | 25 | I waited for a staggering 9 minutes and the bus was nowhere to be seen and it really gets on my nerves. I am starting to get irritated and hallucinating because of this. 26 | 27 | ## It Does Not End There 28 | 29 | Finally, after a whole minute later, I finally saw the bus bellowing down the road. In the symphony of emotions, my heart soared with breathtaking ardor, its rhythm racing like a thousand celestial stallions unleashed upon a moonlit gallop as the bus drove nearer and nearer. I am sweating with excitement, and my sickening body healed at an instant. 30 | 31 | The moment when the bus stopped in front of me, I was flabbergasted and stunned. I couldn't feel my breath anymore and starts to comtemplate about my mixed feelings towards the arrival of the bus. 32 | 33 | After regaining my consciousness, I realised that the bus and the fellow peasants has long been gone. "Can like that one meh," I chided. 34 | -------------------------------------------------------------------------------- /content/blogs/3.my-third-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: My Third Blog 3 | description: A humble subtitle describing the blog in more details, should be written in sentence-case 4 | topic: Blog 5 | category: Category 6 | authors: 7 | - name: Contributor 1 8 | avatar: contrib_1.png 9 | - name: Contributor 2 10 | avatar: contrib_2.png 11 | tags: 12 | - blog 13 | - diary 14 | - example 15 | updatedAt: 2022-11-18T11:37:49.432Z 16 | createdAt: 2022-11-18T11:37:49.432Z 17 | --- 18 | 19 | ## A Scary Morning 20 | 21 | It was 5am in the morning. I woke up from the abyss of a haunting dream, trembling with fear, my senses electrified by the lingering echoes of terror. With every breath, my chest heaved, the cadence of my heart pounding against the walls of my ribcage, as if desperately seeking escape from the clutches of the nightmarish realm that had ensnared me. 22 | 23 | I sit up, and sauntered to the washroom. I turned on the tap, relentlessly splashing the freezing water onto my face, trying to calm myself down. I surreptitiously looked into the mirror, aghast. I recalled that I still had yet to complete my programming assignment that will be due this afternoon. 24 | 25 | ## It's Just An Easy Assignment 26 | 27 | With the fleetness of a gazelle, I dashed towards my desk, switching on my computer and adding in a final touch to my assignment and submit before it's too late. 28 | 29 | ```wenyan 30 | 吾有一術。名之曰「埃氏篩」。欲行是術。必先得一數。曰「甲」。乃行是術曰。 31 | 吾有一列。名之曰「掩」。為是「甲」遍。充「掩」以陽也。 32 | 除「甲」以二。名之曰「甲半」。 33 | 34 | 有數二。名之曰「戊」。恆為是。若「戊」不小於「甲半」者乃止也。 35 | 有數二。名之曰「戌」。恆為是。若「戌」不小於「甲半」者乃止也。 36 | 37 | 乘「戊」以「戌」。名之曰「合」 38 | 若「合」不大於「甲」者。 39 | 昔之「掩」之「合」者。今陰是矣。 40 | 若非乃止也。 41 | 加一以「戌」。昔之「戌」者。今其是矣云云。 42 | 加一以「戊」。昔之「戊」者。今其是矣云云。 43 | 44 | 吾有一列。名之曰「諸素」。 45 | 昔之「戊」者。今二是矣。恆為是。若「戊」等於「掩」之長者乃止也。 46 | 夫「掩」之「戊」。名之曰「素耶」。 47 | 若「素耶」者充「諸素」以「戊」也。 48 | 加一以「戊」。昔之「戊」者。今其是矣云云。 49 | 乃得「諸素」。 50 | 是謂「埃氏篩」之術也。 51 | 52 | 施「埃氏篩」於一百。書之。 53 | ``` 54 | 55 | With the submission of my work out of the place, I heaved a sigh of relief and sank myself into the plush embrace of the sofa, a sanctuary of respite. My weary limbs, burdened with the weight of perseverance, found solace in the cushions as I surrendered to the sheer relief that washed over me. 56 | -------------------------------------------------------------------------------- /content/blogs/_dir.yml: -------------------------------------------------------------------------------- 1 | title: "My Blogs" 2 | navigation.description: "Read some of my blogs" 3 | -------------------------------------------------------------------------------- /content/demo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RxJS Primer 3 | description: The curated topics and concepts that are commonly used in RxJS 4 | topic: Programming 5 | displayTopic: Programming 6 | directory: programming 7 | author: 8 | - name: Shaun Chong 9 | avatar: levi.png 10 | tags: 11 | - reactive 12 | - rxjs 13 | - cheatsheet 14 | updatedAt: 2024-03-28T11:05:53.157Z 15 | createdAt: 2022-11-18T11:37:49.432Z 16 | --- 17 | 18 | > This is an article that I wrote on my technical blog website ["book"](https://book-dun-three.vercel.app/programming/rxjs-primer) that I use it here to demonstrate contents generated. 19 | 20 |
21 | 22 | RxJS is a [ReactiveX](https://reactivex.io/) implementation in JavaScript. ReactiveX is an API for asynchronous programming with observable streams. There are many more implementations of ReactiveX in other languages such as [RxJava](https://github.com/ReactiveX/RxJava) for Java, [Rx.NET](https://github.com/dotnet/reactive) for C#, [RxSwift](https://github.com/ReactiveX/RxSwift) for Swift etc. 23 | 24 | The streams of data encompasses database events, dom events and file uploads. 25 | 26 | ## Installation 27 | 28 | RxJS can be installed by using any of the node package managers out there and [Yarn](https://yarnpkg.com) is used for demonstration here. In addition, using TypeScript is highly recommended as the strict typings can make the code more robust and easier to read. 29 | 30 | ``` 31 | yarn add rxjs 32 | yarn add -D typescript ts-loader 33 | ``` 34 | 35 | If you are using [Webpack](https://webpack.js.org/) or other JavaScript bundler, make sure to configure the bundler to run accordingly and have a start script in `package.json` file. 36 | 37 | ```json [package.json] 38 | { 39 | "scripts": { 40 | "start": "webpack-dev-server --mode development" 41 | } 42 | } 43 | ``` 44 | 45 | ## Angular 46 | 47 | [Angular](https://angular.io/) is a JavaScript Framework developed by Google. RxJS can be used on the get-go as it is baked into Angular by default so there is no need for a separate installation. All we need is just the [Angular CLI](https://angular.io/cli) to create a new project to work with. 48 | 49 | ``` 50 | ng new 51 | ``` 52 | 53 | Start the project after the dependencies have been installed. 54 | 55 | ## Observable 56 | 57 | It is a wrapper around a piece of data that can be subscribed to. The subscriber of that data will then get notified when there is changes on the data itself. 58 | 59 | Observable literally means _"something that can be observed"_. It can also be think as a pipe of data. 60 | 61 | The following code to create an observable is for demonstration purposes only. Observables can only be created in a useful way by some of the operators offered by RxJS library itself. 62 | 63 | The code creates an observable that will send the `'hello'` text upon subscription. 64 | 65 | ```ts 66 | import { Observable } from "rxjs"; 67 | 68 | var observable = Observable.create((observer) => { 69 | observer.next("hello"); 70 | observer.next("hello"); 71 | }); 72 | ``` 73 | 74 | To subscribe to the observer, use the `subscribe` method and it takes in one compulsory callback, and two optional callbacks as its argument. 75 | 76 | ```ts 77 | var observer = observable.subscribe( 78 | (x) => console.log("onSuccess: ", x), 79 | (err) => console.error("onError", err), 80 | () => console.log("onComplete") 81 | ); 82 | ``` 83 | 84 | The subscription will activate the observable and 2 lines of `onSuccess: hello` should be appearing in the browser dev tools. 85 | 86 | When the observer is marked as `complete`, it will be deactivated and no more data can be send through. 87 | 88 | ```ts 89 | var observable = Observable.create((observer) => { 90 | observer.next("hey"); 91 | observer.next("hey"); 92 | observer.complete(); 93 | observer.next("hey"); // not sent 94 | }); 95 | ``` 96 | 97 | ### Creating Observables 98 | 99 | As mentioned above, observables needs to be created with the officially endorsed way by RxJS. Here are some ways to create an observable. 100 | 101 | ```ts 102 | import { Observable, of, from, interval, fromEvent } from "rxjs"; 103 | ``` 104 | 105 | To wrap a raw value inside an observable, `of` can be used as it will only emit the value wrapped once and this is useful in software testing. However, there will be times that `of` can be useful in production code as well. 106 | 107 | ```ts 108 | const hello$ = of("hello"); 109 | 110 | hello$.subscribe((x) => console.log(x)); // hello 111 | ``` 112 | 113 | Next, the `from` operator takes in an iterable and emits them one by one. 114 | 115 | ```ts 116 | const hello$ = from("hello"); 117 | 118 | hello$.subscribe((x) => console.log(x)); // h, e, l, l, o 119 | ``` 120 | 121 | Next, the `fromEvent` operator is useful in composing events in DOM into observables. `fromEvent` takes in the DOM element as its first parameter and the event to be listened to as its second argument. 122 | 123 | ```ts 124 | const event$ = fromEvent(document, "click"); 125 | event$.subscribe((x) => console.log(x)); 126 | ``` 127 | 128 | Another observer creation method is `interval`, where it takes in the time interval in milliseconds and perpetually emits an increment of integer by 1 starting from 0. 129 | 130 | ```ts 131 | const periodic$ = interval(1000); 132 | 133 | // 5 seconds passed 134 | periodic$.subscribe((x) => console.log(x)); // 0, 1, 2, 3, 4 135 | ``` 136 | 137 | ### Synchronous and Asynchronous 138 | 139 | RxJS can be both synchronous and asynchronous. 140 | 141 | ```ts 142 | const hello$ = of("hello"); 143 | hello$.subscribe((x) => console.log(x)); 144 | console.log("world"); 145 | ``` 146 | 147 | The above code yields result of `'hello'` first and subsequently `'world'` because the code execute synchronously from top to bottom all within the main thread. 148 | 149 | To make it asynchronous, `asyncScheduler` can be used. 150 | 151 | ```ts 152 | import { asyncScheduler } from "rxjs"; 153 | 154 | const hello$ = of("hello", asyncScheduler); 155 | hello$.subscribe((x) => console.log(x)); 156 | console.log("world"); 157 | ``` 158 | 159 | The output is `'world'` followed by `'hello'` because the subscription only happens on the second iteration of the asynchronous event loop whereas the line to print `'world'` is already completed in the first event loop. 160 | 161 | ### Hot and Cold Observables 162 | 163 | When the data is produced by the Observable itself, we call it a cold Observable. When the data is produced outside the Observable, we call it a hot Observable. Hot observables can have multiple subscriptions whereas cold observables can only have one subscription. If there are more than one subscription to a cold observable, the data obtained might differs. 164 | 165 | Cold observables is lazy. They will not create the values until they are subscribed to it. Here is an example of cold observable. 166 | 167 | ```ts 168 | const cold$ = Observable.create((observer) => observer.next(Math.random())); 169 | 170 | cold$.subscribe(console.log); // 0.5 171 | cold$.subscribe(console.log); // 0.89 172 | ``` 173 | 174 | However, this might not be useful in real life scenario and we want the data to be consistent. To achieve this, the cold observables needs to be converted into the hot observables. 175 | 176 | The first way is to move the data generation outside the observable. 177 | 178 | ```ts 179 | const random = Math.random(); 180 | 181 | const hot$ = Observable.create((observer) => observer.next(random)); 182 | 183 | hot$.subscribe(console.log); // 0.5 184 | hot$.subscribe(console.log); // no value 185 | ``` 186 | 187 | The second subscriber receives no value because the data is already emitted when the first observer subscribe to it. 188 | 189 | The other way to transform a cold observable to a hot observable is to use the `share` operator. 190 | 191 | ```ts 192 | const cold$ = Observable.create((observer) => observer.next(Math.random())); 193 | 194 | const hot$ = cold$.pipe(share()); 195 | 196 | hot$.subscribe(console.log); // 0.5 197 | hot$.subscribe(console.log); // no value 198 | ``` 199 | 200 | To make the second subscriber to receive the last value emitted, `shareReplay` can be used to replace the `share` operator. 201 | 202 | ```ts 203 | const cold$ = Observable.create((observer) => observer.next(Math.random())); 204 | 205 | const hot$ = cold$.pipe(shareReplay()); 206 | 207 | hot$.subscribe(console.log); // 0.5 208 | hot$.subscribe(console.log); // 0.5 209 | ``` 210 | 211 | ## Subject 212 | 213 | Subject is a different type of observable that can push values programmatically to it after the creation. 214 | 215 | ```ts 216 | import { Subject } from "rxjs"; 217 | 218 | var subject = new Subject(); 219 | subject.subscribe(console.log); 220 | subject.next("The first thing has been sent"); 221 | 222 | var observer = subject.subscribe(console.log); 223 | subject.next("The second thing has been sent"); 224 | observer.unsubscribe(); 225 | subject.next("The third thing has been sent"); 226 | ``` 227 | 228 | ### Behaviour Subject 229 | 230 | Behaviour subject will emit the last cached value upon new subsciption. 231 | 232 | ```ts 233 | var subject = new BehaviorSubject("First"); 234 | 235 | subject.subscribe((data) => addItem("observer 1 ", data)); 236 | ``` 237 | 238 | ### Replay Subject 239 | 240 | With behaviour subject, the late comers can only receive the last emitted item. However with replay subject, the late comers can receive $n$ amount of data upon subscription. 241 | 242 | ```ts 243 | var subject = new ReplaySubject(3); 244 | 245 | subject.next(1); 246 | subject.next(2); 247 | subject.subscribe(console.log); // 1, 2 248 | subject.next(3); // 3 249 | subject.next(4); // 4 250 | subject.subscribe(console.log); // 2, 3, 4 251 | ``` 252 | 253 | ### Async Subject 254 | 255 | The simplest subject of all. It will only emit the last value upon completion. 256 | 257 | ```ts 258 | var subject = new AsyncSubject(); 259 | 260 | subject.next(1); 261 | subject.subscribe(console.log); 262 | subject.complete(); // 1 263 | ``` 264 | 265 | ## Operators 266 | 267 | - Static Operators: These operators are usually used to create observables. 268 | - Instance Operators: These methods on observable instance (majority of RxJS) 269 | 270 | ### Modifier Operators 271 | 272 | These operator transform the existing value and modify the data flow. 273 | 274 | ```ts 275 | import { map, filter, take, scan } from "rxjs/operators"; 276 | 277 | const source$ = from([1, 2, 3, 4, 5]); 278 | const modified$ = source$.pipe( 279 | map((x) => x + 1), // 2, 3, 4, 5, 6 280 | scan((acc, val) => acc + val), // 2, 5, 9, 14, 20 281 | filter((x) => x > 10), // 14, 20 282 | take(1) // 14 283 | ); 284 | ``` 285 | 286 | ### Pluck 287 | 288 | A synthetic sugar for `map` to select only a certain keys in the array of object. 289 | 290 | ```ts 291 | const list$ = of([ 292 | { 293 | name: "Shino", 294 | age: 20, 295 | address: "Tokyo", 296 | }, 297 | { 298 | name: "Anthony", 299 | age: 21, 300 | address: "Berkeley", 301 | }, 302 | ]); 303 | 304 | const names$ = list$.pipe(pluck("name")); 305 | 306 | names$.subscribe(console.log); // 'Shino', 'Anthony' 307 | ``` 308 | 309 | ### Tap 310 | 311 | Tap operator allow side-effects to be triggered within the pipe. 312 | 313 | ```ts 314 | source$.pipe( 315 | tap(console.log), 316 | map((x) => x.toUpperCase()), 317 | tap(async (x) => { 318 | await Promise.resolve(); 319 | alert(x); 320 | }) 321 | ); 322 | ``` 323 | 324 | ### Handling Backpressure 325 | 326 | Backpressure is the observables emitting of an **overwhelmingly large amount** of values than we actually need. An epitome would be the inflow of dom events triggered by mouse move. 327 | 328 | The first strategy to handle this is to debounce the events. Debounce will not emit an event until the action has stopped for a period of time and this can be useful for typeahead when user is filling up an input field and a validation would only trigger after they have done typing. 329 | 330 | ```ts 331 | import { fromEvent } from "rxjs"; 332 | import { debounceTime } from "rxjs/operators"; 333 | 334 | const event$ = fromEvent(document, "mousemove"); 335 | 336 | const debounced$ = event$.pipe(debounceTime(1000)); 337 | debounced$.subscribe(console.log); 338 | ``` 339 | 340 | Throttling the event can also be useful as the number of events are significantly reduced by a specified time interval. Throttling can be think as rate-limiting. 341 | 342 | ```ts 343 | import { throttleTime } from "rxjs/operators"; 344 | 345 | const event$ = fromEvent(document, "mousemove"); 346 | 347 | const throttled$ = event$.pipe(throttleTime(1000)); 348 | throttled$.subscribe(console.log); 349 | ``` 350 | 351 | Buffer count on the other hand keeps all the event into an array and emit all of them at once when the buffer capacity has reached. 352 | 353 | ```ts 354 | import { bufferCount } from "rxjs/operators"; 355 | 356 | const event$ = fromEvent(document, "mousemove"); 357 | 358 | const buffered$ = event$.pipe(bufferCount(10)); 359 | buffered$.subscribe(console.log); 360 | ``` 361 | 362 | ### Switch Map 363 | 364 | Switch map allows two relational observables to interoperate for data fetching. 365 | 366 | ```ts 367 | const user$ = of({ uid: Math.random() }); 368 | const fetchOrders$ = (userId: number) => of(`${userId}'s order data'`); 369 | ``` 370 | 371 | First, we will need the user ID before we can fetch the order data. The intuitive way to do so is by nesting subscriptions. 372 | 373 | ```ts 374 | user$.subscribe({ uid } => { 375 | fetchOrders$(uid).subscribe(console.log) 376 | }) 377 | ``` 378 | 379 | The better way to make relational calls is by using switch map. 380 | 381 | ```ts 382 | const orders$ = user$.pipe(switchMap((user) => fetchOrders$(user.uid))); 383 | 384 | orders$.subscribe(console.log); 385 | ``` 386 | 387 | ### Combination Operators 388 | 389 | There are multiple ways to combine observables. **Combine latest** takes in an array of observables, and will wait for all values in each independent observables to resolve their value and only emit all the values together as an array. 390 | 391 | ```ts 392 | import { combineLatest } from "rxjs"; 393 | import { delay } from "rxjs/operators"; 394 | 395 | const randSync$ = Observable.create((o) => o.next(Math.random())); 396 | const randAsync$ = randSync$.pipe(delay(1000)); 397 | 398 | const combined$ = combineLatest([randSync$, randAsync$]); 399 | 400 | combined$.subscribe(console.log); // [0.5, 0.8] 401 | ``` 402 | 403 | **Merge** on the other hand fuse two observables into one to produce an ordinary observable. 404 | 405 | ```ts 406 | import { merge } from "rxjs"; 407 | import { delay } from "rxjs/operators"; 408 | 409 | const randSync$ = Observable.create((o) => o.next(Math.random())); 410 | const randAsync$ = randSync$.pipe(delay(1000)); 411 | 412 | const merged$ = merge([randSync$, randAsync$]); 413 | 414 | merged$.subscribe(console.log); // 0.5, 0.8 415 | ``` 416 | 417 | **Skip until** can be used to ignore the source observable until the second observable emits a value. 418 | 419 | ```ts 420 | var skipped$ = observable1$.skipUntil(observable2$); 421 | ``` 422 | 423 | ### Error Handling 424 | 425 | Error handling can be performed against observable inside the pipe. Retry mechanism can be implemented as well with the `retry` operator. 426 | 427 | ```ts 428 | import { catchError, retry } from "rxjs/operators"; 429 | 430 | someObservable$.pipe( 431 | catchError((err) => of("default value")), 432 | retry(2) 433 | ); 434 | ``` 435 | 436 | ## Memory Leaks 437 | 438 | Remember to unsubscribe any of the long running observables. 439 | 440 | ```ts 441 | const source$ = interval(100); 442 | 443 | const subscription = source.subscribe((x) => { 444 | console.log(x); 445 | if (x > 10) { 446 | subscription.unsubscribe(); 447 | } 448 | }); 449 | ``` 450 | 451 | A nicer way to handle this is to use `takeWhile` where it will stop emitting values when the conditions does not met anymore. 452 | 453 | ```ts 454 | source$.pipe(takeWhile((x) => x <= 10)); 455 | ``` 456 | 457 | To rely on another observables to stop emitting values instead, `takeUntil` can be used as once the other observable emits a value, the subscription to the current observable will automatically cancelled. 458 | 459 | ```ts 460 | source$.pipe(takeUntil(of("something"))); 461 | ``` 462 | 463 | ## Resources 464 | 465 | - [ReactiveX](https://reactivex.io/) 466 | - [RxJS](https://rxjs.dev/) 467 | - [RxJS Primer](https://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer) 468 | - [RxJS Overview](https://rxjs-dev.firebaseapp.com/guide/overview) 469 | - [Understanding hot vs cold Observables](https://luukgruijs.medium.com/understanding-hot-vs-cold-observables-62d04cf92e03) 470 | - [RxJS Top Ten - Code This, Not That - YouTube](https://www.youtube.com/watch?v=ewcoEYS85Co) 471 | -------------------------------------------------------------------------------- /content/fr/_dir.yml: -------------------------------------------------------------------------------- 1 | title: "la maison" 2 | navigation.description: "Bienvenue chez moi" 3 | -------------------------------------------------------------------------------- /content/fr/blogs/1.my-first-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mon premier blog 3 | description: Un humble sous-titre décrivant le blog plus en détail, doit être écrit en cas de phrase 4 | topic: Blog 5 | category: Category 6 | authors: 7 | - name: Contributor 1 8 | avatar: contrib_1.png 9 | - name: Contributor 2 10 | avatar: contrib_2.png 11 | tags: 12 | - blog 13 | - diary 14 | - example 15 | updatedAt: 2022-11-18T11:37:49.432Z 16 | createdAt: 2022-11-18T11:37:49.432Z 17 | --- 18 | 19 | ## Un bon début 20 | 21 | C'était une matinée froide. Je me suis réveillé à 6 heures précises et je me prépare pour la randonnée. Après avoir dégusté le délicieux croissant français accompagné d'une tasse d'Americano glacé exaltant pour mon petit-déjeuner, je suis sorti de chez moi avec une humeur et une énergie étonnamment palpables et trop zélées, prêt à pulvériser tous les obstacles audacieux qui oseraient se mettre en travers de mon chemin. 22 | 23 | ## Souvenirs indélébiles 24 | 25 | Ce fut une expérience incroyable et saine d'être témoin des majestueux rayons dorés dans le ciel serein qui s'intensifient à mesure que le soleil s'élève plus haut dans le ciel. C'était vraiment un spectacle à voir. En le complétant avec la brise froide embrassant mon corps chaud alors qu'il coulait à travers, je me sentais revigorant et ravi pour le moins. 26 | 27 | ## Un incident anecdotique 28 | 29 | Alors que je traversais le sommet de la colline, je suis tombé sur une panthère noire sauvage qui dormait profondément au bord de la rivière cristalline, scintillante d'étincelles éblouissantes. C'était surréaliste et j'ai réussi à capturer quelques photos de la bête avant de continuer mon voyage. 30 | 31 | ## Les références 32 | 33 | ::apa-reference 34 | --- 35 | authors: 36 | - Greenhouse, S 37 | date: 2020, July 30 38 | title: The coronavirus pandemic has intensified systemic economic racism against black Americans 39 | publisher: The New Yorker 40 | url: https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-economic-racism-against-black-americans 41 | source: newspaper 42 | --- 43 | :: 44 | 45 | ::apa-reference 46 | --- 47 | authors: 48 | - Lee, C 49 | date: 2020, February 19 50 | title: A tale of two reference formats 51 | publisher: APA Style Blog 52 | url: https://apastyle.apa.org/blog/two-reference-formats 53 | source: blogs 54 | --- 55 | :: 56 | 57 | ::apa-reference 58 | --- 59 | authors: 60 | - Rowlatt, J 61 | date: 2020, October 19 62 | title: Could cold water hold a clue to a dementia cure? 63 | publisher: BBC News 64 | url: https://www.bbc.com/news/health-54531075 65 | source: online-news 66 | --- 67 | :: 68 | -------------------------------------------------------------------------------- /content/fr/blogs/2.my-second-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mon deuxième blog 3 | description: Un humble sous-titre décrivant le blog plus en détail, doit être écrit en cas de phrase 4 | topic: Blog 5 | category: Category 6 | authors: 7 | - name: Contributor 1 8 | avatar: contrib_1.png 9 | - name: Contributor 2 10 | avatar: contrib_2.png 11 | tags: 12 | - blog 13 | - diary 14 | - example 15 | updatedAt: 2022-11-18T11:37:49.432Z 16 | createdAt: 2022-11-18T11:37:49.432Z 17 | --- 18 | 19 | ## Une matinée ennuyeuse 20 | 21 | Aujourd'hui est encore une autre journée de travail, mais je suis ravi de travailler au bureau car je suis un semi-bourreau de travail qui a beaucoup à faire aujourd'hui. 22 | 23 | Je suis allé à la gare routière la plus proche de chez moi comme d'habitude pour me rendre à mon bureau. J'attendais patiemment à l'arrêt de bus avec d'autres paysans autoritaires. 24 | 25 | J'ai attendu 9 minutes stupéfiantes et le bus était introuvable et cela m'énerve vraiment. Je commence à m'énerver et à halluciner 26 | 27 | ## Ça ne s'arrête pas là 28 | 29 | Enfin, après une minute entière plus tard, j'ai finalement vu le bus mugir sur la route. Dans la symphonie des émotions, mon cœur s'est envolé d'une ardeur époustouflante, son rythme s'emballant comme mille étalons célestes déchaînés au galop au clair de lune alors que le bus se rapprochait de plus en plus. Je transpire d'excitation et mon corps écœurant a guéri en un instant. 30 | 31 | Au moment où le bus s'est arrêté devant moi, j'ai été sidéré et abasourdi. Je ne pouvais plus sentir mon souffle et commençais à réfléchir à mes sentiments mitigés face à l'arrivée du bus. 32 | 33 | Après avoir repris conscience, j'ai réalisé que le bus et les autres paysans avaient disparu depuis longtemps. "Peut aimer celui-là meh," ai-je réprimandé. 34 | -------------------------------------------------------------------------------- /content/fr/blogs/3.my-third-blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mon troisième blog 3 | description: Un humble sous-titre décrivant le blog plus en détail, doit être écrit en cas de phrase 4 | topic: Blog 5 | category: Category 6 | authors: 7 | - name: Contributor 1 8 | avatar: contrib_1.png 9 | - name: Contributor 2 10 | avatar: contrib_2.png 11 | tags: 12 | - blog 13 | - diary 14 | - example 15 | updatedAt: 2022-11-18T11:37:49.432Z 16 | createdAt: 2022-11-18T11:37:49.432Z 17 | --- 18 | 19 | ## Un matin effrayant 20 | 21 | Il était 5h du matin. Je me suis réveillé de l'abîme d'un rêve obsédant, tremblant de peur, mes sens électrisés par les échos persistants de la terreur. À chaque respiration, ma poitrine se soulevait, la cadence de mon cœur battant contre les parois de ma cage thoracique, comme si je cherchais désespérément à échapper aux griffes du royaume cauchemardesque qui m'avait pris au piège. 22 | 23 | Je m'assois et me dirige vers les toilettes. J'ouvris le robinet, éclaboussant sans relâche l'eau glacée sur mon visage, essayant de me calmer. Je me regardai subrepticement dans le miroir, consterné. J'ai rappelé que je n'avais pas encore terminé mon devoir de programmation qui sera dû cet après-midi. 24 | 25 | ## C'est juste une tâche facile 26 | 27 | Avec la rapidité d'une gazelle, je me précipitai vers mon bureau, allumant mon ordinateur et ajoutant une touche finale à mon devoir et le soumettant avant qu'il ne soit trop tard. 28 | 29 | ```wenyan 30 | 吾有一術。名之曰「埃氏篩」。欲行是術。必先得一數。曰「甲」。乃行是術曰。 31 | 吾有一列。名之曰「掩」。為是「甲」遍。充「掩」以陽也。 32 | 除「甲」以二。名之曰「甲半」。 33 | 34 | 有數二。名之曰「戊」。恆為是。若「戊」不小於「甲半」者乃止也。 35 | 有數二。名之曰「戌」。恆為是。若「戌」不小於「甲半」者乃止也。 36 | 37 | 乘「戊」以「戌」。名之曰「合」 38 | 若「合」不大於「甲」者。 39 | 昔之「掩」之「合」者。今陰是矣。 40 | 若非乃止也。 41 | 加一以「戌」。昔之「戌」者。今其是矣云云。 42 | 加一以「戊」。昔之「戊」者。今其是矣云云。 43 | 44 | 吾有一列。名之曰「諸素」。 45 | 昔之「戊」者。今二是矣。恆為是。若「戊」等於「掩」之長者乃止也。 46 | 夫「掩」之「戊」。名之曰「素耶」。 47 | 若「素耶」者充「諸素」以「戊」也。 48 | 加一以「戊」。昔之「戊」者。今其是矣云云。 49 | 乃得「諸素」。 50 | 是謂「埃氏篩」之術也。 51 | 52 | 施「埃氏篩」於一百。書之。 53 | ``` 54 | 55 | Avec la soumission de mon travail hors de l'endroit, j'ai poussé un soupir de soulagement et je me suis enfoncé dans l'étreinte moelleuse du canapé, un sanctuaire de répit. Mes membres fatigués, accablés par le poids de la persévérance, ont trouvé du réconfort dans les coussins alors que je m'abandonnais au pur soulagement qui m'envahissait. 56 | -------------------------------------------------------------------------------- /content/fr/blogs/_dir.yml: -------------------------------------------------------------------------------- 1 | title: "mes blogs" 2 | navigation.description: "Lisez certains de mes blogs" 3 | -------------------------------------------------------------------------------- /content/fr/demo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: RxJS Primer 3 | description: The curated topics and concepts that are commonly used in RxJS 4 | topic: Programming 5 | displayTopic: Programming 6 | directory: programming 7 | author: 8 | - name: Shaun Chong 9 | avatar: levi.png 10 | tags: 11 | - reactive 12 | - rxjs 13 | - cheatsheet 14 | updatedAt: 2022-11-18T11:37:49.432Z 15 | createdAt: 2022-11-18T11:37:49.432Z 16 | --- 17 | 18 | > This is an article that I wrote on my technical blog website ["book"](https://book-dun-three.vercel.app/programming/rxjs-primer) that I use it here to demonstrate contents generated. 19 | 20 |
21 | 22 | RxJS is a [ReactiveX](https://reactivex.io/) implementation in JavaScript. ReactiveX is an API for asynchronous programming with observable streams. There are many more implementations of ReactiveX in other languages such as [RxJava](https://github.com/ReactiveX/RxJava) for Java, [Rx.NET](https://github.com/dotnet/reactive) for C#, [RxSwift](https://github.com/ReactiveX/RxSwift) for Swift etc. 23 | 24 | The streams of data encompasses database events, dom events and file uploads. 25 | 26 | ## Installation 27 | 28 | RxJS can be installed by using any of the node package managers out there and [Yarn](https://yarnpkg.com) is used for demonstration here. In addition, using TypeScript is highly recommended as the strict typings can make the code more robust and easier to read. 29 | 30 | ``` 31 | yarn add rxjs 32 | yarn add -D typescript ts-loader 33 | ``` 34 | 35 | If you are using [Webpack](https://webpack.js.org/) or other JavaScript bundler, make sure to configure the bundler to run accordingly and have a start script in `package.json` file. 36 | 37 | ```json [package.json] 38 | { 39 | "scripts": { 40 | "start": "webpack-dev-server --mode development" 41 | } 42 | } 43 | ``` 44 | 45 | ## Angular 46 | 47 | [Angular](https://angular.io/) is a JavaScript Framework developed by Google. RxJS can be used on the get-go as it is baked into Angular by default so there is no need for a seperate installation. All we need is just the [Angular CLI](https://angular.io/cli) to create a new project to work with. 48 | 49 | ``` 50 | ng new 51 | ``` 52 | 53 | Start the project after the dependencies have been installed. 54 | 55 | ## Observable 56 | 57 | It is a wrapper around a piece of data that can be subscribed to. The subscriber of that data will then get notified when there is changes on the data itself. 58 | 59 | Observable literally means _"something that can be observed"_. It can also be think as a pipe of data. 60 | 61 | The following code to create an observable is for demonstration purposes only. Observables can only be created in a useful way by some of the operators offered by RxJS library itself. 62 | 63 | The code creates an observable that will send the `'hello'` text upon subscription. 64 | 65 | ```ts 66 | import { Observable } from "rxjs"; 67 | 68 | var observable = Observable.create((observer) => { 69 | observer.next("hello"); 70 | observer.next("hello"); 71 | }); 72 | ``` 73 | 74 | To subscribe to the observer, use the `subscribe` method and it takes in one compulsory callback, and two optional callbacks as its argument. 75 | 76 | ```ts 77 | var observer = observable.subscribe( 78 | (x) => console.log("onSuccess: ", x), 79 | (err) => console.error("onError", err), 80 | () => console.log("onComplete") 81 | ); 82 | ``` 83 | 84 | The subscription will activate the observable and 2 lines of `onSuccess: hello` should be appearing in the browser dev tools. 85 | 86 | When the observer is marked as `complete`, it will be deactivated and no more data can be send through. 87 | 88 | ```ts 89 | var observable = Observable.create((observer) => { 90 | observer.next("hey"); 91 | observer.next("hey"); 92 | observer.complete(); 93 | observer.next("hey"); // not sent 94 | }); 95 | ``` 96 | 97 | ### Creating Observables 98 | 99 | As mentioned above, observables needs to be created with the officially endorsed way by RxJS. Here are some ways to create an observable. 100 | 101 | ```ts 102 | import { Observable, of, from, interval, fromEvent } from "rxjs"; 103 | ``` 104 | 105 | To wrap a raw value inside an observable, `of` can be used as it will only emit the value wrapped once and this is useful in software testing. However, there will be times that `of` can be useful in production code as well. 106 | 107 | ```ts 108 | const hello$ = of("hello"); 109 | 110 | hello$.subscribe((x) => console.log(x)); // hello 111 | ``` 112 | 113 | Next, the `from` operator takes in an iterable and emits them one by one. 114 | 115 | ```ts 116 | const hello$ = from("hello"); 117 | 118 | hello$.subscribe((x) => console.log(x)); // h, e, l, l, o 119 | ``` 120 | 121 | Next, the `fromEvent` operator is useful in composing events in DOM into observables. `fromEvent` takes in the DOM element as its first parameter and the event to be listened to as its second argument. 122 | 123 | ```ts 124 | const event$ = fromEvent(document, "click"); 125 | event$.subscribe((x) => console.log(x)); 126 | ``` 127 | 128 | Another observer creation method is `interval`, where it takes in the time inteval in miliseconds and perpetually emits an increment of integer by 1 starting from 0. 129 | 130 | ```ts 131 | const periodic$ = interval(1000); 132 | 133 | // 5 seconds passed 134 | periodic$.subscribe((x) => console.log(x)); // 0, 1, 2, 3, 4 135 | ``` 136 | 137 | ### Synchronous and Asynchronous 138 | 139 | RxJS can be both synchronous and asynchronous. 140 | 141 | ```ts 142 | const hello$ = of("hello"); 143 | hello$.subscribe((x) => console.log(x)); 144 | console.log("world"); 145 | ``` 146 | 147 | The above code yields result of `'hello'` first and subsequently `'world'` because the code execute synchronously from top to bottom all within the main thread. 148 | 149 | To make it asynchronous, `asyncScheduler` can be used. 150 | 151 | ```ts 152 | import { asyncScheduler } from "rxjs"; 153 | 154 | const hello$ = of("hello", asyncScheduler); 155 | hello$.subscribe((x) => console.log(x)); 156 | console.log("world"); 157 | ``` 158 | 159 | The output is `'world'` followed by `'hello'` because the subscription only happens on the second iteration of the asynchronous event loop whereas the line to print `'world'` is already completed in the first event loop. 160 | 161 | ### Hot and Cold Observables 162 | 163 | When the data is produced by the Observable itself, we call it a cold Observable. When the data is produced outside the Observable, we call it a hot Observable. Hot observables can have multiple subscriptions whereas cold observables can only have one subscription. If there are more than one subscription to a cold observable, the data obtained might differs. 164 | 165 | Cold observables is lazy. They will not create the values until they are subscribed to it. Here is an example of cold observable. 166 | 167 | ```ts 168 | const cold$ = Observable.create((observer) => observer.next(Math.random())); 169 | 170 | cold$.subscribe(console.log); // 0.5 171 | cold$.subscribe(console.log); // 0.89 172 | ``` 173 | 174 | However, this might not be useful in real life scenario and we want the data to be consistent. To achieve this, the cold observables needs to be converted into the hot observables. 175 | 176 | The first way is to move the data generation outside the observable. 177 | 178 | ```ts 179 | const random = Math.random(); 180 | 181 | const hot$ = Observable.create((observer) => observer.next(random)); 182 | 183 | hot$.subscribe(console.log); // 0.5 184 | hot$.subscribe(console.log); // no value 185 | ``` 186 | 187 | The second subscriber receives no value because the data is already emitted when the first observer subscribe to it. 188 | 189 | The other way to transform a cold observable to a hot observable is to use the `share` operator. 190 | 191 | ```ts 192 | const cold$ = Observable.create((observer) => observer.next(Math.random())); 193 | 194 | const hot$ = cold$.pipe(share()); 195 | 196 | hot$.subscribe(console.log); // 0.5 197 | hot$.subscribe(console.log); // no value 198 | ``` 199 | 200 | To make the second subscriber to receive the last value emitted, `shareReplay` can be used to replace the `share` operator. 201 | 202 | ```ts 203 | const cold$ = Observable.create((observer) => observer.next(Math.random())); 204 | 205 | const hot$ = cold$.pipe(shareReplay()); 206 | 207 | hot$.subscribe(console.log); // 0.5 208 | hot$.subscribe(console.log); // 0.5 209 | ``` 210 | 211 | ## Subject 212 | 213 | Subject is a different type of observable that can push values programmatically to it after the creation. 214 | 215 | ```ts 216 | import { Subject } from "rxjs"; 217 | 218 | var subject = new Subject(); 219 | subject.subscribe(console.log); 220 | subject.next("The first thing has been sent"); 221 | 222 | var observer = subject.subscribe(console.log); 223 | subject.next("The second thing has been sent"); 224 | observer.unsubscribe(); 225 | subject.next("The third thing has been sent"); 226 | ``` 227 | 228 | ### Behaviour Subject 229 | 230 | Behaviour subject will emit the last cached value upon new subsciption. 231 | 232 | ```ts 233 | var subject = new BehaviorSubject("First"); 234 | 235 | subject.subscribe((data) => addItem("observer 1 ", data)); 236 | ``` 237 | 238 | ### Replay Subject 239 | 240 | With behaviour subject, the late comers can only receive the last emitted item. However with replay subject, the late comers can receive $n$ amount of data upon subscription. 241 | 242 | ```ts 243 | var subject = new ReplaySubject(3); 244 | 245 | subject.next(1); 246 | subject.next(2); 247 | subject.subscribe(console.log); // 1, 2 248 | subject.next(3); // 3 249 | subject.next(4); // 4 250 | subject.subscribe(console.log); // 2, 3, 4 251 | ``` 252 | 253 | ### Async Subject 254 | 255 | The simplest subject of all. It will only emit the last value upon completion. 256 | 257 | ```ts 258 | var subject = new AsyncSubject(); 259 | 260 | subject.next(1); 261 | subject.subscribe(console.log); 262 | subject.complete(); // 1 263 | ``` 264 | 265 | ## Operators 266 | 267 | - Static Operators: These operators are usually used to create observables. 268 | - Instance Operators: These methods on observable instance (majority of RxJS) 269 | 270 | ### Modifier Operators 271 | 272 | These operator transform the existing value and modify the data flow. 273 | 274 | ```ts 275 | import { map, filter, take, scan } from "rxjs/operators"; 276 | 277 | const source$ = from([1, 2, 3, 4, 5]); 278 | const modified$ = source$.pipe( 279 | map((x) => x + 1), // 2, 3, 4, 5, 6 280 | scan((acc, val) => acc + val), // 2, 5, 9, 14, 20 281 | filter((x) => x > 10), // 14, 20 282 | take(1) // 14 283 | ); 284 | ``` 285 | 286 | ### Pluck 287 | 288 | A synthetic sugar for `map` to select only a certain keys in the array of object. 289 | 290 | ```ts 291 | const list$ = of([ 292 | { 293 | name: "Shino", 294 | age: 20, 295 | address: "Tokyo", 296 | }, 297 | { 298 | name: "Anthony", 299 | age: 21, 300 | address: "Berkeley", 301 | }, 302 | ]); 303 | 304 | const names$ = list$.pipe(pluck("name")); 305 | 306 | names$.subscribe(console.log); // 'Shino', 'Anthony' 307 | ``` 308 | 309 | ### Tap 310 | 311 | Tap operator allow side-effects to be triggered within the pipe. 312 | 313 | ```ts 314 | source$.pipe( 315 | tap(console.log), 316 | map((x) => x.toUpperCase()), 317 | tap(async (x) => { 318 | await Promise.resolve(); 319 | alert(x); 320 | }) 321 | ); 322 | ``` 323 | 324 | ### Handling Backpressure 325 | 326 | Backpressure is the observables emitting of an **overwhelmingly large amount** of values than we actually need. An epitome would be the inflow of dom events triggered by mouse move. 327 | 328 | The first strategy to handle this is to debounce the events. Debounce will not emit an event until the action has stopped for a period of time and this can be useful for typeahead when user is filling up an input field and a validation would only trigger after they have done typing. 329 | 330 | ```ts 331 | import { fromEvent } from "rxjs"; 332 | import { debounceTime } from "rxjs/operators"; 333 | 334 | const event$ = fromEvent(document, "mousemove"); 335 | 336 | const debounced$ = event$.pipe(debounceTime(1000)); 337 | debounced$.subscribe(console.log); 338 | ``` 339 | 340 | Throttling the event can also be useful as the number of events are significantly reduced by a specified time interval. Throttling can be think as rate-limiting. 341 | 342 | ```ts 343 | import { throttleTime } from "rxjs/operators"; 344 | 345 | const event$ = fromEvent(document, "mousemove"); 346 | 347 | const throttled$ = event$.pipe(throttleTime(1000)); 348 | throttled$.subscribe(console.log); 349 | ``` 350 | 351 | Buffer count on the other hand keeps all the event into an array and emit all of them at once when the buffer capacity has reached. 352 | 353 | ```ts 354 | import { bufferCount } from "rxjs/operators"; 355 | 356 | const event$ = fromEvent(document, "mousemove"); 357 | 358 | const buffered$ = event$.pipe(bufferCount(10)); 359 | buffered$.subscribe(console.log); 360 | ``` 361 | 362 | ### Switch Map 363 | 364 | Switch map allows two relational observables to interoperate for data fetching. 365 | 366 | ```ts 367 | const user$ = of({ uid: Math.random() }); 368 | const fetchOrders$ = (userId: number) => of(`${userId}'s order data'`); 369 | ``` 370 | 371 | First, we will need the user ID before we can fetch the order data. The intuitive way to do so is by nesting subscriptions. 372 | 373 | ```ts 374 | user$.subscribe({ uid } => { 375 | fetchOrders$(uid).subscribe(console.log) 376 | }) 377 | ``` 378 | 379 | The better way to make relational calls is by using switch map. 380 | 381 | ```ts 382 | const orders$ = user$.pipe(switchMap((user) => fetchOrders$(user.uid))); 383 | 384 | orders$.subscribe(console.log); 385 | ``` 386 | 387 | ### Combination Operators 388 | 389 | There are multiple ways to combine observables. **Combine latest** takes in an array of observables, and will wait for all values in each independent observables to resolve their value and only emit all the values together as an array. 390 | 391 | ```ts 392 | import { combineLatest } from "rxjs"; 393 | import { delay } from "rxjs/operators"; 394 | 395 | const randSync$ = Observable.create((o) => o.next(Math.random())); 396 | const randAsync$ = randSync$.pipe(delay(1000)); 397 | 398 | const combined$ = combineLatest([randSync$, randAsync$]); 399 | 400 | combined$.subscribe(console.log); // [0.5, 0.8] 401 | ``` 402 | 403 | **Merge** on the other hand fuse two observables into one to produce an ordinary observable. 404 | 405 | ```ts 406 | import { merge } from "rxjs"; 407 | import { delay } from "rxjs/operators"; 408 | 409 | const randSync$ = Observable.create((o) => o.next(Math.random())); 410 | const randAsync$ = randSync$.pipe(delay(1000)); 411 | 412 | const merged$ = merge([randSync$, randAsync$]); 413 | 414 | merged$.subscribe(console.log); // 0.5, 0.8 415 | ``` 416 | 417 | **Skip until** can be used to ignore the source observable until the second observable emits a value. 418 | 419 | ```ts 420 | var skipped$ = observable1$.skipUntil(observable2$); 421 | ``` 422 | 423 | ### Error Handling 424 | 425 | Error handling can be performed against observable inside the pipe. Retry mechanism can be implemented as well with the `retry` operator. 426 | 427 | ```ts 428 | import { catchError, retry } from "rxjs/operators"; 429 | 430 | someObservable$.pipe( 431 | catchError((err) => of("default value")), 432 | retry(2) 433 | ); 434 | ``` 435 | 436 | ## Memory Leaks 437 | 438 | Remember to unsubscribe any of the long running observables. 439 | 440 | ```ts 441 | const source$ = interval(100); 442 | 443 | const subscription = source.subscribe((x) => { 444 | console.log(x); 445 | if (x > 10) { 446 | subscription.unsubscribe(); 447 | } 448 | }); 449 | ``` 450 | 451 | A nicer way to handle this is to use `takeWhile` where it will stop emitting values when the conditions does not met anymore. 452 | 453 | ```ts 454 | source$.pipe(takeWhile((x) => x <= 10)); 455 | ``` 456 | 457 | To rely on another observables to stop emitting values instead, `takeUntil` can be used as once the other observable emits a value, the subscription to the current observable will automatically cancelled. 458 | 459 | ```ts 460 | source$.pipe(takeUntil(of("something"))); 461 | ``` 462 | 463 | ## Resources 464 | 465 | - [ReactiveX](https://reactivex.io/) 466 | - [RxJS](https://rxjs.dev/) 467 | - [RxJS Primer](https://www.learnrxjs.io/learn-rxjs/concepts/rxjs-primer) 468 | - [RxJS Overview](https://rxjs-dev.firebaseapp.com/guide/overview) 469 | - [Understanding hot vs cold Observables](https://luukgruijs.medium.com/understanding-hot-vs-cold-observables-62d04cf92e03) 470 | - [RxJS Top Ten - Code This, Not That - YouTube](https://www.youtube.com/watch?v=ewcoEYS85Co) 471 | -------------------------------------------------------------------------------- /content/fr/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Commencer 3 | description: Tout ce que vous devez savoir pour commencer avec ce modèle génial 4 | topic: Guide 5 | category: Guide 6 | authors: 7 | - name: Shaun 8 | avatar: shaun.png 9 | tags: 10 | - didacticiel 11 | - guide 12 | updatedAt: 2023-11-18T11:37:49.432Z 13 | createdAt: 2023-11-18T11:37:49.432Z 14 | --- 15 | 16 | > Remarque: Cette page est traduite à l'aide de Google Traduction 17 | 18 | ## Aperçu 19 | 20 | Ce modèle est mieux utilisé pour les sites statiques tels que la **documentation** et la **vitrine de portefeuille** qui peuvent bénéficier de l'utilisation de Markdown. 21 | 22 | La pile est composée de Vue.js et Nuxt.js comme technologie de base pour la création du modèle, Nuxt Content pour la gestion de contenu et complétée par Tailwind CSS comme bibliothèque de style. 23 | 24 | ## Installation 25 | 26 | Pour commencer, clonez le modèle de projet depuis GitHub. 27 | 28 | ``` 29 | git clone https://github.com/data-miner00/nuxt-content-template.git 30 | ``` 31 | 32 | Ensuite, installez les dépendances avec [Pnpm](https://pnpm.io). 33 | 34 | ``` 35 | pnpm i 36 | ``` 37 | 38 | Il est crucial d'utiliser le gestionnaire de packages Pnpm avec le fichier `pnpm-lock.yaml` actuel pour le moment car il existe des incompatibilités dans la nouvelle version de Nuxt et Nuxt Content. Cependant, si vous avez réussi à courir avec [Npm](https://npmjs.com/) ou [Yarn](https://yarnpkg.com/), bien sûr. 39 | 40 | Une fois l'installation terminée, démarrez le serveur de développement en exécutant la commande `dev`. 41 | 42 | ``` 43 | pnpm dev 44 | ``` 45 | 46 | ## Pré-écriture 47 | 48 | Avant d'écrire des articles dans le fichier `.md`, il est conseillé d'ajouter le code frontmatter qui décrit l'article afin qu'il puisse être utilisé lors de la mise en page de la page individuelle. L'exemple de frontmatter peut être trouvé dans le fichier `/templates/frontmatter.yml`. Il s'agit essentiellement d'un assortiment de propriétés personnalisées liées à l'article. 49 | 50 | ```yaml 51 | title: Titre de l'article 52 | description: Le sous-titre décrivant le titre plus en détail, doit être écrit en casse-phrase 53 | topic: Topic 54 | category: Category 55 | authors: 56 | - name: Contributor 1 57 | avatar: contrib_1.png 58 | - name: Contributor 2 59 | avatar: contrib_2.png 60 | tags: 61 | - tutorial 62 | - guide 63 | - cheatsheet 64 | updatedAt: 2022-11-18T11:37:49.432Z 65 | createdAt: 2022-11-18T11:37:49.432Z 66 | ``` 67 | 68 | Les propriétés recommandées sont répertoriées comme suit: 69 | 70 | - `title`: Le titre (h1) de l'article/sujet. 71 | - `description`: Une définition plus longue qui décrit l'intention et l'objectif de l'article. 72 | - `category`: Le sujet principal de l'article. 73 | - `tags`: Une liste de balises liées à l'article. 74 | - `createdAt`: L'horodatage de la création de l'article. 75 | - `updatedAt`: L'horodatage de la modification de l'article. 76 | 77 | Cependant, n'hésitez pas à ajouter d'autres champs personnalisés qui vous conviennent le mieux. 78 | 79 | Pour accéder aux champs, ils peuvent être obtenus à partir des composables `useAsyncData` comme suit. 80 | 81 | ```vue 82 | 87 | 88 | 91 | ``` 92 | 93 | L'exemple ci-dessus illustre l'accès de l'attribut personnalisé `title` à partir du frontmatter de l'article. D'autres valeurs définies peuvent être récupérées de la même manière. 94 | 95 | ## Writing Content 96 | 97 | Dans Nuxt, chaque fichier `.vue` dans le dossier `/pages` correspond directement au chemin du nom de fichier à partir de la racine en conséquence. Par exemple, le `pages/home.vue` correspond à `/home` dans l'application. 98 | 99 | Avec Nuxt Content, il est également possible de mapper le fichier `.md` dans le dossier `/content` directement à la racine. Cela nécessitera qu'un fichier `[...slug].vue` soit placé dans le dossier `/pages` pour fonctionner. 100 | 101 | S'il y a des conflits de noms entre le fichier `.md` et le fichier `.vue` dans les dossiers `/content` et `/pages`, le fichier `.vue` dans le dossier `/pages` aura la priorité et annulant la déclaration du fichier `.md`. 102 | 103 | Pour mémoire, cette page (`/guide`) est écrite en `.md` dans le dossier `/content` qui est ensuite rendu avec des styles et une mise en page prédéfinis. 104 | 105 | ### Table des matières 106 | 107 | Par défaut, le contenu Nuxt rend `

` et `

` uniquement dans la table des matières et ignorera tout `

` présent dans le démarquage car `

` est reconnu comme le titre principal de la article ou page. Tous les titres suivants de l'article devront commencer sémantiquement à partir de `

`. Par conséquent, il est recommandé d'utiliser les en-têtes `

` et supérieurs lors de l'écriture. 108 | 109 | Ce modèle n'affiche que les balises `

` dans la table des matières. Pour visualiser cela, considérez l'exemple suivant de structure de titre dans un article. 110 | 111 | ``` 112 | ├─ Titre 1 113 | │ ├─ Sous-titre A 114 | │ ├─ Sous-titre B 115 | │ └─ Sous-titre C 116 | ├─ Titre 2 117 | │ ├─ Sous-titre A 118 | │ ├─ Sous-titre B 119 | │ └─ Sous-titre C 120 | ├─ Titre 3 121 | └─ Titre 4 122 | ``` 123 | 124 | Dans la table des matières, le contenu généré sera le suivant. 125 | 126 | ``` 127 | ├─ Titre 1 128 | ├─ Titre 2 129 | ├─ Titre 3 130 | └─ Titre 4 131 | ``` 132 | 133 | Pour personnaliser ce comportement afin d'inclure également le sous-titre, rendez-vous sur `/pages/[...slug].vue` et décommentez les codes qui généreront le sous-titre. 134 | 135 | ### Commander du contenu 136 | 137 | Le classement par défaut est par ordre alphabétique. Pour personnaliser les commandes, faites précéder le nom du fichier de nombres suivis d'un `.` immédiat tel que `1.Introduction.md` et incrémentez le nombre pour les fichiers suivants. 138 | 139 | ```[Structure du répertoire] {1} 140 | content/ 141 | 1.frameworks/ 142 | 1.vue.md 143 | 2.nuxt.md 144 | 2.examples/ 145 | 1.vercel.md 146 | 2.netlify.md 147 | 3.heroku.md 148 | index.md 149 | ``` 150 | 151 | ### Images 152 | 153 | ![ma belle image](/images/demo.png) 154 | 155 | Voici à quoi ressemble une image de portrait dans la prose. Il est aligné à gauche et s'étendra jusqu'à la largeur maximale disponible dans la prose. Les images qui seront diffusées avec l'application peuvent être placées dans le répertoire "public". Par exemple, les images du dossier `/public/images` sont accessibles via le chemin `/images/img.jpg` directement. 156 | 157 | ```md 158 | ![ma belle image](/images/demo.png) 159 | ``` 160 | 161 | L'image de paysage occupera très probablement toute la largeur du conteneur si elle a une résolution suffisamment large. 162 | 163 | ![mon image de paysage cool](/images/demo_landscape.png) 164 | 165 | Pour les images situées dans le dossier `/assets/images`, elles devront être traitées par les outils de construction en les référençant à partir du code source en utilisant `require` ou toute forme d'importation avant qu'elles ne soient incluses dans la sortie finale de la construction. . 166 | 167 | ```vue 168 | 171 | ``` 172 | 173 | ### Tableau 174 | 175 | Voici à quoi ressemble le tableau par défaut. 176 | 177 | | Âge | Personne 1 | Personne 2 | Personne 3 | Moyenne | 178 | | --- | ---------- | ---------- | ---------- | ------- | 179 | | 8 | 6 | 7 | 5 | 6 | 180 | | 10 | 6.5 | 7.5 | 8.5 | 7.5 | 181 | | 12 | 8 | 9 | 10 | 9 | 182 | 183 | ### Clavier 184 | 185 | Les touches du clavier peuvent être représentées par la balise HTML `` telle que Esc, Entrée et Ctrl. 186 | 187 | ### Prise en charge de LaTeX 188 | 189 | Ce modèle est également livré avec le support de $\LaTeX$. Écrivez facilement de belles équations dans le Markdown ! 190 | 191 | ```latex [Équation de Schrödinger] 192 | \begin{equation} 193 | i \hbar \frac{\partial}{\partial t} \Psi \big(\textbf{r}, t) = \left[- \frac{\hbar^2}{2m}\nabla^2 + V(\textbf{r})\right]\Psi(\textbf{r}, t) 194 | \end{equation} 195 | ``` 196 | 197 | Le code $\LaTeX$ pour l'équation de Schrödinger affiché rendra la sortie riche comme indiqué ci-dessous. 198 | 199 | $$ 200 | \begin{equation} 201 | i \hbar \frac{\partial}{\partial t} \Psi \big(\textbf{r}, t) = \left[- \frac{\hbar^2}{2m}\nabla^2 + V(\textbf{r})\right]\Psi(\textbf{r}, t) 202 | \end{equation} 203 | $$ 204 | 205 | Voici un autre exemple de matrice rendue avec $\LaTeX$. 206 | 207 | ```latex 208 | \begin{pmatrix*}[r] 209 | -1 & 2 & 3 \\ 210 | 4 & -5 & 6 \\ 211 | 7 & 8 & -9 212 | \end{pmatrix*} 213 | ``` 214 | 215 | $$ 216 | \begin{pmatrix*}[r] 217 | -1 & 2 & 3 \\ 218 | 4 & -5 & 6 \\ 219 | 7 & 8 & -9 220 | \end{pmatrix*} 221 | $$ 222 | 223 | ## Coiffant 224 | 225 | Ce modèle ouvre beaucoup de possibilités de personnalisation en plus de ce qui est actuellement fourni par Nuxt, Nuxt Content, TailwindCSS et certaines de mes touches pour le rendre génial. 226 | 227 | ### Content 228 | 229 | Le contenu de l'article a été stylisé à l'aide du plugin Typography de Tailwind (`@tailwindcss/typography`) qui offre un ensemble de styles prédéfinis suffisants pour rendre l'article élégant et attrayant à lire. Le style peut être personnalisé sur la balise `
` dans `/pages/[...slug].vue` en utilisant le mot clé `prose`. 230 | 231 | Pour styliser un `

` à l'intérieur du bloc d'article, préfixez le style avec `prose-h2:` dans le bloc d'article où `prose` est défini dans la classe. 232 | 233 | ```html 234 |
235 | ``` 236 | 237 | En outre, pour modifier la façon dont le Markdown est traduit en composants Vue pour le rendu, créez les éléments Prose spécifiques dans le dossier `/components/content`. Nuxt utilisera automatiquement ces éléments pour restituer le contenu Markdown à la place. La liste des remplacements disponibles comprend `ProseCode`, `ProseH1`, `ProseA` etc. La liste complète peut être consultée dans le référentiel [GitHub officiel](https://github.com/nuxt/content/tree/main/src/runtime/components/Prose). 238 | 239 | Pour votre information, des exemples d'éléments Prose remplacés dans ce modèle sont `ProseCode`, `ProseH2` et `ProseH3` qui se trouvent dans le dossier `/components/content`. 240 | 241 | ### Composants personnalisés 242 | 243 | Nuxt Content permet la création de composants personnalisés qui peuvent être intégrés au script Markdown et le restituer en HTML. Il existe quelques syntaxes qui peuvent être utilisées pour présenter des composants personnalisés dans le Markdown. La première façon de le faire est d'utiliser la syntaxe MDC. 244 | 245 | Pour les **composants en ligne**, il peut être utilisé en ajoutant deux-points devant le nom du composant. Cela rendra le composant qui ne prend aucun accessoire ni enfant dans la page. 246 | 247 | ``` 248 | :my-component 249 | ``` 250 | 251 | Pour les **composants de bloc**, il peut être utilisé en préfixant le nom du composant avec un double deux-points, suivi d'un autre double-virgule fermant pour signifier la fin du composant. Le composant de bloc est le composant qui peut accepter le contenu Markdown ou un autre composant comme contenu d'emplacement. 252 | 253 | ``` 254 | ::card 255 | Bonjour le monde 256 | :: 257 | ``` 258 | 259 | **L'imbrication** est prise en charge pour le composant de bloc en tant que tel. 260 | 261 | ``` 262 | ::hero 263 | :::card 264 | Une carte imbriquée 265 | ::card 266 | Une carte super imbriquée 267 | :: 268 | ::: 269 | :: 270 | ``` 271 | 272 | Pour effectuer le rendu d'un **emplacement nommé** personnalisé à l'intérieur du composant, utilisez le modèle `#slot-name` pour indiquer quel contenu doit être rendu dans quel emplacement. 273 | 274 | ``` 275 | ::card 276 | Le texte par défaut de l'emplacement 277 | 278 | #description 279 | Cela sera rendu dans l'emplacement `description` du composant. 280 | :: 281 | ``` 282 | 283 | Pour un composant personnalisé qui accepte les **props**, placez simplement les paires clé-valeur souhaitées dans une paire d'accolades immédiatement après le nom du composant. 284 | 285 | ``` 286 | ::alert{type="warning" color="default"} 287 | Le composant **alerte** 288 | :: 289 | ``` 290 | 291 | Une autre façon de transmettre des accessoires à un composant personnalisé consiste à utiliser la **méthode YAML** illustrée ci-dessous. 292 | 293 | ``` 294 | ::alert 295 | --- 296 | type: warning 297 | color: default 298 | --- 299 | Le composant **alerte** 300 | :: 301 | ``` 302 | 303 | Voici un petit exemple d'utilisation du composant personnalisé avec la syntaxe MDC. Le composant personnalisé nommé "Card.vue" est utilisé pour la démonstration suivante. 304 | 305 | ``` 306 | ::card{title="Titre génial!" footer="Ceci est un pied de page discret"} 307 | Ceci est un exemple de paragraphe qui est **passé** dans l'**emplacement par défaut** de la carte personnalisée pour le rendu. 308 | :: 309 | ``` 310 | 311 | L'extrait de code ci-dessus aura son contenu rendu comme ci-dessous. 312 | 313 | ::card{title="Titre génial!" footer="Ceci est un pied de page discret"} 314 | Ceci est un exemple de paragraphe qui est **passé** dans l'**emplacement par défaut** de la carte personnalisée pour le rendu. 315 | :: 316 | 317 | ### Thème sombre 318 | 319 | Ce modèle utilise le mode sombre basé sur les classes de Tailwind pour la thématisation. Un composant de changement de thème prêt à l'emploi est déjà fourni. Il utilise le composable [useColorMode](https://color-mode.nuxtjs.org/) du mode de couleur Nuxt pour prendre en charge le changement de thème et la persistance de la préférence de thème via l'API de stockage local du navigateur. C'est incroyablement pratique. 320 | 321 | ```ts 322 | const colorMode = useColorMode(); 323 | 324 | colorMode.preference = "dark"; 325 | colorMode.preference = "light"; 326 | colorMode.preference = "system"; 327 | ``` 328 | 329 | ### Blocs de code 330 | 331 | Le style des blocs de code peut être modifié à votre guise en vous rendant dans la section `content` du fichier de configuration `nuxt.config.ts`. 332 | 333 | ```ts [nuxt.config.ts] 334 | export default defineNuxtConfig({ 335 | content: { 336 | documentDriven: true, 337 | highlight: { 338 | theme: { 339 | default: "github-light", 340 | dark: "github-dark", 341 | }, 342 | preload: ["cpp", "csharp", "rust", "wenyan"], 343 | }, 344 | }, 345 | }); 346 | ``` 347 | 348 | Dans la section `highlight`, vous pouvez choisir le thème qui affichera les blocs de code pour les modes clair et sombre. Le contenu Nuxt utilise [Shiki](https://github.com/shikijs/shiki) comme surligneur de code et il est accompagné d'un large éventail de [thèmes populaires](https://github.com/shikijs/shiki/blob/main/docs/themes.md) prêt à être utilisé. 349 | 350 | La prise en charge de la coloration syntaxique n'est disponible que pour un ensemble limité de langages par défaut tels que HTML, JavaScript, TypeScript et Vue. Pour activer la mise en surbrillance de la langue de votre choix, ajoutez simplement l'[identifiant de langue](https://github.com/shikijs/shiki/blob/main/docs/languages.md) dans le tableau "preload". 351 | 352 | ### Tailwind dans Markdown 353 | 354 | Étant donné que Tailwind a été configuré pour surveiller les styles dans les fichiers `.md` dans `tailwind.config.js`, il peut être utilisé librement n'importe où dans le fichier. Par exemple, si vous souhaitez styliser un mot particulier avec une classe Tailwind `text-pink-400`, encapsulez-le simplement dans une balise HTML span et attribuez-lui le nom de la classe. 355 | 356 | ```md 357 | ## Mon titre superflu 358 | 359 | Lorem ipsum dolor sit amet, adispicing elit. 360 | ``` 361 | 362 | Une autre façon de styliser un texte en ligne consiste à utiliser la syntaxe indiquée ci-dessous. Il est plus simple et soigné que l'exemple précédent. 363 | 364 | ```md 365 | Lorem [ipsum]{.text-pink-400} dolor sit amet, adispicing elit. 366 | ``` 367 | 368 | > https://tailwindcss.nuxtjs.org/examples/content 369 | 370 | ## Internationalisation 371 | 372 | L'internationalisation est déjà intégrée à ce modèle à l'aide de [Nuxt i18n](https://v8.i18n.nuxtjs.org/). Le composant de changement de langue est également fourni pour permettre une transition facile entre les langues disponibles (dans ce cas, l'anglais et le français) de manière transparente. 373 | 374 | L'URL de la locale non par défaut sera préfixée avec son code alors que la locale par défaut ne nécessite pas de préfixe. 375 | 376 | ### Définition des mots 377 | 378 | Pour définir un mot pour les langues prévues, il existe une section dans `nuxt.config.ts` nommée `vueI18n` dans l'objet `i18n` pour les définir. Par exemple, pour définir le monde "welcome" pour l'anglais et le français, créez une propriété appelée `welcome` dans leurs objets régionaux respectifs à l'intérieur de `messages` avec leur valeur correspondante. 379 | 380 | ```ts [nuxt.config.ts] 381 | export default defineNuxtConfig({ 382 | i18n: { 383 | vueI18n: { 384 | messages: { 385 | en: { 386 | welcome: "Welcome", 387 | }, 388 | fr: { 389 | welcome: "Bienvenue", 390 | }, 391 | }, 392 | }, 393 | }, 394 | }); 395 | ``` 396 | 397 | Le mot nouvellement défini peut être utilisé n'importe où dans le projet à l'intérieur de la balise `templates` en interpolant avec la fonction `$t` qui prend la clé du mot défini. 398 | 399 | ```vue 400 | 403 | ``` 404 | 405 | Avec cela en place, Nuxt est assez intelligent pour restituer correctement "Bienvenue" ou "Bienvenue" lorsque le contexte de la langue a changé. 406 | 407 | ### Définition du fichier de paramètres régionaux 408 | 409 | Bien que la solution ci-dessus consistant à ajouter une nouvelle définition de mots dans le fichier `nuxt.config.ts` fonctionne, elle peut poser un réel problème lorsque le **vocabulaire s'agrandit** à mesure que le fichier devient lourd à maintenir. 410 | 411 | Heureusement, il existe un autre moyen préféré de stocker les définitions de langage dans leur propre fichier JSON séparé. Avec cette approche, non seulement il atteint le principe de responsabilité unique, mais il améliore également considérablement la maintenabilité des fichiers. 412 | 413 | Voici comment il est configuré dans `nuxt.config.ts`. 414 | 415 | ```ts [nuxt.config.ts] 416 | export default defineNuxtConfig({ 417 | i18n: { 418 | langDir: "locales", 419 | locales: [ 420 | { 421 | code: "en", 422 | file: "en.json", 423 | }, 424 | { 425 | code: "fr", 426 | file: "fr.json", 427 | }, 428 | ], 429 | }, 430 | }); 431 | ``` 432 | 433 | Le code ci-dessus indique à Nuxt de localiser la définition anglaise dans le fichier "en.json" et la définition française dans le fichier "fr.json" dans le dossier "locales". 434 | 435 | ### I18n dans le contenu Nuxt 436 | 437 | Pour prendre en charge l'internationalisation des contenus basés sur Markdown à partir de Nuxt Content, créez un dossier correspondant dans le dossier `content` avec le code de la locale non par défaut et imitez la structure du dossier de base. 438 | 439 | Par exemple, étant donné que j'ai la structure de fichiers suivante qui a un contenu en anglais, le contenu en français peut être hébergé de la manière suivante. 440 | 441 | De: 442 | 443 | ```[Structure du répertoire] 444 | ├─ content 445 | │ ├─ blogs 446 | │ │ ├─ blog1.md 447 | │ │ └─ blog2.md 448 | │ ├─ demo.md 449 | │ └─ guide.md 450 | ``` 451 | 452 | En: 453 | 454 | ```[Structure du répertoire] 455 | ├─ content 456 | │ ├─ blogs 457 | │ │ ├─ blog1.md (Anglais) 458 | │ │ └─ blog2.md (Anglais) 459 | │ ├─ fr 460 | │ │ ├─ blogs 461 | │ │ │ ├─ blog1.md (Français) 462 | │ │ │ └─ blog2.md (Français) 463 | │ │ ├─ demo.md (Français) 464 | │ │ └─ guide.md (Français) 465 | │ ├─ demo.md (Anglais) 466 | │ └─ guide.md (Anglais) 467 | ``` 468 | 469 | En faisant cela, nous utilisons le comportement de l'URL préfixée pour les paramètres régionaux non par défaut et cela fait l'affaire. Ce n'est pas la solution la plus élégante mais ça marche pour l'instant. 470 | 471 | ### Liens internationalisés 472 | 473 | Pour nous assurer que chaque lien du site Web correspond à ses homologues linguistiques, nous devons prétraiter les liens avec le composable `useLocalePath`. Voici à quoi cela ressemble dans le code. 474 | 475 | ```vue 476 | 479 | 480 | 485 | ``` 486 | 487 | Cela garantira que lorsque vous êtes dans le contexte anglais, le lien vous redirigera vers la page normale `/careers` alors que si vous êtes dans le contexte français, il pointera vers `/fr/careers` pour sa version française. 488 | 489 | ## Références de style APA 490 | 491 | De plus, ce modèle est également livré avec un simple composant de citation de style APA qui peut être utilisé dans le fichier Markdown à l'aide de la syntaxe MDC. Les exemples et les styles sont conçus sur la base de cet [article Scribbr](https://www.scribbr.com/apa-examples/website/). 492 | 493 | ``` 494 | ::apa-reference 495 | --- 496 | authors: 497 | - Greenhouse, S 498 | date: 2020, July 30 499 | title: The coronavirus pandemic has intensified systemic economic racism against black Americans 500 | publisher: The New Yorker 501 | url: https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-economic-racism-against-black-americans 502 | source: newspaper 503 | --- 504 | :: 505 | ``` 506 | 507 | ### Exemples 508 | 509 | 510 | ::apa-reference 511 | --- 512 | authors: 513 | - Greenhouse, S 514 | date: 2020, July 30 515 | title: The coronavirus pandemic has intensified systemic economic racism against black Americans 516 | publisher: The New Yorker 517 | url: https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-economic-racism-against-black-americans 518 | source: newspaper 519 | --- 520 | :: 521 | 522 | ::apa-reference 523 | --- 524 | authors: 525 | - Lee, C 526 | date: 2020, February 19 527 | title: A tale of two reference formats 528 | publisher: APA Style Blog 529 | url: https://apastyle.apa.org/blog/two-reference-formats 530 | source: blogs 531 | --- 532 | :: 533 | 534 | ::apa-reference 535 | --- 536 | authors: 537 | - Rowlatt, J 538 | date: 2020, October 19 539 | title: Could cold water hold a clue to a dementia cure? 540 | publisher: BBC News 541 | url: https://www.bbc.com/news/health-54531075 542 | source: online-news 543 | --- 544 | :: 545 | 546 | ::apa-reference 547 | --- 548 | organization: Scribbr 549 | date: n.d. 550 | title: How to cite a website in APA style 551 | url: https://www.scribbr.com/apa-examples/website/ 552 | source: websites 553 | --- 554 | :: 555 | 556 | ::apa-reference 557 | --- 558 | date: 2020, October 19 559 | title: "The countdown: A prophecy, crowds and a TikTok takedown" 560 | publisher: BBC News 561 | url: https://www.bbc.com/news/election-us-2020-54596667 562 | source: online-news 563 | --- 564 | :: 565 | 566 | -------------------------------------------------------------------------------- /content/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | description: Everything you need to know to get started with this awesome template 4 | topic: Guide 5 | category: Guide 6 | authors: 7 | - name: Shaun 8 | avatar: shaun.png 9 | tags: 10 | - tutorial 11 | - guide 12 | updatedAt: 2024-03-28T11:05:53.157Z 13 | createdAt: 2023-11-18T11:37:49.432Z 14 | --- 15 | 16 | ## Overview 17 | 18 | This template is best used for static sites such as **documentation** and **portfolio showcase** that can be benefited by using Markdown. 19 | 20 | The stack is comprised of Vue.js and Nuxt.js as the core technology for building the template, Nuxt Content for content management and supplemented by Tailwind CSS as the styling library. 21 | 22 | ## Installation 23 | 24 | To get started, clone the project template from GitHub. 25 | 26 | ``` 27 | git clone https://github.com/data-miner00/nuxt-content-template.git 28 | ``` 29 | 30 | Next, install the dependencies with [Pnpm](https://pnpm.io). 31 | 32 | ``` 33 | pnpm i 34 | ``` 35 | 36 | It is crucial to use the Pnpm package manager with the current `pnpm-lock.yaml` file for the time being as there are incompatibilities in the new version of Nuxt and Nuxt Content. However, if you managed to run with [Npm](https://npmjs.com/) or [Yarn](https://yarnpkg.com/), by all means. 37 | 38 | After the installation is completed, start the development server by running the `dev` command. 39 | 40 | ``` 41 | pnpm dev 42 | ``` 43 | 44 | ## Pre-writing 45 | 46 | Before writing any articles in the `.md` file, it is advised to add the frontmatter code that describes the article so that it can be used when laying out the individual page. The frontmatter example can be found at `/templates/frontmatter.yml` file. It is essentially an assortment of custom properties that relates to the article. 47 | 48 | ```yaml 49 | title: Title of the Article 50 | description: The subtitle describing the title in more details, should be written in sentence-case 51 | topic: Topic 52 | category: Category 53 | authors: 54 | - name: Contributor 1 55 | avatar: contrib_1.png 56 | - name: Contributor 2 57 | avatar: contrib_2.png 58 | tags: 59 | - tutorial 60 | - guide 61 | - cheatsheet 62 | updatedAt: 2022-11-18T11:37:49.432Z 63 | createdAt: 2022-11-18T11:37:49.432Z 64 | ``` 65 | 66 | The recommended properties are listed as follows: 67 | 68 | - `title`: The title (h1) of the article/topic. 69 | - `description`: A longer definition that describes the intention and objective of the article. 70 | - `category`: The main topic of the article. 71 | - `tags`: A list of tags that are related to the article. 72 | - `createdAt`: The timestamp of article creation. 73 | - `updatedAt`: The timestamp of article modification. 74 | 75 | However, feel free to add in more custom fields that makes most sense to you. 76 | 77 | To access the fields, they can be obtained from the `useAsyncData` composables as follow. 78 | 79 | ```vue 80 | 85 | 86 | 89 | ``` 90 | 91 | The example above demonstrates the access of the custom `title` attribute from the frontmatter of the article. Other defined values can be retrieved similarly. 92 | 93 | ## Writing Content 94 | 95 | In Nuxt, every `.vue` file inside the `/pages` folder maps directly to the path of the file name from root accordingly. For instance, the `pages/home.vue` maps to `/home` in the application. 96 | 97 | With Nuxt Content, it is possible to map `.md` file within the `/content` folder directly to root as well. This will require a `[...slug].vue` file to be placed within the `/pages` folder in order to work. 98 | 99 | If there are conflicting names between the `.md` file and the `.vue` file in both `/content` and `/pages` folder, the `.vue` file in the `/pages` folder will get the precedence and annulling the declaration of the `.md` file. 100 | 101 | For the record, this page (`/guide`) is written in `.md` within the `/content` folder that is then rendered with predefined stylings and layout. 102 | 103 | ### Table of Content 104 | 105 | By default, Nuxt content renders `

` and `

` only in the ToC and will skip any `

` that is present to the markdown because `

` is recognized as the main title of the article or page. Any subsequent headings in the article will have to start from `

` semantically. Hence, it is recommended to use headings `

` and above when writing. 106 | 107 | This template only renders out the `

` tags in the table of content. To visualize this, consider the following example of heading structure within an article. 108 | 109 | ``` 110 | ├─ Heading 1 111 | │ ├─ Subheading A 112 | │ ├─ Subheading B 113 | │ └─ Subheading C 114 | ├─ Heading 2 115 | │ ├─ Subheading A 116 | │ ├─ Subheading B 117 | │ └─ Subheading C 118 | ├─ Heading 3 119 | └─ Heading 4 120 | ``` 121 | 122 | In the ToC, the contents generated will be as follows. 123 | 124 | ``` 125 | ├─ Heading 1 126 | ├─ Heading 2 127 | ├─ Heading 3 128 | └─ Heading 4 129 | ``` 130 | 131 | To customize this behaviour to include the subheading as well, head over to `/pages/[...slug].vue` and uncomment the codes that will generate the subheading. 132 | 133 | ### Ordering Content 134 | 135 | The default ordering is by alphabetical order. To customize the orderings, prepend the filename with numbers followed by an immediate `.` such as `1.Introduction.md` and increment the count for subsequent files. 136 | 137 | ```[Directory Structure] {1} 138 | content/ 139 | 1.frameworks/ 140 | 1.vue.md 141 | 2.nuxt.md 142 | 2.examples/ 143 | 1.vercel.md 144 | 2.netlify.md 145 | 3.heroku.md 146 | index.md 147 | ``` 148 | 149 | ### Images 150 | 151 | ![my cool image](/images/demo.png) 152 | 153 | This is how a portrait image looks like within the prose. It is left aligned and will extend to the max width available in the prose. Images that will be served alongside with the app can be placed within the `public` directory. For instance, images within the `/public/images` folder can be accessed via the path `/images/img.jpg` directly. 154 | 155 | ```md 156 | ![my cool image](/images/demo.png) 157 | ``` 158 | 159 | The landscape image will most probably took over the full width of the container if they have a wide enough resolution. 160 | 161 | ![my cool landscape image](/images/demo_landscape.png) 162 | 163 | For images that are located in `/assets/images` folder, they will need to be processed by the build tools by referencing them from source code by using `require` or any form of import before they will be included in the final build output. 164 | 165 | ```vue 166 | 169 | ``` 170 | 171 | ### Tables 172 | 173 | This is how the default table looks like. 174 | 175 | | Age | Person 1 | Person 2 | Person 3 | Average | 176 | | --- | -------- | -------- | -------- | ------- | 177 | | 8 | 6 | 7 | 5 | 6 | 178 | | 10 | 6.5 | 7.5 | 8.5 | 7.5 | 179 | | 12 | 8 | 9 | 10 | 9 | 180 | 181 | ### Keyboard 182 | 183 | Keyboard keys can be represented with the `` HTML tag such as Esc, Enter and Ctrl. 184 | 185 | ### LaTeX Support 186 | 187 | This template also comes with the support for $\LaTeX$. Write beautiful equations within the Markdown with ease! 188 | 189 | ```latex [Schrödinger equation] 190 | \begin{equation} 191 | i \hbar \frac{\partial}{\partial t} \Psi \big(\textbf{r}, t) = \left[- \frac{\hbar^2}{2m}\nabla^2 + V(\textbf{r})\right]\Psi(\textbf{r}, t) 192 | \end{equation} 193 | ``` 194 | 195 | The $\LaTeX$ code for Schrödinger equation shown will render the rich output as shown below. 196 | 197 | $$ 198 | \begin{equation} 199 | i \hbar \frac{\partial}{\partial t} \Psi \big(\textbf{r}, t) = \left[- \frac{\hbar^2}{2m}\nabla^2 + V(\textbf{r})\right]\Psi(\textbf{r}, t) 200 | \end{equation} 201 | $$ 202 | 203 | Here is another example of matrix rendered with $\LaTeX$. 204 | 205 | ```latex 206 | \begin{pmatrix*}[r] 207 | -1 & 2 & 3 \\ 208 | 4 & -5 & 6 \\ 209 | 7 & 8 & -9 210 | \end{pmatrix*} 211 | ``` 212 | 213 | $$ 214 | \begin{pmatrix*}[r] 215 | -1 & 2 & 3 \\ 216 | 4 & -5 & 6 \\ 217 | 7 & 8 & -9 218 | \end{pmatrix*} 219 | $$ 220 | 221 | ## Styling 222 | 223 | This template opens up a lot of room for customization on top of what's currently provided by Nuxt, Nuxt Content, TailwindCSS and some of my touches to make it great. 224 | 225 | ### Content 226 | 227 | The content of the article was styled using Tailwind's Typography plugin (`@tailwindcss/typography`) that offers a set of pre-defined styles that is enough the make the article sleak and appealing to read through. The style can be customized on the `
` tag in `/pages/[...slug].vue` by using the `prose` keyword. 228 | 229 | To style an `

` inside the article block, prefix the style with `prose-h2:` in the article block where `prose` is defined in the class. 230 | 231 | ```html 232 |
233 | ``` 234 | 235 | Besides, to modify how the Markdown is translated to Vue components for rendering, create the specific Prose elements within the `/components/content` folder. Nuxt will automatically use those elements to render out the Markdown content instead. The list of available overrides includes `ProseCode`, `ProseH1`, `ProseA` etc. The full list can be inspected in the official [GitHub repo](https://github.com/nuxt/content/tree/main/src/runtime/components/Prose). 236 | 237 | For your reference, examples of the overridden Prose element in this template are `ProseCode`, `ProseH2` and `ProseH3` that can be found in the `/components/content` folder. 238 | 239 | ### Custom Components 240 | 241 | Nuxt Content allows the creation of custom components that can be embedded to the Markdown script and render it as HTML altogether. There are a couple of syntax that can be used to feature custom components in the Markdown. The first way to do so is by using the MDC syntax. 242 | 243 | For **inline components**, it can be used by prepending a colon in front of the component's name. This will render the component that takes in no props nor children into the page. 244 | 245 | ``` 246 | :my-component 247 | ``` 248 | 249 | For **block components**, it can be used by prefixing the component's name with a double colon, followed by another closing double colon to signify the end of the component. Block component is the component that can accept Markdown content or another component as it's slot content. 250 | 251 | ``` 252 | ::card 253 | Hello world 254 | :: 255 | ``` 256 | 257 | **Nesting** is supported for the block component as such. 258 | 259 | ``` 260 | ::hero 261 | :::card 262 | A nested card 263 | ::card 264 | A super nested card 265 | :: 266 | ::: 267 | :: 268 | ``` 269 | 270 | To render for a custom **named slot** inside the component, use the `#slot-name` pattern to outline which content to be rendered within which slot. 271 | 272 | ``` 273 | ::card 274 | The slot default text 275 | 276 | #description 277 | This will be rendered within the `description` slot in the component. 278 | :: 279 | ``` 280 | 281 | For a custom component that accepts **props**, simply enclose the desired key-value pairs within a pair of curly braces immediately after the component's name. 282 | 283 | ``` 284 | ::alert{type="warning" color="default"} 285 | The **alert** component 286 | :: 287 | ``` 288 | 289 | Another way of passing props to a custom component is by using the **YAML method** that is demonstrated as below. 290 | 291 | ``` 292 | ::alert 293 | --- 294 | type: warning 295 | color: default 296 | --- 297 | The **alert** component 298 | :: 299 | ``` 300 | 301 | Here is a small example on using the custom component with the MDC syntax. The custom component named `Card.vue` is used for the following demo. 302 | 303 | ``` 304 | ::card{title="Awesome title!" footer="This is an inconspicuous footer"} 305 | This is a sample paragraph that is **passed** into the custom card's **default slot** for rendering. 306 | :: 307 | ``` 308 | 309 | The code snippet above will have it's content rendered as below. 310 | 311 | ::card{title="Awesome title!" footer="This is an inconspicuous footer"} 312 | This is a sample paragraph that is **passed** into the custom card's **default slot** for rendering. 313 | :: 314 | 315 | ### Dark Theme 316 | 317 | This template uses Tailwind's class-based Dark Mode for theming. A ready to use theme switching component is already provided. It uses the Nuxt Color Mode's [useColorMode](https://color-mode.nuxtjs.org/) composable to take care of the theme switching and persisting the theme preference through the browser's Local Storage API. It is incredibly convenient. 318 | 319 | ```ts 320 | const colorMode = useColorMode(); 321 | 322 | colorMode.preference = "dark"; 323 | colorMode.preference = "light"; 324 | colorMode.preference = "system"; 325 | ``` 326 | 327 | ### Code Blocks 328 | 329 | The style of the code blocks can be modified to suits your liking by heading over to the `content` section in the `nuxt.config.ts` configuration file. 330 | 331 | ```ts [nuxt.config.ts] 332 | export default defineNuxtConfig({ 333 | content: { 334 | documentDriven: true, 335 | highlight: { 336 | theme: { 337 | default: "github-light", 338 | dark: "github-dark", 339 | }, 340 | preload: ["cpp", "csharp", "rust", "wenyan"], 341 | }, 342 | }, 343 | }); 344 | ``` 345 | 346 | In the `highlight` section, you can choose the theme that will render out the code blocks for both light and dark mode. Nuxt Content uses [Shiki](https://github.com/shikijs/shiki) as their code highlighter and it comes along with a wide array of [popular themes](https://github.com/shikijs/shiki/blob/main/docs/themes.md) ready to be used. 347 | 348 | The syntax highlighting support are only available for a limited set of languages by default such as HTML, JavaScript, TypeScript and Vue. To enable highlighting for the language of choice, just add in the [language identifier](https://github.com/shikijs/shiki/blob/main/docs/languages.md) into the `preload` array. 349 | 350 | ### Tailwind in Markdown 351 | 352 | Since Tailwind has been configured to watch for styles in `.md` files in the `tailwind.config.js`, it can be used freely anywhere in the file. For example, if you want to style a particular word with a `text-pink-400` Tailwind class, just wrap it inside a HTML span tag and assign the class name to it. 353 | 354 | ```md 355 | ## My Superfluous Title 356 | 357 | Lorem ipsum dolor sit amet, adispicing elit. 358 | ``` 359 | 360 | Another way to style an inline text is by using the syntax as shown below. It is simpler and neat than the previous example. 361 | 362 | ```md 363 | Lorem [ipsum]{.text-pink-400} dolor sit amet, adispicing elit. 364 | ``` 365 | 366 | > https://tailwindcss.nuxtjs.org/examples/content 367 | 368 | ## Internationalization 369 | 370 | Internationalization is already baked into this template with the help of [Nuxt i18n](https://v8.i18n.nuxtjs.org/). The language switcher component is also provided that allows easy transition between the languages that is available (in this case English and French) seamlessly. 371 | 372 | The url of the non-default locale will be prefixed with it's code whereas the default locale does not require prefixing. 373 | 374 | ### Defining Words 375 | 376 | To define a word for the intended languages, there is a section in `nuxt.config.ts` named `vueI18n` within the `i18n` object to define them. For instance, to define the world "welcome" for both English and French, create a property called `welcome` within their respective locales object inside `messages` with their corresponding value will do. 377 | 378 | ```ts [nuxt.config.ts] 379 | export default defineNuxtConfig({ 380 | i18n: { 381 | vueI18n: { 382 | messages: { 383 | en: { 384 | welcome: "Welcome", 385 | }, 386 | fr: { 387 | welcome: "Bienvenue", 388 | }, 389 | }, 390 | }, 391 | }, 392 | }); 393 | ``` 394 | 395 | The newly defined word can be used anywhere within the project inside the `templates` tag by interpolating with the `$t` function that takes in the key of the defined word. 396 | 397 | ```vue 398 | 401 | ``` 402 | 403 | With this in place, Nuxt is smart enough to render out "Welcome" or "Bienvenue" correctly when the language context changed. 404 | 405 | ### Locales File Definition 406 | 407 | While the above solution of adding new definition of words in the `nuxt.config.ts` file works, it can pose a real problem when the **vocabulary grows** as the file become cumbersome to maintain. 408 | 409 | Fortunately, there is another preferred way to store the language definitions in their own, separate JSON file. With this approach, not only it achieves the Single Responsibility Principle, it also drastically improve the maintainability of the files. 410 | 411 | Here is how it is configured in `nuxt.config.ts`. 412 | 413 | ```ts [nuxt.config.ts] 414 | export default defineNuxtConfig({ 415 | i18n: { 416 | langDir: "locales", 417 | locales: [ 418 | { 419 | code: "en", 420 | file: "en.json", 421 | }, 422 | { 423 | code: "fr", 424 | file: "fr.json", 425 | }, 426 | ], 427 | }, 428 | }); 429 | ``` 430 | 431 | The above code tells Nuxt to locate English definition in `en.json` file and French definition in `fr.json` file inside the `locales` folder. 432 | 433 | ### I18n in Nuxt Content 434 | 435 | To support internationalization for Markdown based contents from Nuxt Content, create a corresponding folder inside the `content` folder with the non-default locale's code and imitate the structure of the base folder. 436 | 437 | For example, given I have the following file structure that has English contents, the French contents can be housed in the following manner. 438 | 439 | From: 440 | 441 | ```[Directory Structure] 442 | ├─ content 443 | │ ├─ blogs 444 | │ │ ├─ blog1.md 445 | │ │ └─ blog2.md 446 | │ ├─ demo.md 447 | │ └─ guide.md 448 | ``` 449 | 450 | To: 451 | 452 | ```[Directory Structure] 453 | ├─ content 454 | │ ├─ blogs 455 | │ │ ├─ blog1.md (English) 456 | │ │ └─ blog2.md (English) 457 | │ ├─ fr 458 | │ │ ├─ blogs 459 | │ │ │ ├─ blog1.md (French) 460 | │ │ │ └─ blog2.md (French) 461 | │ │ ├─ demo.md (French) 462 | │ │ └─ guide.md (French) 463 | │ ├─ demo.md (English) 464 | │ └─ guide.md (English) 465 | ``` 466 | 467 | By doing this, we are utilizing the behaviour of the prefixed URL for non-default locale and it does the trick. Not the most elegant solution but it works for now. 468 | 469 | ### Internationalized Links 470 | 471 | To make sure that every links in the website corresponds to its language counterparts, we have to preprocess the links with the `useLocalePath` composable. Here is how it looks like in code. 472 | 473 | ```vue 474 | 477 | 478 | 483 | ``` 484 | 485 | This will ensures when you are in the English context, the link will redirect you to the normal `/careers` page whereas if you are in the French context, it will points to `/fr/careers` for its French version. 486 | 487 | ## APA Style References 488 | 489 | Additionally, this template also comes with a simple APA style citation component that can be utilized in the Markdown file using the MDC syntax. The examples and styles are crafted base on this [Scribbr article](https://www.scribbr.com/apa-examples/website/). 490 | 491 | ``` 492 | ::apa-reference 493 | --- 494 | authors: 495 | - Greenhouse, S 496 | date: 2020, July 30 497 | title: The coronavirus pandemic has intensified systemic economic racism against black Americans 498 | publisher: The New Yorker 499 | url: https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-economic-racism-against-black-americans 500 | source: newspaper 501 | --- 502 | :: 503 | ``` 504 | 505 | ### Examples 506 | 507 | 508 | ::apa-reference 509 | --- 510 | authors: 511 | - Greenhouse, S 512 | date: 2020, July 30 513 | title: The coronavirus pandemic has intensified systemic economic racism against black Americans 514 | publisher: The New Yorker 515 | url: https://www.newyorker.com/news/news-desk/the-pandemic-has-intensified-systemic-economic-racism-against-black-americans 516 | source: newspaper 517 | --- 518 | :: 519 | 520 | ::apa-reference 521 | --- 522 | authors: 523 | - Lee, C 524 | date: 2020, February 19 525 | title: A tale of two reference formats 526 | publisher: APA Style Blog 527 | url: https://apastyle.apa.org/blog/two-reference-formats 528 | source: blogs 529 | --- 530 | :: 531 | 532 | ::apa-reference 533 | --- 534 | authors: 535 | - Rowlatt, J 536 | date: 2020, October 19 537 | title: Could cold water hold a clue to a dementia cure? 538 | publisher: BBC News 539 | url: https://www.bbc.com/news/health-54531075 540 | source: online-news 541 | --- 542 | :: 543 | 544 | ::apa-reference 545 | --- 546 | organization: Scribbr 547 | date: n.d. 548 | title: How to cite a website in APA style 549 | url: https://www.scribbr.com/apa-examples/website/ 550 | source: websites 551 | --- 552 | :: 553 | 554 | ::apa-reference 555 | --- 556 | date: 2020, October 19 557 | title: "The countdown: A prophecy, crowds and a TikTok takedown" 558 | publisher: BBC News 559 | url: https://www.bbc.com/news/election-us-2020-54596667 560 | source: online-news 561 | --- 562 | :: 563 | 564 | -------------------------------------------------------------------------------- /error.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 43 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "homePage": { 3 | "landing": { 4 | "new": "new", 5 | "description_html": "Welcome to the ultimate `.md` template enamored by the green technologies.", 6 | "view_guide": "View Guide", 7 | "clone": "Clone me on GitHub" 8 | }, 9 | "features": { 10 | "markdown": { 11 | "title": "Markdown oriented", 12 | "description": "Articulate, write and publish compositions that matters. Rinse and repeat." 13 | }, 14 | "tailwind": { 15 | "title": "Tailwind included", 16 | "description": "Low level utility-rich styling library at your disposal without naming elements." 17 | }, 18 | "dark_mode": { 19 | "title": "Dark mode", 20 | "description": "Enrich user experience with dark and light mode whichever that suits their appetite." 21 | } 22 | }, 23 | "sitemap": { 24 | "sitemap": "Sitemap", 25 | "heading": "Full List of Articles, on this Website.", 26 | "description_html": "This section lists down every single Markdown entry in the contents folder." 27 | } 28 | }, 29 | "header": { 30 | "home": "Home", 31 | "guide": "Guide", 32 | "demo": "Demo", 33 | "resources": "Resources", 34 | "blogs": "Blogs", 35 | "buttons": { 36 | "change_theme": "Change theme", 37 | "change_language": "Change language", 38 | "github_repo": "Link to GitHub" 39 | } 40 | }, 41 | "footer": { 42 | "description": "The ultimate `.md` template powered by green technologies. ", 43 | "sitemap": { 44 | "title": "Sitemap", 45 | "homepage": "Homepage", 46 | "guide": "@:header.guide", 47 | "demo": "@:header.demo", 48 | "resources": "@:header.resources", 49 | "blogs": "@:header.blogs", 50 | "credits": "Credits" 51 | }, 52 | "projects": { 53 | "title": "Projects", 54 | "titles": { 55 | "notes": "My technical blog", 56 | "blog": "My personal blog", 57 | "portfolio": "My personal website", 58 | "sitemap_prettier": "A sitemap stylesheet collection", 59 | "linker": "A suite of tools to manage links", 60 | "sway": "Educational e-commerce implementation", 61 | "hacker_times": "Custom interface for Hackernews", 62 | "keylogger": "A key visualizer for Windows", 63 | "image_squared": "A tool to make images squared", 64 | "url_shortener": "A simple URL shortener" 65 | } 66 | }, 67 | "templates": { 68 | "title": "Templates", 69 | "react_esbuild": "React Esbuild Template", 70 | "react_rescript": "React Rescript Template", 71 | "algorand_react": "Algorand React Template" 72 | }, 73 | "technologies": { 74 | "title": "Technologies" 75 | } 76 | }, 77 | "error": { 78 | "title": "404 Not Found", 79 | "description": "The content does not exist. Please try another one. " 80 | }, 81 | "errorPage": { 82 | "message": "Page not found", 83 | "action": "Go back home" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "homePage": { 3 | "landing": { 4 | "new": "nouvelle", 5 | "description_html": "Bienvenue dans le modèle ultime `.md` amoureux des technologies vertes.", 6 | "view_guide": "Voir le guide", 7 | "clone": "Clonez-moi sur GitHub" 8 | }, 9 | "features": { 10 | "markdown": { 11 | "title": "Orienté Markdown", 12 | "description": "Articulez, écrivez et publiez des compositions qui comptent. Rincer et répéter." 13 | }, 14 | "tailwind": { 15 | "title": "Tailwind inclus", 16 | "description": "Bibliothèque de style riche en utilitaires de bas niveau à votre disposition sans nommer les éléments." 17 | }, 18 | "dark_mode": { 19 | "title": "Mode sombre", 20 | "description": "Enrichissez l'expérience utilisateur avec le mode sombre et clair selon ce qui convient à leur appétit." 21 | } 22 | }, 23 | "sitemap": { 24 | "sitemap": "Plan du site", 25 | "heading": "Liste complète des articles, sur ce site Web.", 26 | "description_html": "Cette section répertorie chaque entrée Markdown dans le dossier contents." 27 | } 28 | }, 29 | "header": { 30 | "home": "Maison", 31 | "guide": "Guide", 32 | "demo": "Démo", 33 | "resources": "Ressources", 34 | "blogs": "Blogs", 35 | "buttons": { 36 | "change_theme": "Changer de thème", 37 | "change_language": "Changer la langue", 38 | "github_repo": "Lien vers GitHub" 39 | } 40 | }, 41 | "footer": { 42 | "description": "Le modèle `.md` ultime alimenté par des technologies vertes.", 43 | "sitemap": { 44 | "title": "Plan du site", 45 | "homepage": "Page d'accueil", 46 | "guide": "@:header.guide", 47 | "demo": "@:header.demo", 48 | "resources": "@:header.resources", 49 | "blogs": "@:header.blogs", 50 | "credits": "Crédits" 51 | }, 52 | "projects": { 53 | "title": "Les projets", 54 | "titles": { 55 | "notes": "Mon blog technique", 56 | "blog": "Mon blog personnel", 57 | "portfolio": "Mon site personnel", 58 | "sitemap_prettier": "Une collection de feuilles de style pour les sitemaps", 59 | "linker": "Une suite d'outils pour gérer les liens", 60 | "sway": "Implémentation éducative de commerce électronique", 61 | "hacker_times": "Interface personnalisée pour Hackernews", 62 | "keylogger": "Un visualiseur de touches pour Windows", 63 | "image_squared": "Un outil pour rendre les images carrées", 64 | "url_shortener": "Un raccourcisseur d'URL simple" 65 | } 66 | }, 67 | "templates": { 68 | "title": "Modèles", 69 | "react_esbuild": "Modèle React Esbuild", 70 | "react_rescript": "Modèle React Rescript", 71 | "algorand_react": "Modèle Algorand React" 72 | }, 73 | "technologies": { 74 | "title": "Les technologies" 75 | } 76 | }, 77 | "error": { 78 | "title": "404 Non trouvé", 79 | "description": "Le contenu n'existe pas. Veuillez en essayer un autre." 80 | }, 81 | "errorPage": { 82 | "message": "Page non trouvée", 83 | "action": "Rentrer à la maison" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /logs/3.8.1-generate-error.txt: -------------------------------------------------------------------------------- 1 | Nuxt 3.8.1 with Nitro 2.8.1 6:24:19 pm 2 | ℹ Using Nitro server preset: static 6:24:19 pm 3 | ℹ Using Tailwind CSS from ~/assets/css/styles.scss nuxt:tailwindcss 6:24:21 pm 4 | 5 | WARN Using inside app.vue will cause unwanted layout shifting in your application.Consider removing from app.vue and using it in your pages. @nuxt/content 6:24:22 pm 6 | 7 | ℹ Building client... 6:24:23 pm 8 | ℹ vite v4.5.2 building for production... 6:24:23 pm 9 | ℹ ✓ 354 modules transformed. 6:24:26 pm 10 | Inspect report generated at D:/Workspace/Templates/nuxt-content-template/.nuxt/analyze/.vite-inspect 11 | ℹ .nuxt/dist/client/manifest.json 25.13 kB │ gzip: 1.75 kB 6:24:27 pm 12 | ℹ .nuxt/dist/client/_nuxt/ProsePre.e63e49c6.css 0.05 kB │ gzip: 0.07 kB 6:24:27 pm 13 | ℹ .nuxt/dist/client/_nuxt/_...slug_.6e839d22.css 0.38 kB │ gzip: 0.18 kB 6:24:27 pm 14 | ℹ .nuxt/dist/client/_nuxt/Landing.b1d935a1.css 0.77 kB │ gzip: 0.36 kB 6:24:27 pm 15 | ℹ .nuxt/dist/client/_nuxt/default.7755e198.css 2.69 kB │ gzip: 0.81 kB 6:24:27 pm 16 | ℹ .nuxt/dist/client/_nuxt/ProseCode.0212109b.js 0.10 kB │ gzip: 0.11 kB 6:24:27 pm 17 | ℹ .nuxt/dist/client/_nuxt/ContentRendererMarkdown.33317df3.js 0.11 kB │ gzip: 0.12 kB 6:24:27 pm 18 | ℹ .nuxt/dist/client/_nuxt/ProseHr.a2264c89.js 0.15 kB │ gzip: 0.14 kB 6:24:27 pm 19 | ℹ .nuxt/dist/client/_nuxt/DocumentDrivenNotFound.a3afbb64.js 0.16 kB │ gzip: 0.15 kB 6:24:27 pm 20 | ℹ .nuxt/dist/client/_nuxt/ProseTr.6903a2e3.js 0.18 kB │ gzip: 0.16 kB 6:24:27 pm 21 | ℹ .nuxt/dist/client/_nuxt/ProseP.2c1268a1.js 0.19 kB │ gzip: 0.16 kB 6:24:27 pm 22 | ℹ .nuxt/dist/client/_nuxt/ProseTh.69540786.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 23 | ℹ .nuxt/dist/client/_nuxt/ProseTd.bf38d7ba.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 24 | ℹ .nuxt/dist/client/_nuxt/ProseUl.badbe6a3.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 25 | ℹ .nuxt/dist/client/_nuxt/ProseOl.556aebe5.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 26 | ℹ .nuxt/dist/client/_nuxt/ProseLi.be4a0f2b.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 27 | ℹ .nuxt/dist/client/_nuxt/ProseEm.3aba9784.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 28 | ℹ .nuxt/dist/client/_nuxt/ProseCodeInline.0b7ead86.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 29 | ℹ .nuxt/dist/client/_nuxt/ProseTbody.ae8f1dbf.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 30 | ℹ .nuxt/dist/client/_nuxt/ProseTable.7d12e618.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 31 | ℹ .nuxt/dist/client/_nuxt/ProseThead.c00732b0.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 32 | ℹ .nuxt/dist/client/_nuxt/ProseBlockquote.8604ccf0.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 33 | ℹ .nuxt/dist/client/_nuxt/ProseStrong.c5929c7d.js 0.19 kB │ gzip: 0.17 kB 6:24:27 pm 34 | ℹ .nuxt/dist/client/_nuxt/DocumentDrivenEmpty.a40eeb22.js 0.29 kB │ gzip: 0.24 kB 6:24:27 pm 35 | ℹ .nuxt/dist/client/_nuxt/Landing.087c7dda.js 0.30 kB │ gzip: 0.24 kB 6:24:27 pm 36 | ℹ .nuxt/dist/client/_nuxt/Markdown.82d6d3a0.js 0.33 kB │ gzip: 0.26 kB 6:24:27 pm 37 | ℹ .nuxt/dist/client/_nuxt/ProseA.c955e08e.js 0.36 kB │ gzip: 0.26 kB 6:24:27 pm 38 | ℹ .nuxt/dist/client/_nuxt/ProseH1.9feeb826.js 0.44 kB │ gzip: 0.32 kB 6:24:27 pm 39 | ℹ .nuxt/dist/client/_nuxt/ProseH5.247fb783.js 0.45 kB │ gzip: 0.32 kB 6:24:27 pm 40 | ℹ .nuxt/dist/client/_nuxt/ProseH4.f0cd7ea0.js 0.45 kB │ gzip: 0.32 kB 6:24:27 pm 41 | ℹ .nuxt/dist/client/_nuxt/ProseH6.cfcc991f.js 0.45 kB │ gzip: 0.32 kB 6:24:27 pm 42 | ℹ .nuxt/dist/client/_nuxt/Card.fedb4eb2.js 0.54 kB │ gzip: 0.34 kB 6:24:27 pm 43 | ℹ .nuxt/dist/client/_nuxt/ProseImg.9c1d1cee.js 0.62 kB │ gzip: 0.37 kB 6:24:27 pm 44 | ℹ .nuxt/dist/client/_nuxt/ProseH2.084d8df2.js 0.63 kB │ gzip: 0.44 kB 6:24:27 pm 45 | ℹ .nuxt/dist/client/_nuxt/ProseH3.125d1a15.js 0.63 kB │ gzip: 0.44 kB 6:24:27 pm 46 | ℹ .nuxt/dist/client/_nuxt/ProsePre.fe086fa8.js 0.68 kB │ gzip: 0.36 kB 6:24:27 pm 47 | ℹ .nuxt/dist/client/_nuxt/ContentNavigation.d6372af9.js 0.86 kB │ gzip: 0.51 kB 6:24:27 pm 48 | ℹ .nuxt/dist/client/_nuxt/ContentList.7b721388.js 0.87 kB │ gzip: 0.48 kB 6:24:27 pm 49 | ℹ .nuxt/dist/client/_nuxt/ContentRenderer.e80e0192.js 1.21 kB │ gzip: 0.61 kB 6:24:27 pm 50 | ℹ .nuxt/dist/client/_nuxt/ContentSlot.6156ef30.js 1.86 kB │ gzip: 0.88 kB 6:24:27 pm 51 | ℹ .nuxt/dist/client/_nuxt/client-db.31f81804.js 1.91 kB │ gzip: 0.98 kB 6:24:27 pm 52 | ℹ .nuxt/dist/client/_nuxt/ContentDoc.bfc9321a.js 2.01 kB │ gzip: 0.94 kB 6:24:27 pm 53 | ℹ .nuxt/dist/client/_nuxt/client-db.5a4b2d83.js 2.10 kB │ gzip: 1.05 kB 6:24:27 pm 54 | ℹ .nuxt/dist/client/_nuxt/ContentQuery.9cfd2e0a.js 2.46 kB │ gzip: 1.02 kB 6:24:27 pm 55 | ℹ .nuxt/dist/client/_nuxt/asyncData.d4a3aa2d.js 2.60 kB │ gzip: 1.12 kB 6:24:27 pm 56 | ℹ .nuxt/dist/client/_nuxt/en.3ac83c28.js 2.79 kB │ gzip: 0.85 kB 6:24:27 pm 57 | ℹ .nuxt/dist/client/_nuxt/fr.2e6ad269.js 2.87 kB │ gzip: 0.94 kB 6:24:27 pm 58 | ℹ .nuxt/dist/client/_nuxt/_...slug_.9dc53ac0.js 4.31 kB │ gzip: 1.91 kB 6:24:27 pm 59 | ℹ .nuxt/dist/client/_nuxt/ProseCode.vue.f79e9970.js 4.40 kB │ gzip: 2.17 kB 6:24:27 pm 60 | ℹ .nuxt/dist/client/_nuxt/index.0937e233.js 5.29 kB │ gzip: 2.15 kB 6:24:27 pm 61 | ℹ .nuxt/dist/client/_nuxt/pipeline.0e6c0421.js 18.48 kB │ gzip: 7.71 kB 6:24:27 pm 62 | ℹ .nuxt/dist/client/_nuxt/default.1fb0ad15.js 18.51 kB │ gzip: 6.24 kB 6:24:27 pm 63 | ℹ .nuxt/dist/client/_nuxt/ContentRendererMarkdown.vue.e7626398.js 23.29 kB │ gzip: 8.26 kB 6:24:27 pm 64 | ℹ .nuxt/dist/client/_nuxt/entry.9af7032e.js 248.21 kB │ gzip: 89.42 kB 6:24:27 pm 65 | ℹ ✓ built in 3.70s 6:24:27 pm 66 | ✔ Client built in 3715ms 6:24:27 pm 67 | ℹ Building server... 6:24:27 pm 68 | ℹ vite v4.5.2 building SSR bundle for production... 6:24:27 pm 69 | ℹ ✓ 253 modules transformed. 6:24:29 pm 70 | Inspect report generated at D:/Workspace/Templates/nuxt-content-template/.nuxt/analyze/.vite-inspect 71 | ℹ .nuxt/dist/server/_nuxt/entry-styles.5c601762.mjs 0.08 kB 6:24:30 pm 72 | ℹ .nuxt/dist/server/_nuxt/ProseCode-styles.93005832.mjs 0.08 kB 6:24:30 pm 73 | ℹ .nuxt/dist/server/_nuxt/_...slug_-styles.839964f7.mjs 0.08 kB 6:24:30 pm 74 | ℹ .nuxt/dist/server/_nuxt/ProsePre-styles.9d8d359a.mjs 0.15 kB 6:24:30 pm 75 | ℹ .nuxt/dist/server/_nuxt/default-styles.c42bfaec.mjs 0.22 kB 6:24:30 pm 76 | ℹ .nuxt/dist/server/styles.mjs 0.75 kB 6:24:30 pm 77 | ℹ .nuxt/dist/server/_nuxt/ProsePre-styles-1.mjs-2476b8a8.js 0.22 kB │ map: 0.12 kB 6:24:30 pm 78 | ℹ .nuxt/dist/server/_nuxt/ProseCode-styles-1.mjs-ef625421.js 0.23 kB │ map: 0.12 kB 6:24:30 pm 79 | ℹ .nuxt/dist/server/_nuxt/_...slug_-styles-1.mjs-45ba723b.js 0.57 kB │ map: 0.12 kB 6:24:30 pm 80 | ℹ .nuxt/dist/server/_nuxt/DocumentDrivenNotFound-df9d55f8.js 0.67 kB │ map: 0.60 kB 6:24:30 pm 81 | ℹ .nuxt/dist/server/_nuxt/default-styles-2.mjs-c3a57979.js 0.69 kB │ map: 0.11 kB 6:24:30 pm 82 | ℹ .nuxt/dist/server/_nuxt/default-styles-1.mjs-91c4490c.js 0.81 kB │ map: 0.11 kB 6:24:30 pm 83 | ℹ .nuxt/dist/server/_nuxt/DocumentDrivenEmpty-2a708a54.js 0.85 kB │ map: 0.99 kB 6:24:30 pm 84 | ℹ .nuxt/dist/server/_nuxt/Landing-a8f52e2a.js 1.02 kB │ map: 1.29 kB 6:24:30 pm 85 | ℹ .nuxt/dist/server/_nuxt/Markdown-f08a5fe5.js 1.04 kB │ map: 0.13 kB 6:24:30 pm 86 | ℹ .nuxt/dist/server/_nuxt/ProseHr-b38a2f0c.js 1.16 kB │ map: 0.35 kB 6:24:30 pm 87 | ℹ .nuxt/dist/server/_nuxt/Card-df4572ac.js 1.23 kB │ map: 0.12 kB 6:24:30 pm 88 | ℹ .nuxt/dist/server/_nuxt/ProseP-ebfd7041.js 1.25 kB │ map: 0.48 kB 6:24:30 pm 89 | ℹ .nuxt/dist/server/_nuxt/ProseTd-dcac05bd.js 1.26 kB │ map: 0.49 kB 6:24:30 pm 90 | ℹ .nuxt/dist/server/_nuxt/ProseLi-ead71b75.js 1.26 kB │ map: 0.48 kB 6:24:30 pm 91 | ℹ .nuxt/dist/server/_nuxt/ProseUl-f53b0d25.js 1.26 kB │ map: 0.49 kB 6:24:30 pm 92 | ℹ .nuxt/dist/server/_nuxt/ProseTr-504e7dd9.js 1.26 kB │ map: 0.49 kB 6:24:30 pm 93 | ℹ .nuxt/dist/server/_nuxt/ProseOl-9f9bd375.js 1.26 kB │ map: 0.49 kB 6:24:30 pm 94 | ℹ .nuxt/dist/server/_nuxt/ProseTh-12469609.js 1.26 kB │ map: 0.49 kB 6:24:30 pm 95 | ℹ .nuxt/dist/server/_nuxt/ProseEm-e69a562d.js 1.26 kB │ map: 0.49 kB 6:24:30 pm 96 | ℹ .nuxt/dist/server/_nuxt/ProseThead-9538ab16.js 1.28 kB │ map: 0.50 kB 6:24:30 pm 97 | ℹ .nuxt/dist/server/_nuxt/ProseTbody-da1b77fc.js 1.28 kB │ map: 0.50 kB 6:24:30 pm 98 | ℹ .nuxt/dist/server/_nuxt/ProseTable-eb30ded9.js 1.28 kB │ map: 0.50 kB 6:24:30 pm 99 | ℹ .nuxt/dist/server/_nuxt/ProseStrong-a4c7e101.js 1.28 kB │ map: 0.51 kB 6:24:30 pm 100 | ℹ .nuxt/dist/server/_nuxt/ProseBlockquote-fd32c62b.js 1.31 kB │ map: 0.52 kB 6:24:30 pm 101 | ℹ .nuxt/dist/server/_nuxt/ProseCodeInline-d8074b11.js 1.31 kB │ map: 0.52 kB 6:24:30 pm 102 | ℹ .nuxt/dist/server/_nuxt/island-renderer-10a535c2.js 1.37 kB │ map: 1.87 kB 6:24:30 pm 103 | ℹ .nuxt/dist/server/_nuxt/default-styles-3.mjs-c6d95b22.js 1.80 kB │ map: 0.11 kB 6:24:30 pm 104 | ℹ .nuxt/dist/server/_nuxt/ProseH1-779a17d2.js 1.87 kB │ map: 0.85 kB 6:24:30 pm 105 | ℹ .nuxt/dist/server/_nuxt/ProseH4-ce466927.js 1.88 kB │ map: 0.86 kB 6:24:30 pm 106 | ℹ .nuxt/dist/server/_nuxt/ProseH6-d277c447.js 1.88 kB │ map: 0.86 kB 6:24:30 pm 107 | ℹ .nuxt/dist/server/_nuxt/ProseH5-fa4ee0db.js 1.88 kB │ map: 0.86 kB 6:24:30 pm 108 | ℹ .nuxt/dist/server/_nuxt/ProseA-d390ca00.js 1.89 kB │ map: 0.16 kB 6:24:30 pm 109 | ℹ .nuxt/dist/server/_nuxt/ProseH2-9b258021.js 1.95 kB │ map: 0.98 kB 6:24:30 pm 110 | ℹ .nuxt/dist/server/_nuxt/ProseH3-a30baf9d.js 1.95 kB │ map: 0.98 kB 6:24:30 pm 111 | ℹ .nuxt/dist/server/_nuxt/ProseImg-d0414787.js 2.13 kB │ map: 1.50 kB 6:24:30 pm 112 | ℹ .nuxt/dist/server/_nuxt/ProsePre-b1d95840.js 2.20 kB │ map: 0.17 kB 6:24:30 pm 113 | ℹ .nuxt/dist/server/_nuxt/ProseCode-d0319663.js 2.62 kB │ map: 2.68 kB 6:24:30 pm 114 | ℹ .nuxt/dist/server/_nuxt/ContentList-45ff97a8.js 2.83 kB │ map: 2.99 kB 6:24:30 pm 115 | ℹ .nuxt/dist/server/_nuxt/ContentNavigation-4e9398c4.js 2.84 kB │ map: 3.42 kB 6:24:30 pm 116 | ℹ .nuxt/dist/server/_nuxt/OgImageBasic.island-e24bc524.js 3.01 kB │ map: 3.04 kB 6:24:30 pm 117 | ℹ .nuxt/dist/server/_nuxt/ContentRenderer-4d799447.js 3.27 kB │ map: 3.55 kB 6:24:30 pm 118 | ℹ .nuxt/dist/server/_nuxt/asyncData-57a4c648.js 4.15 kB │ map: 10.33 kB 6:24:30 pm 119 | ℹ .nuxt/dist/server/_nuxt/client-db-34111973.js 4.70 kB │ map: 7.14 kB 6:24:30 pm 120 | ℹ .nuxt/dist/server/_nuxt/ContentSlot-b2d47a38.js 4.78 kB │ map: 7.94 kB 6:24:30 pm 121 | ℹ .nuxt/dist/server/_nuxt/client-db-fec8da3f.js 5.18 kB │ map: 8.21 kB 6:24:30 pm 122 | ℹ .nuxt/dist/server/_nuxt/en-d484eaa4.js 5.33 kB │ map: 7.17 kB 6:24:30 pm 123 | ℹ .nuxt/dist/server/_nuxt/fr-4afea3ee.js 5.41 kB │ map: 7.33 kB 6:24:30 pm 124 | ℹ .nuxt/dist/server/_nuxt/ContentQuery-1f38045c.js 6.36 kB │ map: 9.08 kB 6:24:30 pm 125 | ℹ .nuxt/dist/server/_nuxt/ContentDoc-fa7fd49d.js 7.65 kB │ map: 11.04 kB 6:24:30 pm 126 | ℹ .nuxt/dist/server/_nuxt/pipeline-c5c54ecd.js 10.52 kB │ map: 20.68 kB 6:24:30 pm 127 | ℹ .nuxt/dist/server/_nuxt/_...slug_-eabce24f.js 10.82 kB │ map: 5.05 kB 6:24:30 pm 128 | ℹ .nuxt/dist/server/_nuxt/index-8e1a702a.js 12.46 kB │ map: 9.83 kB 6:24:30 pm 129 | ℹ .nuxt/dist/server/_nuxt/ContentRendererMarkdown-4683471c.js 13.35 kB │ map: 21.06 kB 6:24:30 pm 130 | ℹ .nuxt/dist/server/_nuxt/default-6ad6a4d6.js 31.05 kB │ map: 26.56 kB 6:24:30 pm 131 | ℹ .nuxt/dist/server/_nuxt/entry-styles-1.mjs-285f94ee.js 39.44 kB │ map: 0.11 kB 6:24:30 pm 132 | ℹ .nuxt/dist/server/server.mjs 260.21 kB │ map: 607.07 kB 6:24:30 pm 133 | ℹ ✓ built in 3.09s 6:24:30 pm 134 | ✔ Server built in 3145ms 6:24:30 pm 135 | ℹ Initializing prerenderer nitro 6:24:30 pm 136 | 137 | ERROR The requested module 'file://D:/Workspace/Templates/nuxt-content-template/node_modules/.pnpm/h3@1.7.1/node_modules/h3/dist/index.mjs' does not provide an export named 'removeResponseHeader' 6:24:34 pm 138 | 139 | globalThis._importMeta_=globalThis._importMeta_||{url:"/_entry.js",env:process.env};import { defineEventHandler, handleCacheHeaders, splitCookiesString, isEvent, createEvent, getRequestHeader, eventHandler, setHeaders, sendRedirect, proxyRequest, setResponseStatus, setResponseHeader, send, getRequestHeaders, removeResponseHeader, createError, getResponseHeader, getQuery as getQuery$1, setHeader, getCookie, lazyEventHandler, createApp, createRouter as createRouter$1, toNodeListener, fetchWithEvent } from 'file://D:/Workspace/Templates/nuxt-content-template/node_modules/.pnpm/h3@1.7.1/node_modules/h3/dist/index.mjs'; 140 | ^^^^^^^^^^^^^^^^^^^^ 141 | SyntaxError: The requested module 'D:/Workspace/Templates/nuxt-content-template/node_modules/.pnpm/h3@1.7.1/node_modules/h3/dist/index.mjs' does not provide an export named 'removeResponseHeader' 142 | at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21) 143 | at async ModuleJob.run (node:internal/modules/esm/module_job:190:5) 144 | 145 | 146 | 147 | ERROR The requested module 'file://D:/Workspace/Templates/nuxt-content-template/node_modules/.pnpm/h3@1.7.1/node_modules/h3/dist/index.mjs' does not provide an export named 'removeResponseHeader' -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://v3.nuxtjs.org/api/configuration/nuxt.config 2 | export default defineNuxtConfig({ 3 | app: { 4 | head: { 5 | charset: "utf-8", 6 | viewport: "width=device-width, initial-scale=1.0", 7 | title: "Nuxt Content Template", 8 | meta: [ 9 | { 10 | name: "description", 11 | content: 12 | "A hand-crafted feature-rich document driven template powered by Nuxt and Nuxt Content.", 13 | }, 14 | ], 15 | link: [ 16 | { 17 | rel: "icon", 18 | type: "image/svg+xml", 19 | href: "/nuxt.svg", 20 | }, 21 | ], 22 | htmlAttrs: { 23 | lang: "en", 24 | }, 25 | bodyAttrs: { 26 | class: "dark:bg-slate-800 dark:text-gray-50 text-gray-800", 27 | }, 28 | }, 29 | }, 30 | modules: [ 31 | "@nuxt/content", 32 | "@nuxtjs/tailwindcss", 33 | "@nuxtjs/color-mode", 34 | "@nuxtjs/i18n", 35 | "@nuxt/image", 36 | ], 37 | extends: ["nuxt-seo-kit"], 38 | tailwindcss: { 39 | configPath: "./tailwind.config.js", 40 | cssPath: "./assets/css/styles.scss", 41 | viewer: false, 42 | }, 43 | content: { 44 | documentDriven: true, 45 | highlight: { 46 | theme: { 47 | default: "github-light", 48 | dark: "github-dark", 49 | }, 50 | preload: ["cpp", "csharp", "rust", "wenyan", "yaml", "latex"], 51 | }, 52 | markdown: { 53 | remarkPlugins: ["remark-math"], 54 | rehypePlugins: ["rehype-mathjax"], 55 | }, 56 | }, 57 | colorMode: { classSuffix: "" }, 58 | i18n: { 59 | locales: [ 60 | { 61 | code: "en", 62 | file: "en.json", 63 | }, 64 | { 65 | code: "fr", 66 | file: "fr.json", 67 | }, 68 | ], 69 | vueI18n: { 70 | legacy: false, 71 | locale: "en", 72 | messages: { 73 | en: { 74 | welcome: "Welcome", 75 | }, 76 | fr: { 77 | welcome: "Bienvenue", 78 | }, 79 | }, 80 | }, 81 | langDir: "locales", 82 | lazy: true, 83 | defaultLocale: "en", 84 | }, 85 | runtimeConfig: { 86 | public: { 87 | siteUrl: process.env.NUXT_PUBLIC_SITE_URL || "https://example.com", 88 | siteName: "Nuxt Content Template", 89 | siteDescription: 90 | "A Nuxt3 template built specifically for documentations and blogs.", 91 | language: "en", 92 | }, 93 | }, 94 | image: { 95 | provider: "netlify", // Use your own provider! 96 | }, 97 | }); 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-content-template", 3 | "description": "An opinionated template built for documentations and static websites.", 4 | "author": "Shaun Chong ", 5 | "license": "MIT", 6 | "private": false, 7 | "version": "1.2.3", 8 | "scripts": { 9 | "build": "nuxt build", 10 | "dev": "nuxt dev", 11 | "generate": "nuxt generate", 12 | "preview": "nuxt preview", 13 | "clean": "nuxi cleanup" 14 | }, 15 | "devDependencies": { 16 | "@nuxt/content": "^2.7.0", 17 | "@nuxt/image": "1.0.0-rc.1", 18 | "@nuxtjs/color-mode": "^3.3.0", 19 | "@nuxtjs/i18n": "8.0.0-beta.10", 20 | "@nuxtjs/tailwindcss": "^6.9.4", 21 | "@tailwindcss/typography": "^0.5.10", 22 | "nuxt": "^3.6.1", 23 | "nuxt-seo-kit": "^1.3.9", 24 | "rehype-mathjax": "^4.0.2", 25 | "remark-math": "^5.1.1", 26 | "sass": "^1.63.6", 27 | "sass-loader": "^13.3.2", 28 | "@vueuse/core": "^10.7.2", 29 | "@vueuse/motion": "^2.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pages/[...slug].vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 122 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 113 | 114 | 134 | -------------------------------------------------------------------------------- /public/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-miner00/nuxt-content-template/25c384174298dff24113dd1ff943e00c5e49f3a6/public/images/demo.png -------------------------------------------------------------------------------- /public/images/demo_landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-miner00/nuxt-content-template/25c384174298dff24113dd1ff943e00c5e49f3a6/public/images/demo_landscape.png -------------------------------------------------------------------------------- /public/images/flag-france.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/flag-united-states.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/nuxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./content/**/*.md", 5 | "./components/**/*.vue", 6 | "./layouts/**/*.vue", 7 | "./pages/**/*.vue", 8 | "./plugins/**/*.{js,ts}", 9 | "./nuxt.config.{js,ts}", 10 | "./locales/**/*.json", 11 | ], 12 | theme: { 13 | extend: {}, 14 | }, 15 | plugins: [require("@tailwindcss/typography")], 16 | darkMode: "class", 17 | }; 18 | -------------------------------------------------------------------------------- /templates/frontmatter.yml: -------------------------------------------------------------------------------- 1 | title: Title of the Article 2 | description: The subtitle describing the title in more details, should be written in sentence-case 3 | topic: Topic 4 | category: Category 5 | authors: 6 | - name: Contributor 1 7 | avatar: contrib_1.png 8 | - name: Contributor 2 9 | avatar: contrib_2.png 10 | tags: 11 | - tutorial 12 | - guide 13 | - cheatsheet 14 | updatedAt: 2022-11-18T11:37:49.432Z 15 | createdAt: 2022-11-18T11:37:49.432Z 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://v3.nuxtjs.org/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [files] 2 | extend-exclude = ["locales/fr.json", "content/fr/**/*.md"] 3 | --------------------------------------------------------------------------------