57 |
58 |
59 |
60 |
61 |
62 | ## About The Project
63 |
64 | 
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 |
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 |
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 |
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 |
20 |
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 |
89 |
{{ data?.title }}
90 |
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 | 
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 | 
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 | 
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 |
169 |
170 |
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 |
401 |
{{ $t("welcome") }}
402 |
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 |
481 | Avant i18n
482 |
483 | Après i18n
484 |
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 |
87 |
{{ data?.title }}
88 |
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 | 
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 | 
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 | 
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 |
167 |
168 |
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 |
399 |
{{ $t("welcome") }}
400 |
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 |
479 | Before i18n
480 |
481 | After i18n
482 |
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 |
11 |
12 |