diff --git a/content/components.md b/content/components.md index d835ac3d15..52cac155a2 100644 --- a/content/components.md +++ b/content/components.md @@ -2,7 +2,7 @@ Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on. The main idea of a provider is that it can be **injected** as a dependency; this means objects can create various relationships with each other, and the function of "wiring up" these objects can largely be delegated to the Nest runtime system. -
+
In the previous chapter, we built a simple `CatsController`. Controllers should handle HTTP requests and delegate more complex tasks to **providers**. Providers are plain JavaScript classes that are declared as `providers` in a [module](/modules). diff --git a/content/controllers.md b/content/controllers.md index e791f60b5e..34818fb953 100644 --- a/content/controllers.md +++ b/content/controllers.md @@ -2,7 +2,7 @@ Controllers are responsible for handling incoming **requests** and returning **responses** to the client. -
+
A controller's purpose is to receive specific requests for the application. The **routing** mechanism controls which controller receives which requests. Frequently, each controller has more than one route, and different routes can perform different actions. diff --git a/content/exception-filters.md b/content/exception-filters.md index 7f40d69e6d..28c3b9f72c 100644 --- a/content/exception-filters.md +++ b/content/exception-filters.md @@ -3,7 +3,7 @@ Nest comes with a built-in **exceptions layer** which is responsible for processing all unhandled exceptions across an application. When an exception is not handled by your application code, it is caught by this layer, which then automatically sends an appropriate user-friendly response.
- +
Out of the box, this action is performed by a built-in **global exception filter**, which handles exceptions of type `HttpException` (and subclasses of it). When an exception is **unrecognized** (is neither `HttpException` nor a class that inherits from `HttpException`), the built-in exception filter generates the following default JSON response: diff --git a/content/first-steps.md b/content/first-steps.md index 098a7eec7e..23e7012f07 100644 --- a/content/first-steps.md +++ b/content/first-steps.md @@ -4,7 +4,7 @@ In this set of articles, you'll learn the **core fundamentals** of Nest. To get #### Language -We're in love with [TypeScript](https://www.typescriptlang.org/), but above all - we love [Node.js](https://nodejs.org/en/). That's why Nest is compatible with both TypeScript and **pure JavaScript**. Nest takes advantage of the latest language features, so to use it with vanilla JavaScript we need a [Babel](https://babeljs.io/) compiler. +We're in love with [TypeScript](https://www.typescriptlang.org/), but above all - we love [Node.js](https://nodejs.org/en/). That's why Nest is compatible with both TypeScript and pure JavaScript. Nest takes advantage of the latest language features, so to use it with vanilla JavaScript we need a [Babel](https://babeljs.io/) compiler. We'll mostly use TypeScript in the examples we provide, but you can always **switch the code snippets** to vanilla JavaScript syntax (simply click to toggle the language button in the upper right hand corner of each snippet). diff --git a/content/fundamentals/lifecycle-events.md b/content/fundamentals/lifecycle-events.md index 8f87b07a63..e5a7928082 100644 --- a/content/fundamentals/lifecycle-events.md +++ b/content/fundamentals/lifecycle-events.md @@ -6,7 +6,7 @@ A Nest application, as well as every application element, has a lifecycle manage The following diagram depicts the sequence of key application lifecycle events, from the time the application is bootstrapped until the node process exits. We can divide the overall lifecycle into three phases: **initializing**, **running** and **terminating**. Using this lifecycle, you can plan for appropriate initialization of modules and services, manage active connections, and gracefully shutdown your application when it receives a termination signal. -
+
#### Lifecycle events diff --git a/content/guards.md b/content/guards.md index afdf194ac4..e040d661d3 100644 --- a/content/guards.md +++ b/content/guards.md @@ -2,7 +2,7 @@ A guard is a class annotated with the `@Injectable()` decorator, which implements the `CanActivate` interface. -
+
Guards have a **single responsibility**. They determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time. This is often referred to as **authorization**. Authorization (and its cousin, **authentication**, with which it usually collaborates) has typically been handled by [middleware](/middleware) in traditional Express applications. Middleware is a fine choice for authentication, since things like token validation and attaching properties to the `request` object are not strongly connected with a particular route context (and its metadata). diff --git a/content/interceptors.md b/content/interceptors.md index 132eca8b41..8c9d31a185 100644 --- a/content/interceptors.md +++ b/content/interceptors.md @@ -2,7 +2,7 @@ An interceptor is a class annotated with the `@Injectable()` decorator and implements the `NestInterceptor` interface. -
+
Interceptors have a set of useful capabilities which are inspired by the [Aspect Oriented Programming](https://en.wikipedia.org/wiki/Aspect-oriented_programming) (AOP) technique. They make it possible to: diff --git a/content/microservices/basics.md b/content/microservices/basics.md index c4957fbaf1..1951cd09c9 100644 --- a/content/microservices/basics.md +++ b/content/microservices/basics.md @@ -4,7 +4,7 @@ In addition to traditional (sometimes called monolithic) application architectur In Nest, a microservice is fundamentally an application that uses a different **transport** layer than HTTP. -
+
Nest supports several built-in transport layer implementations, called **transporters**, which are responsible for transmitting messages between different microservice instances. Most transporters natively support both **request-response** and **event-based** message styles. Nest abstracts the implementation details of each transporter behind a canonical interface for both request-response and event-based messaging. This makes it easy to switch from one transport layer to another -- for example to leverage the specific reliability or performance features of a particular transport layer -- without impacting your application code. diff --git a/content/microservices/redis.md b/content/microservices/redis.md index 905b7b6740..3010b0a4ac 100644 --- a/content/microservices/redis.md +++ b/content/microservices/redis.md @@ -2,7 +2,7 @@ The [Redis](https://redis.io/) transporter implements the publish/subscribe messaging paradigm and leverages the [Pub/Sub](https://redis.io/topics/pubsub) feature of Redis. Published messages are categorized in channels, without knowing what subscribers (if any) will eventually receive the message. Each microservice can subscribe to any number of channels. In addition, more than one channel can be subscribed to at a time. Messages exchanged through channels are **fire-and-forget**, which means that if a message is published and there are no subscribers interested in it, the message is removed and cannot be recovered. Thus, you don't have a guarantee that either messages or events will be handled by at least one service. A single message can be subscribed to (and received) by multiple subscribers. -
+
#### Installation diff --git a/content/middlewares.md b/content/middlewares.md index f160e8c065..4a9ceaf174 100644 --- a/content/middlewares.md +++ b/content/middlewares.md @@ -2,7 +2,7 @@ Middleware is a function which is called **before** the route handler. Middleware functions have access to the [request](https://expressjs.com/en/4x/api.html#req) and [response](https://expressjs.com/en/4x/api.html#res) objects, and the `next()` middleware function in the application’s request-response cycle. The **next** middleware function is commonly denoted by a variable named `next`. -
+
Nest middleware are, by default, equivalent to [express](https://expressjs.com/en/guide/using-middleware.html) middleware. The following description from the official express documentation describes the capabilities of middleware: diff --git a/content/modules.md b/content/modules.md index e1061c22a2..e1f6ae1391 100644 --- a/content/modules.md +++ b/content/modules.md @@ -2,7 +2,7 @@ A module is a class annotated with a `@Module()` decorator. The `@Module()` decorator provides metadata that **Nest** makes use of to organize the application structure. -
+
Each application has at least one module, a **root module**. The root module is the starting point Nest uses to build the **application graph** - the internal data structure Nest uses to resolve module and provider relationships and dependencies. While very small applications may theoretically have just the root module, this is not the typical case. We want to emphasize that modules are **strongly** recommended as an effective way to organize your components. Thus, for most applications, the resulting architecture will employ multiple modules, each encapsulating a closely related set of **capabilities**. diff --git a/content/pipes.md b/content/pipes.md index 79537b645b..66f53942e1 100644 --- a/content/pipes.md +++ b/content/pipes.md @@ -3,7 +3,7 @@ A pipe is a class annotated with the `@Injectable()` decorator, which implements the `PipeTransform` interface.
- +
Pipes have two typical use cases: diff --git a/content/websockets/gateways.md b/content/websockets/gateways.md index cb0eb8e32a..dd055477ac 100644 --- a/content/websockets/gateways.md +++ b/content/websockets/gateways.md @@ -4,7 +4,7 @@ Most of the concepts discussed elsewhere in this documentation, such as dependen In Nest, a gateway is simply a class annotated with `@WebSocketGateway()` decorator. Technically, gateways are platform-agnostic which makes them compatible with any WebSockets library once an adapter is created. There are two WS platforms supported out-of-the-box: [socket.io](https://github.com/socketio/socket.io) and [ws](https://github.com/websockets/ws). You can choose the one that best suits your needs. Also, you can build your own adapter by following this [guide](/websockets/adapter). -
+
> info **Hint** Gateways can be treated as [providers](/providers); this means they can inject dependencies through the class constructor. Also, gateways can be injected by other classes (providers and controllers) as well. diff --git a/src/app/common/social-wrapper/social-wrapper.component.html b/src/app/common/social-wrapper/social-wrapper.component.html index f4191b3d8f..0496a91386 100644 --- a/src/app/common/social-wrapper/social-wrapper.component.html +++ b/src/app/common/social-wrapper/social-wrapper.component.html @@ -27,4 +27,5 @@ rel="nofollow"> + diff --git a/src/app/common/social-wrapper/social-wrapper.component.scss b/src/app/common/social-wrapper/social-wrapper.component.scss index d176330040..1267a9a1ad 100644 --- a/src/app/common/social-wrapper/social-wrapper.component.scss +++ b/src/app/common/social-wrapper/social-wrapper.component.scss @@ -1,4 +1,3 @@ -@import '../../../scss/variables.scss'; @import '../../../scss/utils.scss'; .social-wrapper { @@ -6,6 +5,7 @@ float: right; padding-right: 40px; position: relative; + display: flex; a { @extend .transition-fast; @@ -21,7 +21,7 @@ position: relative; &:hover { - fill: $red-color; + fill: var(--primary); } } @@ -29,7 +29,7 @@ font-size: 20px; } &:hover { - color: $red-color; + color: var(--primary); } } @include media(medium) { diff --git a/src/app/homepage/footer/footer.component.scss b/src/app/homepage/footer/footer.component.scss index 83292309a4..346a68f432 100644 --- a/src/app/homepage/footer/footer.component.scss +++ b/src/app/homepage/footer/footer.component.scss @@ -4,7 +4,7 @@ :host { @extend .transition; @extend .box-sizing; - background: variables.$black-color; + background: var(--header-background); display: block; padding: 40px 0; margin: 0 -85px; @@ -34,15 +34,13 @@ footer { text-align: center; a { - color: variables.$red-color; + color: var(--primary-5dp); font-weight: 600; @include utils.text-gradient(); &:hover { - color: variables.$red-color; background: #fff; - } } @@ -73,12 +71,13 @@ footer { font-size: 20px; margin-left: 15px; cursor: pointer; + color: #fff; &:first-of-type { font-size: 20px; } &:hover { - color: variables.$red-color; + color: var(--primary); } } diff --git a/src/app/homepage/header/header.component.scss b/src/app/homepage/header/header.component.scss index 4066f4f7c3..e9e874cbe8 100644 --- a/src/app/homepage/header/header.component.scss +++ b/src/app/homepage/header/header.component.scss @@ -2,10 +2,14 @@ @use '../../../scss/utils'; :host { + background: var(--header-background); + display: block; + width: 100%; + height: 70px; position: fixed; z-index: 10000; width: 100%; - height: 105px; + height: 70px; @media print { position: relative; @@ -46,7 +50,7 @@ header { display: block; position: relative; - background: variables.$black-color; + background: var(--header-background); width: 100%; height: 70px; z-index: 10000; @@ -129,7 +133,7 @@ header { padding: 14px 25px; @media (min-width: 1500px) { - margin-right: 80px; + margin-right: 30px; } @media (max-width: 1499px) { @@ -142,8 +146,12 @@ header { } } - @media (max-width: 1250px) { + @media (max-width: 1350px) { padding: 14px 5px; + + li:nth-of-type(3) { + display: none; + } } @media (max-width: 1150px) { @@ -213,7 +221,7 @@ header { font-weight: 600; &:hover { - color: variables.$red-color; + color: var(--primary-4dp); } } @media print { @@ -274,9 +282,9 @@ header { position: relative; &:hover { - color: variables.$red-color; + color: var(--primary); &::after { - background: variables.$red-color; + background: var(--primary); } } @@ -390,11 +398,11 @@ header { &:active, &:focus { - border-bottom-color: variables.$red-color; + border-bottom-color: var(--primary); width: 190px; } - @media (min-width: 1200px) and (max-width: 1300px) { + @media (max-width: 1300px) { width: 190px; } } @@ -459,7 +467,7 @@ header { &::after, &::before { content: ''; - background: variables.$red-color; + background: var(--primary); display: block; height: 2px; position: absolute; diff --git a/src/app/homepage/homepage.component.scss b/src/app/homepage/homepage.component.scss index 6225650a9b..fbfccb1346 100644 --- a/src/app/homepage/homepage.component.scss +++ b/src/app/homepage/homepage.component.scss @@ -55,25 +55,25 @@ } .page-wrapper { - color: variables.$grey-color; + color: var(--color); line-height: 26px; a { font-weight: 600; - color: variables.$red-color; - + color: var(--primary-3dp); &:hover { color: #0894e2; } } .content a { + transition: none; @include utils.text-gradient(); &:hover { background: #0894e2; + -webkit-background-clip: text !important; -webkit-text-fill-color: transparent; - -webkit-background-clip: text; } } @@ -108,7 +108,7 @@ h4, h5, h6 { - color: variables.$black-color; + color: var(--color-1dp); } h3 { font-size: 24px; @@ -142,7 +142,7 @@ } } h5 { - color: variables.$grey-color; + color: var(--color); margin-top: 5px; font-weight: 600; font-size: 16px; @@ -159,7 +159,7 @@ } .sponsors-wrapper { - border-top: 1px solid #e8e8e8; + border-top: 1px solid var(--background-2dp); padding: 10px 0 60px; margin-top: 20px; position: relative; @@ -170,7 +170,7 @@ } a { - color: variables.$red-color; + color: var(--primary); font-weight: 600; &:hover { color: #0894e2; @@ -180,11 +180,10 @@ h3 { font-size: 24px; font-weight: 700; - color: #1d1d1d; } h4 { - color: #1b2247; + color: var(--color-1dp); font-weight: 600; margin: 0; font-size: 18px; @@ -200,7 +199,7 @@ line-height: 26px; font-size: 16px; font-weight: 500; - color: #4e4e4e; + color: var(--color); } .logo-sponsor-container { @@ -224,7 +223,7 @@ } .logo-sponsor { width: 160px; - filter: grayscale(1); + filter: var(--company-logo-filter); &:hover { filter: grayscale(0); @@ -248,7 +247,7 @@ } .btn-primary { - color: variables.$red-color; + color: var(--primary-4dp); padding: 12px 20px; display: inline-block; border-radius: 2px; @@ -375,7 +374,7 @@ } .contact-us { - background: variables.$red-color; + background: var(--primary); color: #fff; padding: 20px 30px; border-radius: 3px; @@ -439,10 +438,11 @@ svg { font-size: 17px; - color: variables.$black-color; - &:hover { - color: variables.$red-color; - } + color: var(--color-1dp); + } + + a:hover svg { + color: var(--primary); } } @@ -462,19 +462,19 @@ max-width: 130px; max-height: 60px; padding: 6px; - -webkit-filter: grayscale(100%); - filter: grayscale(100%); - opacity: 0.5; - + -webkit-filter: var(--company-filter); + filter: var(--company-filter); + opacity: var(--company-logo-opacity); + &:hover { - -webkit-filter: grayscale(0%); - filter: grayscale(0%); + -webkit-filter: var(--company-filter-hover); + filter: var(--company-filter-hover); opacity: 1; } } .companies-list a { - color: #404040; + color: var(--color); font-size: 15px; font-weight: 400; } @@ -527,7 +527,7 @@ bottom: 20px; overflow: hidden; padding: 1em; - background: #fdfdfd; + background: var(--background-2dp) !important; text-align: center; line-height: 1.5; font-size: 13px; @@ -590,7 +590,7 @@ } .algolia-autocomplete .algolia-docsearch-suggestion--highlight { - color: variables.$red-color; + color: var(--primary); background: #fff2f4; } diff --git a/src/app/homepage/menu/menu-item/menu-item.component.scss b/src/app/homepage/menu/menu-item/menu-item.component.scss index 591d9b5263..4e85c79c7c 100644 --- a/src/app/homepage/menu/menu-item/menu-item.component.scss +++ b/src/app/homepage/menu/menu-item/menu-item.component.scss @@ -10,7 +10,7 @@ h3 { @extend .transition; text-transform: uppercase; font-weight: bold; - color: variables.$black-color; + color: var(--color-1dp); font-size: 15px; margin: 10px 0; &:hover { @@ -59,7 +59,7 @@ li { } a { - color: variables.$black-color; + color: var(--menu-color); font-size: 14px; text-decoration: none; cursor: pointer; @@ -85,10 +85,14 @@ li { margin: 10px 0 15px; } + .arrow-icon { + fill: var(--color); + } &.opened { .arrow-icon { @include utils.transform(rotate(-90deg)); - fill: variables.$red-color; + fill: var(--primary); + } } } @@ -97,7 +101,7 @@ li { cursor: pointer; &:hover { .arrow-icon { - color: variables.$red-color; + color: var(--primary); } } } diff --git a/src/app/homepage/menu/menu.component.scss b/src/app/homepage/menu/menu.component.scss index ed7c2f7db2..a0110b2f8d 100644 --- a/src/app/homepage/menu/menu.component.scss +++ b/src/app/homepage/menu/menu.component.scss @@ -8,7 +8,7 @@ padding: 90px 17px 40px 24px; width: 250px; - background: #f5f5f5; + background: var(--menu-background); position: fixed !important; bottom: 0; top: 0; @@ -68,12 +68,12 @@ } .btn-version { - background: #dfdfdf; + background: var(--background-3dp); margin-top: 5px; - color: #151515; + color: var(--color-1dp); &:hover { - background: #cccccc; + background: var(--background-5dp); } } diff --git a/src/app/homepage/newsletter/newsletter.component.scss b/src/app/homepage/newsletter/newsletter.component.scss index a0e49cc250..b97bc9862b 100644 --- a/src/app/homepage/newsletter/newsletter.component.scss +++ b/src/app/homepage/newsletter/newsletter.component.scss @@ -2,7 +2,7 @@ @use '../../../scss/utils'; .newsletter-wrapper { - background: #e8e8e8; + background: var(--background-3dp); padding: 40px 85px; overflow: hidden; position: relative; @@ -26,13 +26,13 @@ font-size: 18px; font-weight: 600; margin: 0 0 4px; - color: #111; + color: var(--color-1dp); } .newsletter-wrapper p { font-size: 15px; margin: 0; - color: #7b7b7b; + color: var(--color); font-weight: 500; line-height: 22px; } @@ -58,12 +58,14 @@ .newsletter-form .form-control { border: 0; - font-size: 15px; + font-size: 14px; + font-family: 'Source Sans Pro'; height: 40px; padding: 10px 20px; - background: #fdfdfd; + background: var(--background); + color: var(--color); width: calc(100% - 70px); - box-shadow: -2px 2px 3px gainsboro; + // box-shadow: -2px 2px 3px var(--background); border-radius: 3px; max-width: 100%; vertical-align: middle; @@ -102,7 +104,7 @@ } .newsletter-form .btn-success[disabled] { - background: #d3e6d4; + background: var(--background); color: #1dd81c; pointer-events: none; } @@ -115,7 +117,7 @@ position: absolute; margin-top: -50px; margin-left: 60px; - color: #dedede; + color: rgba(0,0,0,0.1); z-index: 0; transform: rotate(-30deg); } diff --git a/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.html b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.html new file mode 100644 index 0000000000..5a8846243d --- /dev/null +++ b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.scss b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.scss new file mode 100644 index 0000000000..bf6d02ed2c --- /dev/null +++ b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.scss @@ -0,0 +1,48 @@ +:host { + display: block; + display: flex; + align-items: center; + justify-content: center; +} + +.theme-mode-toggle { + // Button reset + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; + -webkit-appearance: none; + + display: flex; + align-items: center; + justify-content: center; + margin-left: 15px; + cursor: pointer; + color: #fff; + padding-left: 15px; + position: relative; + &:hover { + color: var(--primary); + } + + .material-icons { + font-size: 20px; + + &::before { + content: ''; + position: absolute; + background: #fff; + opacity: 0.2; + left: 0; + top: 0; + bottom: 0; + width: 2px; + } + } +} diff --git a/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.spec.ts b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.spec.ts new file mode 100644 index 0000000000..3ff8c52be0 --- /dev/null +++ b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ThemeModeToggleComponent } from './theme-mode-toggle.component'; + +describe('ThemeModeToggleComponent', () => { + let component: ThemeModeToggleComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ThemeModeToggleComponent], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ThemeModeToggleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.ts b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.ts new file mode 100644 index 0000000000..c7b4d6c062 --- /dev/null +++ b/src/app/shared/components/theme-mode-toggle/theme-mode-toggle.component.ts @@ -0,0 +1,47 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { DOCUMENT } from '@angular/common'; +import { MediaMatcher } from '@angular/cdk/layout'; +import { StorageService } from '../../services/storage.service'; + +@Component({ + selector: 'app-theme-mode-toggle', + templateUrl: './theme-mode-toggle.component.html', + styleUrls: ['./theme-mode-toggle.component.scss'], +}) +export class ThemeModeToggleComponent implements OnInit { + isDarkMode: boolean; + + constructor( + @Inject(DOCUMENT) + private readonly document: Document, + private readonly mediaMatcher: MediaMatcher, + private readonly storageService: StorageService, + ) {} + + ngOnInit() { + const userPrefersTheme = + this.mediaMatcher.matchMedia && + this.mediaMatcher.matchMedia('(prefers-color-scheme: light)').matches; + // In case the user has used the toggle button, we prioritize it over the + // system settings + this.setThemeMode(this.getUserSettingsIsDarkMode() || userPrefersTheme); + } + + toggleThemeMode() { + const isDarkMode = !this.isDarkMode; + this.storageService.set('theme-mode', isDarkMode.toString()); + this.setThemeMode(isDarkMode); + } + + private getUserSettingsIsDarkMode(): boolean { + return this.storageService.get('theme-mode') === 'true'; + } + + private setThemeMode(isDarkMode: boolean) { + this.isDarkMode = isDarkMode; + this.document.documentElement.setAttribute( + 'mode', + isDarkMode ? 'dark' : 'light', + ); + } +} diff --git a/src/app/shared/components/toc/toc.component.scss b/src/app/shared/components/toc/toc.component.scss index 75671c9216..c1bcd6c427 100644 --- a/src/app/shared/components/toc/toc.component.scss +++ b/src/app/shared/components/toc/toc.component.scss @@ -26,7 +26,7 @@ top: 10px; bottom: 10px; width: 2px; - background: #efefef; + background: var(--background-4dp); } } @@ -63,8 +63,8 @@ @include utils.transform(translateY(-50%)); content: ''; - background: #fdfdfd; - border: 2px solid #efefef; + background: var(--background); + border: 2px solid var(--background-4dp); left: -4px; width: 6px; height: 6px; @@ -80,10 +80,10 @@ } a { - color: #404040; + color: var(--color); font-weight: normal; &:hover { - color: variables.$red-color; + color: variables.$red-color; @include utils.text-gradient(); } } diff --git a/src/app/shared/services/storage.service.ts b/src/app/shared/services/storage.service.ts new file mode 100644 index 0000000000..9ce8f1467c --- /dev/null +++ b/src/app/shared/services/storage.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class StorageService { + set(key, val: unknown): void { + localStorage.setItem(key, JSON.stringify(val)); + } + + /** + * Returns the localStorage item for the given key, if any. + * Returns null elsewhere + */ + get(key: string): unknown | null { + const result = localStorage.getItem(key); + + return result ? JSON.parse(result) : null; + } + + remove(key: string): void { + const hasStorageKey = this.get(key); + + if (hasStorageKey) { + localStorage.removeItem(key); + } + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2ada03ab5f..c97ca95aad 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,5 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { StorageService } from './services/storage.service'; +import { ThemeModeToggleComponent } from './components/theme-mode-toggle/theme-mode-toggle.component'; import { BannerCoursesAuthComponent } from './components/banner-courses-auth/banner-courses-auth.component'; import { BannerCoursesGraphQLCodeFirstComponent } from './components/banner-courses-graphql-cf/banner-courses-graphql-cf.component'; import { BannerDevtoolsComponent } from './components/banner-devtools/banner-devtools.component'; @@ -24,6 +26,7 @@ import { ExtensionPipe } from './pipes/extension.pipe'; BannerCoursesGraphQLCodeFirstComponent, BannerDevtoolsComponent, BannerCoursesAuthComponent, + ThemeModeToggleComponent, ], exports: [ ExtensionPipe, @@ -36,6 +39,8 @@ import { ExtensionPipe } from './pipes/extension.pipe'; BannerCoursesGraphQLCodeFirstComponent, BannerDevtoolsComponent, BannerCoursesAuthComponent, + ThemeModeToggleComponent, ], + providers: [StorageService], }) export class SharedModule {} diff --git a/src/assets/sponsors/valor-software.png b/src/assets/sponsors/valor-software.png index cfc141f776..c57697fa37 100644 Binary files a/src/assets/sponsors/valor-software.png and b/src/assets/sponsors/valor-software.png differ diff --git a/src/index.html b/src/index.html index fe1e542392..f187716255 100644 --- a/src/index.html +++ b/src/index.html @@ -1,5 +1,5 @@ - + Documentation | NestJS - A progressive Node.js framework diff --git a/src/scss/hljs.scss b/src/scss/hljs.scss index 913ddddfae..916380d16f 100644 --- a/src/scss/hljs.scss +++ b/src/scss/hljs.scss @@ -25,11 +25,11 @@ code { font-family: Inconsolata, Consolas, 'Courier New', monospace; font-display: swap; padding: 2px 6px; - color: #2876d2; + color: var(--inline-code-color); border-radius: 4px; font-size: 15px; white-space: pre-wrap; - background: #f0f2f3; + background: var(--background-2dp); } code[class*='language-'], @@ -65,7 +65,7 @@ pre[class*='language-'] { pre[class*='language-'], :not(pre) > code[class*='language-'] { - background: #1d1d1d; + background: var(--code-background); // box-shadow: 0 5px 20px rgba(6, 6, 6, 0.35); border-radius: 6px; } diff --git a/src/scss/theme.scss b/src/scss/theme.scss new file mode 100644 index 0000000000..d0a3f29414 --- /dev/null +++ b/src/scss/theme.scss @@ -0,0 +1,99 @@ +.light-mode { + --primary: #ea2845; + --primary-accent: #ea2868; + --primary-1dp: #d71e38; + --primary-2dp: #da2640; + --primary-3dp:#db2840; + --primary-4dp: #e40020; + --primary-5dp: #ff0023; + --primary-gradient: linear-gradient(90deg, var(--primary) 0%, var(--primary-accent) 100%); + + --color: #404040; + --color-1dp: #151515; + + --background: #fdfdfd; + --background-1dp: #f7f7f7; + --background-2dp: #f0f2f3; + --background-3dp: #e8e8e8; + --background-4dp: #efefef; + --background-5dp: #cccccc; + + --header-background: #151515; + + --menu-color: #151515; + --menu-background: #f5f5f5; + + --inline-code-color: #2876d2; + + --code-background: #1d1d1d; + + --warning: #ffb36f; + --warning-color: #ed8529; + --warning-background: #fff5ec; + + --info: #0894e2; + --info-color: #0894e2; + --info-background: rgba(8, 148, 226, 0.038); + + --error: #ed2945; + --error-background: #f9eff1; + + --company-filter: grayscale(100%); + --company-filter-hover: grayscale(0%); + --company-logo-filter: grayscale(1); + + --company-logo-opacity: 0.5; + + --images-filter: unset; + --images-box-shadow: 0 0 50px 0 rgba(0, 0, 0, 0.08); +} + +.dark-mode { + --primary: #f23551; + --primary-accent: #e23770; + --primary-1dp: #f45f75; + --primary-2dp: #f4526a; + --primary-3dp: #f1455f; + --primary-4dp: #f23c57; + --primary-5dp: #f23551; + --primary-gradient: linear-gradient(90deg, var(--primary) 0%, var(--primary-accent) 100%); + + --color: #dfdfe3; + --color-1dp: #d0d0d4; + + --background: #1f1f22; + --background-1dp: #232327; + --background-2dp: #252528; + --background-3dp: #29292d; + --background-4dp: #3d3d41; + --background-5dp: #39393e; + + --header-background: #1b1b1d; + + --menu-color: #dfdfe3; + --menu-background: #242427; + + --inline-code-color: #8ec2ff; + + --code-background: #18181a; + + --warning: #ffb36f; + --warning-color: #ed8529; + --warning-background: #504337; + + --info: #0894e2; + --info-color: #0894e2; + --info-background: rgba(8, 148, 226, 0.038); + + --error: #ff677c; + --error-background: #3a2f30; + + --company-filter: contrast(0.5); + --company-filter-hover: opacity(1); + --company-logo-filter: contrast(0.5) grayscale(100%); + + --company-logo-opacity: unset; + + --images-filter: invert(1) contrast(0.85); + --images-box-shadow: 0 0 0px 0 rgba(0, 0, 0, 0.08); +} diff --git a/src/styles.scss b/src/styles.scss index fb60bd760e..77378763a1 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,6 +1,7 @@ @use './scss/hljs'; @use './scss/variables'; @use './scss/utils'; +@use './scss/theme.scss'; :root { --docsearch-primary-color: #ed2945; @@ -11,7 +12,14 @@ --docsearch-searchbox-shadow: none; --primary-color: #ea2845; --primary-accent-color: #ea2868; - --primary-gradient: linear-gradient(90deg, var(--primary-color) 0%, var(--primary-accent-color) 100%); +} + +html[mode='dark'] { + @extend .dark-mode; +} + +html[mode='light'] { + @extend .light-mode; } body { @@ -19,11 +27,16 @@ body { font-weight: 400; // letter-spacing: 0.2px; font-family: 'Source Sans Pro', 'Helvetica Neue', sans-serif; - background-color: #fdfdfd; font-display: swap; - color: variables.$grey-color; + background-color: var(--background); + color: var(--color); margin: 0; -webkit-font-smoothing: antialiased; + -webkit-transition: background 200ms cubic-bezier(0.7, 0, 0.3, 1); + -moz-transition: background 200ms cubic-bezier(0.7, 0, 0.3, 1); + -ms-transition: background 200ms cubic-bezier(0.7, 0, 0.3, 1); + -o-transition: background 200ms cubic-bezier(0.7, 0, 0.3, 1); + transition: background 200ms cubic-bezier(0.7, 0, 0.3, 1); } a { @@ -31,7 +44,7 @@ a { } strong { - color: #2d2d2d; + color: var(--color-1dp); font-weight: 600; } @@ -51,7 +64,7 @@ blockquote { line-height: 1.6; position: relative; margin: 35px 0; - background: #f9eff1; + background: var(--error-background); padding: 20px; border-radius: 3px; @@ -82,24 +95,24 @@ blockquote { } &.warning { - background: #fff5ec; + background: var(--warning-background); &::before { - background: #ffb36f; + background: var(--warning); } strong, a { - color: #ed8529; + color: var(--warning-color); } } &.info { - background: rgba(8, 148, 226, 0.038); + background: var(--info-background); &::before { - background: #0894e2; + background: var(--info); } strong, a { - color: #0894e2; + color: var(--info-color); } } } @@ -121,6 +134,11 @@ figure { } } +.illustrative-image { + filter: var(--images-filter); + box-shadow: var(--images-box-shadow); +} + figcaption { color: rgba(variables.$silver-color, 0.9); font-size: 16px; @@ -149,27 +167,27 @@ tr { th { padding: 20px; - background: #efefef; + background: var(--background-1dp); } tr:nth-of-type(even) td { - background: #f7f7f7; + background: var(--background-1dp); } tr:nth-of-type(odd) td:first-of-type { - border-right: 1px solid #f0f2f3; + border-right: 1px solid var(--background-2dp); @include utils.media(medium) { border-right: 0; - border-bottom: 1px solid #f0f2f3; + border-bottom: 1px solid var(--background-2dp); } } tr:nth-of-type(even) td:first-of-type { - border-right: 1px solid #fff; + border-right: 1px solid var(--background); @include utils.media(medium) { border-right: 0; - border-bottom: 1px solid #fff; + border-bottom: 1px solid var(--background); } } @@ -184,13 +202,13 @@ tr td { } tr td span.table-code-asterisk { - color: #2876d2; + color: var(--inline-code-color); font-weight: 700; } .file-tree { - background: #f9f9f9; - border: 4px solid #f5f5f5; + background: var(--code-background); + border: 4px solid rgba(var(--background-2dp), 0.8); margin: 40px 0; padding: 16px 32px; @@ -198,7 +216,7 @@ tr td span.table-code-asterisk { display: block; line-height: 32px; font-size: 15px; - color: #5a5a5a; + color: var(--color-1dp); } .children { @@ -226,7 +244,7 @@ tr td span.table-code-asterisk { } .external { - background: #f7f7f7; + background: var(--background-2dp); &::before { background: #e8e8e8;