├── .commitlintrc.json ├── .editorconfig ├── .eslintrc.json ├── .firebaserc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc ├── .vscode └── extensions.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── angular.json ├── apps ├── devto-e2e │ ├── .eslintrc.json │ ├── cypress.json │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── integration │ │ │ └── app.spec.ts │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── index.ts │ ├── tsconfig.e2e.json │ └── tsconfig.json └── devto │ ├── .browserslistrc │ ├── src │ ├── app │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ └── app.module.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ └── test-setup.ts │ ├── tsconfig.app.json │ └── tsconfig.spec.json ├── decorate-angular-cli.js ├── firebase.json ├── jest.config.js ├── jest.preset.js ├── libs ├── .gitkeep ├── article-detail │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── article-detail.component.spec.ts │ │ │ ├── article-detail.component.ts │ │ │ ├── article-detail.module.ts │ │ │ ├── article │ │ │ │ ├── article-details │ │ │ │ │ ├── article-details.component.spec.ts │ │ │ │ │ └── article-details.component.ts │ │ │ │ ├── comments-tree │ │ │ │ │ ├── comments-tree.component.spec.ts │ │ │ │ │ └── comments-tree.component.ts │ │ │ │ ├── comments │ │ │ │ │ ├── comments.component.spec.ts │ │ │ │ │ └── comments.component.ts │ │ │ │ ├── reactions │ │ │ │ │ ├── reactions.component.html │ │ │ │ │ ├── reactions.component.spec.ts │ │ │ │ │ └── reactions.component.ts │ │ │ │ └── services │ │ │ │ │ ├── article-detail-api.service.spec.ts │ │ │ │ │ ├── article-detail-api.service.ts │ │ │ │ │ ├── article-detail.service.spec.ts │ │ │ │ │ ├── article-detail.store.ts │ │ │ │ │ ├── comments-api.service.spec.ts │ │ │ │ │ ├── comments-api.service.ts │ │ │ │ │ ├── comments.service.spec.ts │ │ │ │ │ └── comments.store.ts │ │ │ └── user │ │ │ │ ├── user-articles │ │ │ │ ├── user-articles.component.spec.ts │ │ │ │ └── user-articles.component.ts │ │ │ │ └── user-detail │ │ │ │ ├── user-detail.component.spec.ts │ │ │ │ └── user-detail.component.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── core │ ├── app-routes │ │ ├── .eslintrc.json │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── ng-package.json │ │ ├── package.json │ │ ├── src │ │ │ ├── index.ts │ │ │ ├── lib │ │ │ │ └── core-app-routes.module.ts │ │ │ └── test-setup.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.lib.prod.json │ │ └── tsconfig.spec.json │ └── models │ │ ├── .eslintrc.json │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── ng-package.json │ │ ├── package.json │ │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── articles.ts │ │ │ ├── comment.ts │ │ │ ├── listing.ts │ │ │ ├── listings.ts │ │ │ ├── organisation.ts │ │ │ ├── reaction-data.ts │ │ │ ├── tags.ts │ │ │ ├── user.ts │ │ │ └── videosList.ts │ │ └── test-setup.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.lib.prod.json │ │ └── tsconfig.spec.json ├── environments │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── global-components │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── article-card │ │ │ │ ├── article-card.component.html │ │ │ │ ├── article-card.component.scss │ │ │ │ ├── article-card.component.spec.ts │ │ │ │ └── article-card.component.ts │ │ │ ├── container │ │ │ │ ├── container.component.spec.ts │ │ │ │ └── container.component.ts │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ ├── header.component.spec.ts │ │ │ │ └── header.component.ts │ │ │ └── scoll-tracker │ │ │ │ └── scroll-tracker.directive.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── global-constants │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── HOME_ARTICLE_HEADER_TABS.ts │ │ │ └── HOME_RIGHTBAR_TAGS.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── global-services │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── article │ │ │ │ ├── article-api.service.spec.ts │ │ │ │ └── article-api.service.ts │ │ │ ├── dateago │ │ │ │ ├── dateago.pipe.spec.ts │ │ │ │ └── dateago.pipe.ts │ │ │ ├── user-articles │ │ │ │ ├── user-articles.store.spec.ts │ │ │ │ └── user-articles.store.ts │ │ │ └── user │ │ │ │ ├── user-api.service.spec.ts │ │ │ │ ├── user-api.service.ts │ │ │ │ ├── user.store.spec.ts │ │ │ │ └── user.store.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── home │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── __snapshots__ │ │ │ │ └── home.component.spec.ts.snap │ │ │ ├── articles │ │ │ │ ├── article-container │ │ │ │ │ ├── article-container.component.spec.ts │ │ │ │ │ └── article-container.component.ts │ │ │ │ ├── article-header │ │ │ │ │ ├── article-header.component.spec.ts │ │ │ │ │ └── article-header.component.ts │ │ │ │ ├── featured-article │ │ │ │ │ ├── featured-article.component.spec.ts │ │ │ │ │ └── featured-article.component.ts │ │ │ │ └── services │ │ │ │ │ ├── article.service.spec.ts │ │ │ │ │ └── article.store.ts │ │ │ ├── home.component.spec.ts │ │ │ ├── home.component.ts │ │ │ ├── home.module.ts │ │ │ ├── rightbar │ │ │ │ ├── listings │ │ │ │ │ ├── listings.component.spec.ts │ │ │ │ │ └── listings.component.ts │ │ │ │ ├── rightbar-container │ │ │ │ │ ├── rightbar-container.component.spec.ts │ │ │ │ │ └── rightbar-container.component.ts │ │ │ │ ├── services │ │ │ │ │ ├── article-tags.service.spec.ts │ │ │ │ │ ├── article-tags.store.ts │ │ │ │ │ ├── listings-api.service.spec.ts │ │ │ │ │ ├── listings-api.service.ts │ │ │ │ │ ├── listings.service.spec.ts │ │ │ │ │ └── listings.store.ts │ │ │ │ └── tag-article │ │ │ │ │ ├── tag-article.component.spec.ts │ │ │ │ │ └── tag-article.component.ts │ │ │ └── sidebar │ │ │ │ ├── services │ │ │ │ ├── tags-api.service.spec.ts │ │ │ │ ├── tags-api.service.ts │ │ │ │ ├── tags.store.spec.ts │ │ │ │ └── tags.store.ts │ │ │ │ ├── sidebar-advertisement │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── sidebar-advertisement.component.spec.ts.snap │ │ │ │ ├── sidebar-advertisement.component.spec.ts │ │ │ │ └── sidebar-advertisement.component.ts │ │ │ │ ├── sidebar-social-links │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── sidebar-social-links.component.spec.ts.snap │ │ │ │ ├── sidebar-social-links.component.html │ │ │ │ ├── sidebar-social-links.component.spec.ts │ │ │ │ └── sidebar-social-links.component.ts │ │ │ │ ├── sidebar-tags │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── sidebar-tags.component.spec.ts.snap │ │ │ │ ├── sidebar-tags.component.spec.ts │ │ │ │ └── sidebar-tags.component.ts │ │ │ │ └── sidebar │ │ │ │ ├── __snapshots__ │ │ │ │ └── sidebar.component.spec.ts.snap │ │ │ │ ├── sidebar.component.html │ │ │ │ ├── sidebar.component.spec.ts │ │ │ │ └── sidebar.component.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── listings │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── contents │ │ │ │ ├── listing-card │ │ │ │ │ ├── listing-card.component.html │ │ │ │ │ ├── listing-card.component.scss │ │ │ │ │ ├── listing-card.component.spec.ts │ │ │ │ │ └── listing-card.component.ts │ │ │ │ ├── listings-content │ │ │ │ │ ├── listings-content.component.spec.ts │ │ │ │ │ └── listings-content.component.ts │ │ │ │ ├── masonary.directive.spec.ts │ │ │ │ ├── masonary.directive.ts │ │ │ │ └── service │ │ │ │ │ ├── listings-api.service.spec.ts │ │ │ │ │ ├── listings-api.service.ts │ │ │ │ │ └── listings-store.ts │ │ │ ├── listings-header │ │ │ │ ├── listings-header.component.spec.ts │ │ │ │ └── listings-header.component.ts │ │ │ ├── listings.component.spec.ts │ │ │ ├── listings.component.ts │ │ │ ├── listings.module.ts │ │ │ └── sidenav │ │ │ │ └── listings-sidenav │ │ │ │ ├── listings-sidenav.component.spec.ts │ │ │ │ └── listings-sidenav.component.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── styles │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.scss │ │ ├── index.ts │ │ ├── lib │ │ │ ├── _article-detail.scss │ │ │ ├── _comments.scss │ │ │ ├── _global.scss │ │ │ └── styles.module.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── user-profile │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── lib │ │ │ ├── user-header │ │ │ │ ├── user-header.component.html │ │ │ │ ├── user-header.component.scss │ │ │ │ ├── user-header.component.spec.ts │ │ │ │ └── user-header.component.ts │ │ │ ├── user-profile.component.spec.ts │ │ │ ├── user-profile.component.ts │ │ │ └── user-profile.module.ts │ │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json └── videos │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── index.ts │ ├── lib │ │ ├── videos-list │ │ │ ├── services │ │ │ │ ├── videos-list.store.spec.ts │ │ │ │ ├── videos-list.store.ts │ │ │ │ ├── videoslist-api.service.spec.ts │ │ │ │ └── videoslist-api.service.ts │ │ │ ├── video-card │ │ │ │ ├── video-card.component.spec.ts │ │ │ │ └── video-card.component.ts │ │ │ └── videos-header │ │ │ │ ├── videos-header.component.spec.ts │ │ │ │ └── videos-header.component.ts │ │ ├── videos.component.spec.ts │ │ ├── videos.component.ts │ │ └── videos.module.ts │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── nx.json ├── package-lock.json ├── package.json ├── tools ├── schematics │ └── .gitkeep └── tsconfig.tools.json ├── tsconfig.base.json └── tsconfig.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "extends": [ 18 | "plugin:@angular-eslint/recommended", 19 | "plugin:@angular-eslint/template/process-inline-templates" 20 | ], 21 | "rules": { 22 | "@angular-eslint/directive-selector": [ 23 | "error", 24 | { 25 | "type": "attribute", 26 | "prefix": "app", 27 | "style": "camelCase" 28 | } 29 | ], 30 | "@angular-eslint/component-selector": [ 31 | "error", 32 | { 33 | "type": "element", 34 | "prefix": "app", 35 | "style": "kebab-case" 36 | } 37 | ] 38 | } 39 | }, 40 | { 41 | "files": [ 42 | "*.html" 43 | ], 44 | "extends": [ 45 | "plugin:@angular-eslint/template/recommended" 46 | ], 47 | "rules": {} 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "targets": { 3 | "dev-toclone": { 4 | "hosting": { 5 | "devto": [ 6 | "dev-toclone" 7 | ] 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the main branch 8 | push: 9 | branches: [ main ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | firebase-deploy: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | - uses: actions/checkout@v2 25 | 26 | - uses: actions/setup-node@master 27 | with: 28 | node-version: '14.x' 29 | 30 | - run: npm install 31 | 32 | - run: npm run build 33 | 34 | - uses: w9jds/firebase-action@master 35 | with: 36 | args: deploy --only hosting --project dev-toclone 37 | env: 38 | FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} 39 | 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | .firebase 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | firebase-debug.log 48 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run affected:lint \ 5 | # && npm run test:headless 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "angular.ng-template", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "firsttris.vscode-jest-runner" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ajit Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | I'm starting a new series in which we will create a dev.to clone in angular. I will release some work every Thursday. I'm busy on all other days. I will use 4 | 5 | 1. Component store for state management 6 | 1. rx-angular/template for RxLet and push pipe 7 | 8 | Rest we will add stuff as needed and think about it in various scenario's as we go. I'll add stuff to github repo on Thursday and drop an article on what we did on Friday. Follow along if you want to learn more about Angular. 9 | 10 | Article drops: [article]([https://ajitblogs.com/](https://dev.to/ajitsinghkaler)) 11 | 12 | 13 | # Live stream 14 | 15 | 1. https://www.youtube.com/watch?v=puqXjXH3F6M 16 | 17 | ## Contributing 18 | 19 | Thanks for your interest in contributing! Read up on our guidelines for 20 | [contributing](https://github.com/ajitsinghkaler/devto-clone/blob/master/CONTRIBUTING.md) 21 | 22 | 23 | ## Contributors 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /apps/devto-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["src/plugins/index.js"], 11 | "rules": { 12 | "@typescript-eslint/no-var-requires": "off", 13 | "no-undef": "off" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/devto-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "pluginsFile": "./src/plugins/index", 7 | "supportFile": "./src/support/index.ts", 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/devto-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/devto-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/devto-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/devto-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('devto', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | // cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to devto!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/devto-e2e/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); 15 | 16 | module.exports = (on, config) => { 17 | // `on` is used to hook into various events Cypress emits 18 | // `config` is the resolved Cypress config 19 | 20 | // Preprocess Typescript file using Nx helper 21 | on('file:preprocessor', preprocessTypescript(config)); 22 | }; 23 | -------------------------------------------------------------------------------- /apps/devto-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/devto-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /apps/devto-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/devto-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"], 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "include": ["src/**/*.ts", "src/**/*.js"], 14 | "angularCompilerOptions": { 15 | "strictInjectionParameters": true, 16 | "strictInputAccessModifiers": true, 17 | "strictTemplates": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/devto-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.e2e.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/devto/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 18 | -------------------------------------------------------------------------------- /apps/devto/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async () => { 7 | await TestBed.configureTestingModule({ 8 | imports: [RouterTestingModule], 9 | declarations: [AppComponent], 10 | }).compileComponents(); 11 | }); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /apps/devto/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ' ', 6 | }) 7 | export class AppComponent {} 8 | -------------------------------------------------------------------------------- /apps/devto/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppRoutes } from '@devto/core/app-routes'; 5 | import { AppComponent } from './app.component'; 6 | import { HttpClientModule } from '@angular/common/http'; 7 | import { HeaderModule } from '@devto/global-components'; 8 | 9 | @NgModule({ 10 | declarations: [AppComponent], 11 | imports: [BrowserModule, AppRoutes, HttpClientModule, HeaderModule], 12 | providers: [], 13 | bootstrap: [AppComponent], 14 | }) 15 | export class AppModule {} 16 | -------------------------------------------------------------------------------- /apps/devto/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajitsinghkaler/devto-clone/f8fa3fd2d220bc25bc6c19d420c15f3135b685d7/apps/devto/src/favicon.ico -------------------------------------------------------------------------------- /apps/devto/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Devto 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/devto/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from '@devto/environments'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/devto/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; -------------------------------------------------------------------------------- /apps/devto/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts", "src/polyfills.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/devto/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": [ 3 | { 4 | "target": "devto", 5 | "public": "dist/apps/devto", 6 | "ignore": [ 7 | "**/.*" 8 | ], 9 | "headers": [ 10 | { 11 | "source": "*.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].+(css|js)", 12 | "headers": [ 13 | { 14 | "key": "Cache-Control", 15 | "value": "public,max-age=31536000,immutable" 16 | } 17 | ] 18 | } 19 | ], 20 | "rewrites": [ 21 | { 22 | "source": "**", 23 | "destination": "/index.html" 24 | } 25 | ] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nrwl/jest'); 2 | 3 | module.exports = { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset'); 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajitsinghkaler/devto-clone/f8fa3fd2d220bc25bc6c19d420c15f3135b685d7/libs/.gitkeep -------------------------------------------------------------------------------- /libs/article-detail/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": ["devto","app"], 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": ["devto","app"], 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/article-detail/README.md: -------------------------------------------------------------------------------- 1 | # article-detail 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test article-detail` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/article-detail/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'article-detail', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/article-detail', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/article-detail/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/article-detail", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/article-detail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/article-detail", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/article-detail/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/article-detail.module'; 2 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleDetailComponent } from './article-detail.component'; 4 | 5 | describe('ArticleDetailComponent', () => { 6 | let component: ArticleDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ArticleDetailComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ArticleDetailComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { tap } from 'rxjs/operators'; 4 | import { UserStore } from '@devto/global-services'; 5 | import { ArticleDetailStore } from './article/services/article-detail.store'; 6 | import { CommentsStore } from './article/services/comments.store'; 7 | import { UserArticlesStore } from '@devto/global-services'; 8 | 9 | @Component({ 10 | selector: 'app-article-detail', 11 | template: ` 14 | 15 | 19 | 20 | 21 | 22 | `, 23 | styles: [ 24 | ` 25 | :host { 26 | display: grid; 27 | grid-gap: 1rem; 28 | /* 29 | minmax is used becasue of grid blowout because of the pre tag 30 | For details refer https://css-tricks.com/preventing-a-grid-blowout/ 31 | */ 32 | grid-template-columns: 4rem minmax(0, 7fr) minmax(0, 3fr); 33 | } 34 | `, 35 | ], 36 | viewProviders: [ArticleDetailStore, UserStore, CommentsStore], 37 | }) 38 | export class ArticleDetailComponent implements OnInit { 39 | article$ = this.articleDetailStore.article$.pipe( 40 | tap((article) => { 41 | if (article?.user) { 42 | this.userStore.getUser(article.user.username); 43 | this.articleUserStore.getArticles({ username: article.user.username }); 44 | this.articleDetailStore.getReactions(article.id); 45 | this.commentsStore.getComments(article.id); 46 | } 47 | }) 48 | ); 49 | reaction$ = this.articleDetailStore.reaction$; 50 | user$ = this.userStore.user$; 51 | constructor( 52 | private articleDetailStore: ArticleDetailStore, 53 | private route: ActivatedRoute, 54 | private userStore: UserStore, 55 | private articleUserStore: UserArticlesStore, 56 | private commentsStore: CommentsStore 57 | ) {} 58 | 59 | ngOnInit(): void { 60 | this.articleDetailStore.getArticle(this.route.params); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article-detail.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ArticleDetailsComponent } from './article/article-details/article-details.component'; 4 | import { CommentsComponent } from './article/comments/comments.component'; 5 | import { UserDetailComponent } from './user/user-detail/user-detail.component'; 6 | import { UserArticlesComponent } from './user/user-articles/user-articles.component'; 7 | import { RouterModule } from '@angular/router'; 8 | import { ReactionsComponent } from './article/reactions/reactions.component'; 9 | import { ArticleDetailComponent } from './article-detail.component'; 10 | import { LetModule, PushModule } from '@rx-angular/template'; 11 | import { CommentsTreeComponent } from './article/comments-tree/comments-tree.component'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | ArticleDetailsComponent, 16 | CommentsComponent, 17 | UserDetailComponent, 18 | UserArticlesComponent, 19 | ReactionsComponent, 20 | ArticleDetailComponent, 21 | CommentsTreeComponent, 22 | ], 23 | imports: [ 24 | RouterModule.forChild([ 25 | { 26 | path: '', 27 | component: ArticleDetailComponent, 28 | }, 29 | ]), 30 | CommonModule, 31 | LetModule, 32 | PushModule, 33 | ], 34 | }) 35 | export class ArticleDetailModule {} 36 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/article-details/article-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleDetailsComponent } from './article-details.component'; 4 | 5 | describe('ArticleDetailsComponent', () => { 6 | let component: ArticleDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ArticleDetailsComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ArticleDetailsComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/comments-tree/comments-tree.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentsTreeComponent } from './comments-tree.component'; 4 | 5 | describe('CommentsTreeComponent', () => { 6 | let component: CommentsTreeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [CommentsTreeComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(CommentsTreeComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/comments-tree/comments-tree.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { CommentsStore } from '../services/comments.store'; 3 | 4 | @Component({ 5 | selector: 'app-comments-tree', 6 | template: `
7 |

8 | Discussion ({{ commentsCount }}) 9 |

10 | 11 |
12 |
13 | 14 |
`, 15 | styles: [ 16 | ` 17 | :host { 18 | display: block; 19 | app-comments { 20 | padding-left: 1.75rem; 21 | } 22 | } 23 | `, 24 | ], 25 | }) 26 | export class CommentsTreeComponent { 27 | @Input() commentsCount = 0; 28 | comments$ = this.commentStore.comments$; 29 | constructor(private commentStore: CommentsStore) {} 30 | } 31 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/comments/comments.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentsComponent } from './comments.component'; 4 | 5 | describe('CommentsComponent', () => { 6 | let component: CommentsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [CommentsComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(CommentsComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/reactions/reactions.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ReactionsComponent } from './reactions.component'; 4 | 5 | describe('ReactionsComponent', () => { 6 | let component: ReactionsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ReactionsComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ReactionsComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/reactions/reactions.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { Reaction } from '@devto/core/models'; 3 | 4 | @Component({ 5 | selector: 'app-reactions', 6 | templateUrl: './reactions.component.html', 7 | styles: [ 8 | ` 9 | :host { 10 | display: grid; 11 | gap: 1.5rem; 12 | justify-content: stretch; 13 | z-index: 1; 14 | position: sticky; 15 | top: calc(var(--header-height) + 1rem + 6vh); 16 | } 17 | .article-reactions-inner { 18 | display: grid; 19 | gap: 1.5rem; 20 | justify-content: stretch; 21 | button { 22 | padding: unset; 23 | } 24 | } 25 | 26 | button { 27 | display: inherit; 28 | line-height: 0.8; 29 | } 30 | 31 | .article-reaction-icon { 32 | color: #363d44; 33 | padding: 0.5rem; 34 | transition: all cubic-bezier(0.17, 0.67, 0.5, 0.71); 35 | border-radius: 50%; 36 | } 37 | 38 | .article-reaction-number { 39 | line-height: 1.5; 40 | color: #4d5760; 41 | font-size: 0.875rem; 42 | } 43 | .show-more-button { 44 | margin: auto; 45 | color: #64707d; 46 | } 47 | 48 | svg path { 49 | fill: currentColor; 50 | } 51 | `, 52 | ], 53 | }) 54 | export class ReactionsComponent { 55 | @Input() reactions: Reaction[] = []; 56 | } 57 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/article-detail-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleDetailApiService } from './article-detail-api.service'; 4 | 5 | describe('ArticleDetailApiService', () => { 6 | let service: ArticleDetailApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ArticleDetailApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/article-detail-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { ArticleDetails } from '@devto/core/models'; 6 | import { Reaction, ReactionData } from '@devto/core/models'; 7 | import { environment } from '@devto/environments'; 8 | 9 | @Injectable({ 10 | providedIn: 'root', 11 | }) 12 | export class ArticleDetailApiService { 13 | constructor(private http: HttpClient) {} 14 | 15 | getArticle(username: string, slug: string): Observable { 16 | return this.http.get( 17 | `${environment.baseApi}/articles/${username}/${slug}` 18 | ); 19 | } 20 | 21 | getArticleReactions(id: number): Observable { 22 | return this.http 23 | .get(`${environment.base}/reactions?article_id=${id}`) 24 | .pipe(map((reaction) => reaction.article_reaction_counts)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/article-detail.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleDetailStore } from './article-detail.store'; 4 | 5 | describe('ArticleDetailStore', () => { 6 | let service: ArticleDetailStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ArticleDetailStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/article-detail.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Params } from '@angular/router'; 3 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 4 | import { Observable } from 'rxjs'; 5 | import { switchMap } from 'rxjs/operators'; 6 | import { ArticleDetails } from '@devto/core/models'; 7 | import { Reaction } from '@devto/core/models'; 8 | import { ArticleDetailApiService } from './article-detail-api.service'; 9 | 10 | interface ArticleState { 11 | article: ArticleDetails | null; 12 | reactions: Reaction[]; 13 | } 14 | @Injectable() 15 | export class ArticleDetailStore extends ComponentStore { 16 | readonly article$ = this.select((state) => state.article); 17 | readonly reaction$ = this.select((state) => state.reactions); 18 | readonly setArticle = this.updater( 19 | (state: ArticleState, article: ArticleDetails) => ({ 20 | ...state, 21 | article, 22 | }) 23 | ); 24 | 25 | readonly setReactions = this.updater( 26 | (state: ArticleState, reactions: Reaction[]) => ({ 27 | ...state, 28 | reactions, 29 | }) 30 | ); 31 | readonly getArticle = this.effect((params: Observable) => 32 | params.pipe( 33 | switchMap((params) => 34 | this.articleDetailApiS.getArticle(params.user, params.slug).pipe( 35 | tapResponse( 36 | (article) => this.setArticle(article), 37 | (error) => this.logError(error) 38 | ) 39 | ) 40 | ) 41 | ) 42 | ); 43 | readonly getReactions = this.effect((id: Observable) => 44 | id.pipe( 45 | switchMap((id) => 46 | this.articleDetailApiS.getArticleReactions(id).pipe( 47 | tapResponse( 48 | (reactions) => this.setReactions(reactions), 49 | (error) => this.logError(error) 50 | ) 51 | ) 52 | ) 53 | ) 54 | ); 55 | constructor(private articleDetailApiS: ArticleDetailApiService) { 56 | super({ article: null, reactions: [] }); 57 | } 58 | 59 | private logError(error: unknown) { 60 | console.error(error); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/comments-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentsApiService } from './comments-api.service'; 4 | 5 | describe('CommentsApiService', () => { 6 | let service: CommentsApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(CommentsApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/comments-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Comment } from '@devto/core/models'; 5 | import { environment } from '@devto/environments'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class CommentsApiService { 11 | constructor(private http: HttpClient) {} 12 | 13 | getComments(id: number): Observable { 14 | return this.http.get( 15 | `${environment.baseApi}/comments?a_id=${id}` 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/comments.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { CommentsStore } from './comments.store'; 4 | 5 | describe('CommentsStore', () => { 6 | let service: CommentsStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(CommentsStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/article/services/comments.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { Observable } from 'rxjs'; 4 | import { switchMap } from 'rxjs/operators'; 5 | import { Comment } from '@devto/core/models'; 6 | import { CommentsApiService } from './comments-api.service'; 7 | 8 | interface CommentsState { 9 | comments: Comment[]; 10 | } 11 | 12 | @Injectable({ 13 | providedIn: 'root', 14 | }) 15 | export class CommentsStore extends ComponentStore { 16 | readonly comments$ = this.select((state) => state.comments); 17 | readonly setComments = this.updater( 18 | (state: CommentsState, comments: Comment[]) => ({ 19 | ...state, 20 | comments, 21 | }) 22 | ); 23 | 24 | readonly getComments = this.effect((id: Observable) => 25 | id.pipe( 26 | switchMap((id) => 27 | this.commetsApiS.getComments(id).pipe( 28 | tapResponse( 29 | (comments) => this.setComments(comments), 30 | (error) => this.logError(error) 31 | ) 32 | ) 33 | ) 34 | ) 35 | ); 36 | 37 | constructor(private commetsApiS: CommentsApiService) { 38 | super({ comments: [] }); 39 | } 40 | 41 | private logError(error: unknown) { 42 | console.error(error); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/user/user-articles/user-articles.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserArticlesComponent } from './user-articles.component'; 4 | 5 | describe('UserArticlesComponent', () => { 6 | let component: UserArticlesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [UserArticlesComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(UserArticlesComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/lib/user/user-detail/user-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserDetailComponent } from './user-detail.component'; 4 | 5 | describe('UserDetailComponent', () => { 6 | let component: UserDetailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [UserDetailComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(UserDetailComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/article-detail/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/article-detail/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/article-detail/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/article-detail/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/article-detail/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/core/app-routes/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "devto", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "devto", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/core/app-routes/README.md: -------------------------------------------------------------------------------- 1 | # core-app-routes 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test core-app-routes` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/core/app-routes/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'core-app-routes', 3 | preset: '../../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../../coverage/libs/core/app-routes', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/core/app-routes/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../../dist/libs/core/app-routes", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/core/app-routes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/core/app-routes", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/core/app-routes/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/core-app-routes.module'; 2 | -------------------------------------------------------------------------------- /libs/core/app-routes/src/lib/core-app-routes.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { ContainerComponent } from '@devto/global-components'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: ContainerComponent, 9 | children: [ 10 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 11 | { 12 | path: 'home', 13 | loadChildren: () => 14 | import('@devto/home').then((m) => m.HomeModule), 15 | }, 16 | { 17 | path: 'videos', 18 | loadChildren: () => 19 | import('@devto/videos').then((m) => m.VideosModule), 20 | }, 21 | { 22 | path: 'listings', 23 | loadChildren: () => 24 | import('@devto/listings').then((m) => m.ListingsModule), 25 | }, 26 | { 27 | path: 'users/:username', 28 | loadChildren: () => 29 | import('@devto/user-profile').then( 30 | (m) => m.UserProfileModule 31 | ), 32 | }, 33 | { 34 | path: ':user/:slug', 35 | loadChildren: () => 36 | import('@devto/article-detail').then( 37 | (m) => m.ArticleDetailModule 38 | ), 39 | }, 40 | ], 41 | }, 42 | ]; 43 | 44 | @NgModule({ 45 | imports: [RouterModule.forRoot(routes)], 46 | exports: [RouterModule], 47 | }) 48 | export class AppRoutes {} 49 | -------------------------------------------------------------------------------- /libs/core/app-routes/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/core/app-routes/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/core/app-routes/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/core/app-routes/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/core/app-routes/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/core/models/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "devto", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "devto", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/core/models/README.md: -------------------------------------------------------------------------------- 1 | # core-models 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test core-models` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/core/models/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'core-models', 3 | preset: '../../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../../coverage/libs/core/models', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/core/models/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../../dist/libs/core/models", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/core/models/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/core/models", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/core/models/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/articles'; 2 | export * from './lib/listing'; 3 | export * from './lib/listings'; 4 | 5 | export * from './lib/organisation'; 6 | export * from './lib/reaction-data'; 7 | export * from './lib/tags'; 8 | export * from './lib/user'; 9 | export * from './lib/videosList'; 10 | export * from './lib/comment'; 11 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/articles.ts: -------------------------------------------------------------------------------- 1 | import { Organization } from './organisation'; 2 | import { Tag } from './tags'; 3 | import { User } from './user'; 4 | 5 | export interface Article { 6 | type_of: string; 7 | id: number; 8 | title: string; 9 | description: string; 10 | cover_image: string; 11 | readable_publish_date: string; 12 | social_image: string; 13 | tag_list: Tag[]; 14 | tags: string; 15 | slug: string; 16 | path: string; 17 | url: string; 18 | canonical_url: string; 19 | comments_count: number; 20 | positive_reactions_count: number; 21 | public_reactions_count: number; 22 | collection_id: string; 23 | created_at: Date; 24 | edited_at: Date; 25 | crossposted_at: string; 26 | published_at: Date; 27 | last_comment_at: Date; 28 | published_timestamp: Date; 29 | reading_time_minutes: number; 30 | user: User; 31 | organization: Organization; 32 | } 33 | 34 | /** 35 | * This is a bit redundant wrt to article but interfaces are free in runtime. So I kept it 36 | * Todo In future use Omit and extend to reuse one an another 37 | */ 38 | export interface ArticleDetails { 39 | type_of: string; 40 | id: number; 41 | title: string; 42 | description: string; 43 | readable_publish_date: string; 44 | slug: string; 45 | path: string; 46 | url: string; 47 | comments_count: number; 48 | public_reactions_count: number; 49 | collection_id: string; 50 | published_timestamp: Date; 51 | positive_reactions_count: number; 52 | cover_image: string; 53 | social_image: string; 54 | canonical_url: string; 55 | created_at: Date; 56 | edited_at: string; 57 | crossposted_at: string; 58 | published_at: Date; 59 | last_comment_at: Date; 60 | reading_time_minutes: number; 61 | tag_list: string; 62 | tags: string[]; 63 | body_html: string; 64 | body_markdown: string; 65 | user: User; 66 | flare_tag: Partial; 67 | } 68 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/comment.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | export interface Comment { 4 | type_of: string; 5 | id_code: string; 6 | created_at: Date; 7 | body_html: string; 8 | user: User; 9 | children: Comment[]; 10 | } 11 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/listing.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | export interface Listing { 4 | type_of: string; 5 | id: number; 6 | created_at: string; 7 | title: string; 8 | slug: string; 9 | body_markdown: string; 10 | tag_list: string; 11 | tags: string[]; 12 | category: string; 13 | processed_html: string; 14 | published: boolean; 15 | user: User; 16 | } 17 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/listings.ts: -------------------------------------------------------------------------------- 1 | export interface Author { 2 | username: string; 3 | name: string; 4 | profile_image_90: string; 5 | } 6 | 7 | export interface AuthorListing { 8 | id: number; 9 | body_markdown: string; 10 | bumped_at: Date; 11 | category: string; 12 | contact_via_connect: boolean; 13 | expires_at?: any; 14 | originally_published_at: Date; 15 | location: string; 16 | processed_html: string; 17 | published: boolean; 18 | slug: string; 19 | title: string; 20 | user_id: number; 21 | tags: string[]; 22 | author: Author; 23 | } 24 | 25 | export type ListingsReponse = { result: Listings }; 26 | 27 | export type Listings = AuthorListing[]; 28 | 29 | export type ListingCategory = 30 | | undefined 31 | | 'cfp' 32 | | 'forhire' 33 | | 'collabs' 34 | | 'education' 35 | | 'jobs' 36 | | 'mentors' 37 | | 'products' 38 | | 'mentees' 39 | | 'forsale' 40 | | 'events' 41 | | 'misc'; 42 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/organisation.ts: -------------------------------------------------------------------------------- 1 | export interface Organization { 2 | name: string; 3 | username: string; 4 | slug: string; 5 | profile_image: string; 6 | profile_image_90: string; 7 | } 8 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/reaction-data.ts: -------------------------------------------------------------------------------- 1 | export interface ReactionData { 2 | article_reaction_counts: Reaction[]; 3 | } 4 | 5 | export interface Reaction { 6 | category: string; 7 | count: number; 8 | } 9 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/tags.ts: -------------------------------------------------------------------------------- 1 | export interface Tag { 2 | id: number; 3 | name: string; 4 | bg_color_hex: string; 5 | text_color_hex: string; 6 | } 7 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | name: string; 3 | username: string; 4 | twitter_username: string; 5 | github_username: string; 6 | website_url: string; 7 | profile_image: string; 8 | profile_image_90: string; 9 | } 10 | 11 | export interface UserDetails { 12 | type_of: string; 13 | id: number; 14 | username: string; 15 | name: string; 16 | summary: string; 17 | twitter_username: string; 18 | github_username: string; 19 | website_url: null; 20 | location: string; 21 | joined_at: string; 22 | profile_image: string; 23 | } 24 | -------------------------------------------------------------------------------- /libs/core/models/src/lib/videosList.ts: -------------------------------------------------------------------------------- 1 | export interface VideosList { 2 | cloudinary_video_url: string; 3 | id: number; 4 | path: string; 5 | title: string; 6 | type_of: string; 7 | user: { name: string }; 8 | name: string; 9 | user_id: number; 10 | video_duration_in_minutes: string; 11 | video_source_url: string; 12 | } 13 | -------------------------------------------------------------------------------- /libs/core/models/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/core/models/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/core/models/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/core/models/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/core/models/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/environments/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "devto", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "devto", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/environments/README.md: -------------------------------------------------------------------------------- 1 | # environments 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test environments` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/environments/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'environments', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/environments', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/environments/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/environments", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/environments/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/environments", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/environments/src/index.ts: -------------------------------------------------------------------------------- 1 | export { environment } from './lib/environment'; 2 | export * as environmentProd from './lib/environment.prod'; 3 | -------------------------------------------------------------------------------- /libs/environments/src/lib/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseApi: 'https://dev.to/api', 4 | base: 'https://dev.to', 5 | }; 6 | -------------------------------------------------------------------------------- /libs/environments/src/lib/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | baseApi: 'https://dev.to/api', 8 | base: 'https://dev.to', 9 | }; 10 | 11 | /* 12 | * For easier debugging in development mode, you can import the following file 13 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 14 | * 15 | * This import should be commented out in production mode because it will have a negative impact 16 | * on performance if an error is thrown. 17 | */ 18 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 19 | -------------------------------------------------------------------------------- /libs/environments/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/environments/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/environments/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/environments/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/environments/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/global-components/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": ["devto", "app"], 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": ["devto", "app"], 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/global-components/README.md: -------------------------------------------------------------------------------- 1 | # global-components 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test global-components` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/global-components/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'global-components', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/global-components', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/global-components/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/global-components", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/global-components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/global-components", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/global-components/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/container/container.component'; 2 | export * from './lib/header/header.component'; 3 | export * from './lib/article-card/article-card.component'; 4 | export * from './lib/scoll-tracker/scroll-tracker.directive'; 5 | -------------------------------------------------------------------------------- /libs/global-components/src/lib/article-card/article-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleCardComponent } from './article-card.component'; 4 | 5 | describe('ArticleCardComponent', () => { 6 | let component: ArticleCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ArticleCardComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ArticleCardComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/global-components/src/lib/article-card/article-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, OnInit } from '@angular/core'; 2 | import { Article } from '@devto/core/models'; 3 | 4 | import { NgModule } from '@angular/core'; 5 | import { CommonModule } from '@angular/common'; 6 | import { DateagoPipe } from '@devto/global-services'; 7 | import { RouterModule } from '@angular/router'; 8 | 9 | @Component({ 10 | selector: 'app-article-card', 11 | templateUrl: './article-card.component.html', 12 | styleUrls: ['./article-card.component.scss'], 13 | }) 14 | export class ArticleCardComponent implements OnChanges { 15 | @Input() article!: Article; 16 | uriSection = ''; 17 | constructor() {} 18 | 19 | ngOnChanges(): void { 20 | this.uriSection = 21 | this.article?.organization?.slug || this.article.user.username; 22 | } 23 | } 24 | 25 | @NgModule({ 26 | declarations: [ArticleCardComponent, DateagoPipe], 27 | imports: [CommonModule, RouterModule.forChild([])], 28 | exports: [ArticleCardComponent], 29 | }) 30 | export class ArticleCardModule {} 31 | -------------------------------------------------------------------------------- /libs/global-components/src/lib/container/container.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ContainerComponent } from './container.component'; 4 | 5 | describe('ContainerComponent', () => { 6 | let component: ContainerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ContainerComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ContainerComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/global-components/src/lib/container/container.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-container', 7 | template: ` `, 8 | styles: [ 9 | ` 10 | :host { 11 | display: block; 12 | max-width: var(--screen-width); 13 | padding: 1rem; 14 | margin: auto; 15 | box-sizing: border-box; 16 | } 17 | `, 18 | ], 19 | }) 20 | export class ContainerComponent {} 21 | 22 | @NgModule({ 23 | declarations: [ContainerComponent], 24 | imports: [CommonModule, RouterModule.forChild([])], 25 | exports: [ContainerComponent], 26 | }) 27 | export class ContainerModule {} 28 | -------------------------------------------------------------------------------- /libs/global-components/src/lib/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderComponent } from './header.component'; 4 | 5 | describe('HeaderComponent', () => { 6 | let component: HeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [HeaderComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(HeaderComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/global-components/src/lib/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component, NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-header', 7 | templateUrl: './header.component.html', 8 | styleUrls: ['./header.component.scss'], 9 | }) 10 | export class HeaderComponent { 11 | constructor() {} 12 | } 13 | 14 | @NgModule({ 15 | declarations: [HeaderComponent], 16 | imports: [CommonModule, RouterModule.forChild([])], 17 | exports: [HeaderComponent], 18 | }) 19 | export class HeaderModule {} -------------------------------------------------------------------------------- /libs/global-components/src/lib/scoll-tracker/scroll-tracker.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | Output, 4 | EventEmitter, 5 | OnDestroy, 6 | OnInit, 7 | } from '@angular/core'; 8 | import { fromEvent, Subscription } from 'rxjs'; 9 | import { debounceTime } from 'rxjs/operators'; 10 | 11 | @Directive({ 12 | /* eslint-disable @angular-eslint/directive-selector */ 13 | selector: '[scrollTracker]', 14 | }) 15 | export class ScrollTrackerDirective implements OnDestroy, OnInit { 16 | @Output() scrollingFinished = new EventEmitter(); 17 | subscriptions!: Subscription; 18 | 19 | ngOnInit(): void { 20 | this.subscriptions = fromEvent(window, 'scroll') 21 | .pipe(debounceTime(500)) 22 | .subscribe((_) => { 23 | if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { 24 | this.scrollingFinished.emit(); 25 | } 26 | }); 27 | } 28 | 29 | ngOnDestroy(): void { 30 | this.subscriptions?.unsubscribe(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libs/global-components/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/global-components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/global-components/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/global-components/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/global-components/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/global-constants/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "devto", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "devto", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/global-constants/README.md: -------------------------------------------------------------------------------- 1 | # global-constants 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test global-constants` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/global-constants/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'global-constants', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/global-constants', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/global-constants/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/global-constants", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/global-constants/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/global-constants", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/global-constants/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/HOME_ARTICLE_HEADER_TABS'; 2 | export * from './lib/HOME_RIGHTBAR_TAGS'; 3 | -------------------------------------------------------------------------------- /libs/global-constants/src/lib/HOME_ARTICLE_HEADER_TABS.ts: -------------------------------------------------------------------------------- 1 | export const ARTICLE_HEADER_TABS = ['feed', 'week', 'month', 'year', 'infinity', 'latest']; -------------------------------------------------------------------------------- /libs/global-constants/src/lib/HOME_RIGHTBAR_TAGS.ts: -------------------------------------------------------------------------------- 1 | export const HOME_RIGHTBAR_TAGS = ['news', 'help', 'discuss', 'challenge', 'meta']; 2 | -------------------------------------------------------------------------------- /libs/global-constants/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/global-constants/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/global-constants/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/global-constants/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/global-constants/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/global-services/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "devto", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "devto", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/global-services/README.md: -------------------------------------------------------------------------------- 1 | # global-services 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test global-services` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/global-services/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'global-services', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/global-services', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/global-services/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/global-services", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/global-services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/global-services", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/global-services/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/article/article-api.service'; 2 | export * from './lib/user/user-api.service'; 3 | export * from './lib/user/user.store'; 4 | export * from './lib/user-articles/user-articles.store'; 5 | export * from './lib/dateago/dateago.pipe'; 6 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/article/article-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleApiService } from './article-api.service'; 4 | 5 | describe('ArticleApiService', () => { 6 | let service: ArticleApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ArticleApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/article/article-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpParams } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Article } from '@devto/core/models'; 5 | import { environment } from '@devto/environments'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class ArticleApiService { 11 | constructor(private http: HttpClient) {} 12 | 13 | getArticles( 14 | params?: Record 15 | ): Observable { 16 | const newParams = new HttpParams({ fromObject: params }).toString(); 17 | return this.http.get( 18 | `${environment.baseApi}/articles?${newParams}` 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/dateago/dateago.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { DateagoPipe } from './dateago.pipe'; 2 | 3 | describe('DateagoPipe', () => { 4 | it('create an instance', () => { 5 | const pipe = new DateagoPipe(); 6 | expect(pipe).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/dateago/dateago.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'dateago', 5 | }) 6 | export class DateagoPipe implements PipeTransform { 7 | transform(value: Date, args?: unknown[]): string { 8 | if (value) { 9 | const seconds = Math.floor((Date.now() - +new Date(value)) / 1000); 10 | if (seconds < 59) 11 | // less than 59 seconds ago will show as 'Just now' 12 | return 'Just now'; 13 | const intervals = { 14 | day: 86400, 15 | hour: 3600, 16 | minute: 60, 17 | }; 18 | switch (true) { 19 | case seconds < intervals.minute: { 20 | return '(Just Now)'; 21 | } 22 | case seconds < intervals.hour: { 23 | return `(${Math.floor(seconds / intervals.minute)} minute's ago)`; 24 | } 25 | case seconds < intervals.day: { 26 | return `(${Math.floor(seconds / intervals.hour)} + hours's ago`; 27 | } 28 | } 29 | } 30 | return ''; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/user-articles/user-articles.store.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserArticlesStore } from './user-articles.store'; 4 | 5 | describe('UserArticlesService', () => { 6 | let service: UserArticlesStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserArticlesStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/user-articles/user-articles.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Article } from '@devto/core/models'; 3 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 4 | import { Observable } from 'rxjs'; 5 | import { switchMap } from 'rxjs/operators'; 6 | import { ArticleApiService } from '@devto/global-services'; 7 | 8 | interface ArticlesState { 9 | articles: Article[]; 10 | } 11 | @Injectable({ 12 | providedIn: 'root', 13 | }) 14 | export class UserArticlesStore extends ComponentStore { 15 | readonly articles$ = this.select((state) => state.articles); 16 | 17 | readonly setArticles = this.updater( 18 | (state: ArticlesState, articles: Article[]) => { 19 | return { 20 | ...state, 21 | articles, 22 | }; 23 | } 24 | ); 25 | 26 | readonly getArticles = this.effect( 27 | (params: Observable>) => 28 | params.pipe( 29 | switchMap((params) => 30 | this.articleApiS.getArticles({ username: params.username }).pipe( 31 | tapResponse( 32 | (articles) => this.setArticles(articles), 33 | (error) => this.logError(error) 34 | ) 35 | ) 36 | ) 37 | ) 38 | ); 39 | 40 | constructor(private articleApiS: ArticleApiService) { 41 | super({ articles: [] }); 42 | } 43 | 44 | logError(error: unknown) { 45 | console.error(error); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/user/user-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserApiService } from './user-api.service'; 4 | 5 | describe('UserApiService', () => { 6 | let service: UserApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/user/user-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { UserDetails } from '@devto/core/models'; 5 | import { environment } from '@devto/environments'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class UserApiService { 11 | constructor(private http: HttpClient) {} 12 | 13 | getUser(username?: string): Observable { 14 | return this.http.get( 15 | `${environment.baseApi}/users/by_username?url=${username}` 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/user/user.store.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserStore } from './user.store'; 4 | 5 | describe('UserService', () => { 6 | let service: UserStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/global-services/src/lib/user/user.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { Observable } from 'rxjs'; 4 | import { switchMap } from 'rxjs/operators'; 5 | import { UserDetails } from '@devto/core/models'; 6 | import { UserApiService } from './user-api.service'; 7 | 8 | interface UserState { 9 | user: UserDetails | null; 10 | } 11 | 12 | @Injectable() 13 | export class UserStore extends ComponentStore { 14 | readonly user$ = this.select((state) => state.user); 15 | readonly setUser = this.updater((state: UserState, user: UserDetails) => ({ 16 | ...state, 17 | user, 18 | })); 19 | readonly getUser = this.effect((username: Observable) => 20 | username.pipe( 21 | switchMap((username) => 22 | this.userApiS.getUser(username).pipe( 23 | tapResponse( 24 | (user) => this.setUser(user), 25 | (error) => this.logError(error) 26 | ) 27 | ) 28 | ) 29 | ) 30 | ); 31 | 32 | constructor(private userApiS: UserApiService) { 33 | super({ user: null }); 34 | } 35 | 36 | private logError(error: unknown) { 37 | console.error(error); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /libs/global-services/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/global-services/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/global-services/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/global-services/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/global-services/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/home/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": ["devto","app"], 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": ["devto","app"], 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/home/README.md: -------------------------------------------------------------------------------- 1 | # home 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test home` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/home/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'home', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/home', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/home/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/home", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/home/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/home", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/home/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/home.module'; 2 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/article-container/article-container.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleContainerComponent } from './article-container.component'; 4 | 5 | describe('ArticleContainerComponent', () => { 6 | let component: ArticleContainerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ArticleContainerComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ArticleContainerComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/article-container/article-container.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ArticleStore } from '../services/article.store'; 3 | 4 | @Component({ 5 | selector: 'app-article-container', 6 | template: ` 7 | 10 | 11 | 12 | `, 13 | }) 14 | export class ArticleContainerComponent { 15 | articles$ = this.articleStore.articles$; 16 | featuredArticle$ = this.articleStore.featuredArticle$; 17 | 18 | constructor(private articleStore: ArticleStore) {} 19 | } 20 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/article-header/article-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleHeaderComponent } from './article-header.component'; 4 | 5 | describe('ArticleHeaderComponent', () => { 6 | let component: ArticleHeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ArticleHeaderComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ArticleHeaderComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/article-header/article-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ARTICLE_HEADER_TABS } from '@devto/global-constants'; 3 | 4 | @Component({ 5 | selector: 'app-article-header', 6 | template: `
7 |

Posts

8 | 9 | 18 |
`, 19 | styles: [ 20 | ` 21 | .tabs-list { 22 | margin: 0; 23 | padding: 0; 24 | } 25 | 26 | .tab-item { 27 | padding: 0.5rem; 28 | margin: 0 0.25rem; 29 | flex-direction: column; 30 | font-size: 1rem; 31 | transition: all cubic-bezier(0.17, 0.67, 0.5, 0.71) 100ms; 32 | border-radius: 5px; 33 | &.active { 34 | font-weight: 500; 35 | color: #08090a; 36 | } 37 | &:before { 38 | content: attr(data-text); 39 | height: 0; 40 | visibility: hidden; 41 | overflow: hidden; 42 | user-select: none; 43 | pointer-events: none; 44 | } 45 | &:after { 46 | position: absolute; 47 | content: ''; 48 | } 49 | } 50 | `, 51 | ], 52 | }) 53 | export class ArticleHeaderComponent { 54 | selectedTab = 'feed'; 55 | tabs = ARTICLE_HEADER_TABS; 56 | } 57 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/featured-article/featured-article.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FeaturedArticleComponent } from './featured-article.component'; 4 | 5 | describe('FeaturedArticleComponent', () => { 6 | let component: FeaturedArticleComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [FeaturedArticleComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(FeaturedArticleComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/featured-article/featured-article.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { Article } from '@devto/core/models'; 3 | 4 | @Component({ 5 | selector: 'app-featured-article', 6 | template: ` 13 | 14 | 15 | 23 | `, 24 | styles: [ 25 | ` 26 | :host { 27 | display: block; 28 | background-color: #fff; 29 | box-shadow: 0 0 0 1px rgba(8, 9, 10, 0.1); 30 | border-radius: 5px; 31 | cursor: pointer; 32 | margin: 0 0 0.75rem; 33 | overflow: hidden; 34 | &:focus-within { 35 | outline: none; 36 | box-shadow: 0 0 0 2px #3b49df; 37 | } 38 | } 39 | 40 | app-article-card { 41 | box-shadow: none; 42 | margin: 0 0 0; 43 | &:focus-within { 44 | outline: none; 45 | box-shadow: none; 46 | } 47 | } 48 | 49 | .featured-img { 50 | display: block; 51 | width: 100%; 52 | height: auto; 53 | padding-bottom: 42%; 54 | background-size: cover; 55 | background-position: center center; 56 | } 57 | 58 | .featured-title { 59 | font-size: 1.875rem; 60 | } 61 | `, 62 | ], 63 | }) 64 | export class FeaturedArticleComponent { 65 | @Input() featured!: Article; 66 | } 67 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/services/article.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleStore } from './article.store'; 4 | 5 | describe('ArticleStore', () => { 6 | let service: ArticleStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ArticleStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/home/src/lib/articles/services/article.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { ArticleApiService } from '@devto/global-services'; 4 | import { Article } from '@devto/core/models'; 5 | 6 | interface ArticlesState { 7 | articles: Article[]; 8 | featured?: Article; 9 | } 10 | 11 | @Injectable({ 12 | providedIn: 'root', 13 | }) 14 | export class ArticleStore extends ComponentStore { 15 | readonly articles$ = this.select((state) => state.articles); 16 | readonly featuredArticle$ = this.select((state) => state.featured); 17 | readonly setArticles = this.updater( 18 | (state: ArticlesState, articles: Article[]) => { 19 | let index = 0; 20 | articles.find((article, idx) => { 21 | index = idx; 22 | return article.cover_image; 23 | }); 24 | let featured = articles.splice(index, 1)[0]; 25 | return { 26 | ...state, 27 | featured: featured, 28 | articles: articles, 29 | }; 30 | } 31 | ); 32 | readonly getArticles = this.effect(() => 33 | this.articleApiS.getArticles({ state: 'rising' }).pipe( 34 | tapResponse( 35 | (articles) => this.setArticles(articles), 36 | (error) => this.logError(error) 37 | ) 38 | ) 39 | ); 40 | constructor(private articleApiS: ArticleApiService) { 41 | super({ articles: [] }); 42 | } 43 | 44 | logError(error: unknown) { 45 | console.error(error); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 3 | import { RouterTestingModule } from "@angular/router/testing"; 4 | 5 | import { ArticleContainerComponent } from './articles/article-container/article-container.component'; 6 | import { HomeComponent } from './home.component'; 7 | import { RightbarContainerComponent } from './rightbar/rightbar-container/rightbar-container.component'; 8 | import { SidebarComponent } from './sidebar/sidebar/sidebar.component'; 9 | import { HomeModule } from './home.module'; 10 | 11 | describe('HomeComponent', () => { 12 | let component: HomeComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async () => { 16 | await TestBed.configureTestingModule({ 17 | declarations: [ 18 | HomeComponent, 19 | SidebarComponent, 20 | ArticleContainerComponent, 21 | RightbarContainerComponent, 22 | ], 23 | imports: [HomeModule, HttpClientTestingModule, RouterTestingModule], 24 | }).compileComponents(); 25 | }); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(HomeComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | 37 | it('should render', () => { 38 | expect(fixture.nativeElement).toMatchSnapshot(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | template: ` 6 | 7 | `, 8 | styles: [ 9 | ` 10 | :host { 11 | display: grid; 12 | grid-gap: 1rem; 13 | grid-template-columns: 240px 2fr 1fr; 14 | } 15 | `, 16 | ], 17 | }) 18 | export class HomeComponent {} 19 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SidebarComponent } from './sidebar/sidebar/sidebar.component'; 4 | import { RouterModule } from '@angular/router'; 5 | import { SidebarTagsComponent } from './sidebar/sidebar-tags/sidebar-tags.component'; 6 | import { SidebarAdvertisementComponent } from './sidebar/sidebar-advertisement/sidebar-advertisement.component'; 7 | import { SidebarSocialLinksComponent } from './sidebar/sidebar-social-links/sidebar-social-links.component'; 8 | import { FeaturedArticleComponent } from './articles/featured-article/featured-article.component'; 9 | import { ArticleCardModule } from '@devto/global-components'; 10 | import { ArticleContainerComponent } from './articles/article-container/article-container.component'; 11 | import { ArticleHeaderComponent } from './articles/article-header/article-header.component'; 12 | import { HomeComponent } from './home.component'; 13 | import { LetModule, PushModule } from '@rx-angular/template'; 14 | import { ListingsComponent } from './rightbar/listings/listings.component'; 15 | import { RightbarContainerComponent } from './rightbar/rightbar-container/rightbar-container.component'; 16 | import { TagArticleComponent } from './rightbar/tag-article/tag-article.component'; 17 | @NgModule({ 18 | declarations: [ 19 | SidebarComponent, 20 | SidebarTagsComponent, 21 | SidebarAdvertisementComponent, 22 | SidebarSocialLinksComponent, 23 | FeaturedArticleComponent, 24 | ArticleContainerComponent, 25 | ArticleHeaderComponent, 26 | HomeComponent, 27 | ListingsComponent, 28 | RightbarContainerComponent, 29 | TagArticleComponent, 30 | ], 31 | imports: [ 32 | LetModule, 33 | ArticleCardModule, 34 | PushModule, 35 | CommonModule, 36 | RouterModule.forChild([ 37 | { 38 | path: '', 39 | component: HomeComponent, 40 | }, 41 | ]), 42 | ], 43 | }) 44 | export class HomeModule {} 45 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/listings/listings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsComponent } from './listings.component'; 4 | 5 | describe('ListingsComponent', () => { 6 | let component: ListingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ListingsComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ListingsComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/listings/listings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ListingsStore } from '../services/listings.store'; 3 | 4 | @Component({ 5 | selector: 'app-listings', 6 | template: `
7 |
8 |

Listings

9 |
10 | See all 11 |
12 |
13 | 14 | 27 | loading 28 | loading 29 |
`, 30 | styles: [ 31 | ` 32 | header { 33 | padding: 0.75rem 1rem; 34 | border-bottom: 1px solid #eef0f1; 35 | h3 { 36 | font-size: 1.25rem; 37 | font-weight: 700; 38 | } 39 | 40 | .see-all-link { 41 | font-weight: 500; 42 | font-size: 0.875rem; 43 | color: #3b49df; 44 | } 45 | } 46 | 47 | .listing-item { 48 | display: block; 49 | padding: 1rem; 50 | border-bottom: 1px solid #eef0f1; 51 | color: #202428; 52 | } 53 | 54 | .listing-type { 55 | color: #64707d; 56 | font-size: 0.875rem; 57 | padding-top: 0.25rem; 58 | } 59 | 60 | .create-listing { 61 | font-weight: 500; 62 | padding: 0.75rem; 63 | font-size: 0.875rem; 64 | text-align: center; 65 | color: #202428; 66 | display: block; 67 | } 68 | 69 | :host { 70 | background-color: #f9fafa; 71 | color: #202428; 72 | box-shadow: 0 0 0 1px rgba(8, 9, 10, 0.05); 73 | display: block; 74 | border-radius: 5px; 75 | } 76 | `, 77 | ], 78 | }) 79 | export class ListingsComponent { 80 | listing$ = this.listingStore.listing$; 81 | constructor(private listingStore: ListingsStore) {} 82 | } 83 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/rightbar-container/rightbar-container.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RightbarContainerComponent } from './rightbar-container.component'; 4 | 5 | describe('RightbarContainerComponent', () => { 6 | let component: RightbarContainerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [RightbarContainerComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(RightbarContainerComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/rightbar-container/rightbar-container.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { HOME_RIGHTBAR_TAGS } from '@devto/global-constants'; 3 | @Component({ 4 | selector: 'app-rightbar-container', 5 | template: ` `, 12 | styles: [ 13 | ` 14 | aside { 15 | display: grid; 16 | grid-row-gap: 1rem; 17 | } 18 | :host { 19 | display: block; 20 | } 21 | `, 22 | ], 23 | }) 24 | export class RightbarContainerComponent { 25 | asideTags = HOME_RIGHTBAR_TAGS; 26 | } 27 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/services/article-tags.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ArticleTagsStore } from './article-tags.store'; 4 | 5 | describe('ArticleTagsService', () => { 6 | let service: ArticleTagsStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ArticleTagsStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/services/article-tags.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Article } from '@devto/core/models'; 3 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 4 | import { Observable } from 'rxjs'; 5 | import { switchMap } from 'rxjs/operators'; 6 | import { ArticleApiService } from '@devto/global-services'; 7 | 8 | interface ArticlesState { 9 | articles: Article[]; 10 | } 11 | @Injectable({ 12 | providedIn: 'root', 13 | }) 14 | export class ArticleTagsStore extends ComponentStore { 15 | readonly articles$ = this.select((state) => state.articles); 16 | 17 | readonly setArticles = this.updater( 18 | (state: ArticlesState, articles: Article[]) => ({ 19 | ...state, 20 | articles: articles, 21 | }) 22 | ); 23 | 24 | readonly getArticles = this.effect( 25 | (params: Observable>) => 26 | params.pipe( 27 | switchMap((params) => 28 | this.articleApiS.getArticles({ ...params, top: 3 }).pipe( 29 | tapResponse( 30 | (articles) => this.setArticles(articles), 31 | (error) => this.logError(error) 32 | ) 33 | ) 34 | ) 35 | ) 36 | ); 37 | 38 | constructor(private articleApiS: ArticleApiService) { 39 | super({ articles: [] }); 40 | } 41 | 42 | logError(error: unknown) { 43 | console.error(error); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/services/listings-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsApiService } from './listings-api.service'; 4 | 5 | describe('ListingsApiService', () => { 6 | let service: ListingsApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ListingsApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/services/listings-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Listing } from '@devto/core/models'; 5 | import { environment } from '@devto/environments'; 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class ListingsApiService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getListing(): Observable { 13 | return this.http.get( 14 | `${environment.baseApi}/listings?per_page=5 ` 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/services/listings.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsStore } from './listings.store'; 4 | 5 | describe('ListingsStore', () => { 6 | let service: ListingsStore; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ListingsStore); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/services/listings.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { Listing } from '@devto/core/models'; 4 | import { ListingsApiService } from './listings-api.service'; 5 | interface ListingState { 6 | listing: Listing[]; 7 | } 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class ListingsStore extends ComponentStore { 12 | readonly listing$ = this.select((state) => state.listing); 13 | readonly setlistings = this.updater( 14 | (state: ListingState, listing: Listing[]) => ({ 15 | ...state, 16 | listing: listing, 17 | }) 18 | ); 19 | readonly getlistings = this.effect(() => 20 | this.listingApiS.getListing().pipe( 21 | tapResponse( 22 | (listings) => this.setlistings(listings), 23 | (error) => this.logError(error) 24 | ) 25 | ) 26 | ); 27 | constructor(private listingApiS: ListingsApiService) { 28 | super({ listing: [] }); 29 | } 30 | 31 | logError(error: unknown) { 32 | console.error(error); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/tag-article/tag-article.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TagArticleComponent } from './tag-article.component'; 4 | 5 | describe('TagArticleComponent', () => { 6 | let component: TagArticleComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [TagArticleComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(TagArticleComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/rightbar/tag-article/tag-article.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { ArticleTagsStore } from '../services/article-tags.store'; 3 | 4 | @Component({ 5 | selector: 'app-tag-article', 6 | template: `
7 |
8 |

#{{ tag }}

9 |
10 | 11 | 21 | loading 22 | loading 23 |
`, 24 | styles: [ 25 | ` 26 | header { 27 | padding: 0.75rem 1rem; 28 | border-bottom: 1px solid #eef0f1; 29 | h3 { 30 | font-size: 1.25rem; 31 | font-weight: 700; 32 | } 33 | 34 | .see-all-link { 35 | font-weight: 500; 36 | font-size: 0.875rem; 37 | color: #3b49df; 38 | } 39 | } 40 | 41 | .listing-item { 42 | display: block; 43 | padding: 1rem; 44 | border-bottom: 1px solid #eef0f1; 45 | color: #202428; 46 | } 47 | 48 | .listing-type { 49 | color: #64707d; 50 | font-size: 0.875rem; 51 | padding-top: 0.25rem; 52 | } 53 | 54 | .create-listing { 55 | font-weight: 500; 56 | padding: 0.75rem; 57 | font-size: 0.875rem; 58 | text-align: center; 59 | color: #202428; 60 | display: block; 61 | } 62 | 63 | :host { 64 | background-color: #f9fafa; 65 | color: #202428; 66 | box-shadow: 0 0 0 1px rgba(8, 9, 10, 0.05); 67 | display: block; 68 | border-radius: 5px; 69 | } 70 | `, 71 | ], 72 | viewProviders: [ArticleTagsStore], 73 | }) 74 | export class TagArticleComponent implements OnInit { 75 | @Input() tag = ''; 76 | article$ = this.articleStore.articles$; 77 | 78 | constructor(private articleStore: ArticleTagsStore) {} 79 | 80 | ngOnInit(): void { 81 | this.articleStore.getArticles({ tag: this.tag }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/services/tags-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpClientTestingModule, 3 | HttpTestingController, 4 | } from '@angular/common/http/testing'; 5 | import { TestBed } from '@angular/core/testing'; 6 | import { environment } from '@devto/environments'; 7 | 8 | import { TagsApiService } from './tags-api.service'; 9 | 10 | describe('TagsApiService', () => { 11 | let service: TagsApiService; 12 | let httpMock: HttpTestingController; 13 | 14 | beforeEach(() => { 15 | TestBed.configureTestingModule({ 16 | imports: [HttpClientTestingModule], 17 | }); 18 | service = TestBed.inject(TagsApiService); 19 | httpMock = TestBed.inject(HttpTestingController); 20 | }); 21 | 22 | afterEach(() => { 23 | // After every test, assert that there are no more pending requests. 24 | httpMock.verify(); 25 | }); 26 | 27 | it('should be created', () => { 28 | expect(service).toBeTruthy(); 29 | }); 30 | 31 | it('should send a get request to appropriate url on getTags subscription', () => { 32 | service.getTags().subscribe(); 33 | const req = httpMock.expectOne(`${environment.baseApi}/tags`); 34 | expect(req.request.method).toEqual('GET'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/services/tags-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Tag } from '@devto/core/models'; 5 | import { environment } from '@devto/environments'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class TagsApiService { 11 | constructor(private http: HttpClient) {} 12 | 13 | getTags(): Observable { 14 | return this.http.get(`${environment.baseApi}/tags`); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/services/tags.store.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { of } from 'rxjs'; 4 | import { TagsApiService } from './tags-api.service'; 5 | 6 | import { TagsStore } from './tags.store'; 7 | 8 | const mockTags = [ 9 | { 10 | id: 6, 11 | name: 'javascript', 12 | bg_color_hex: '#F7DF1E', 13 | text_color_hex: '#000000', 14 | }, 15 | { 16 | id: 8, 17 | name: 'webdev', 18 | bg_color_hex: '#562765', 19 | text_color_hex: '#ffffff', 20 | }, 21 | ]; 22 | 23 | const mockTagsService = { 24 | getTags: () => of([mockTags[0]]), 25 | }; 26 | 27 | describe('TagsStore', () => { 28 | let store: TagsStore; 29 | let tagsApiService: TagsApiService; 30 | 31 | beforeEach(() => { 32 | TestBed.configureTestingModule({ 33 | imports: [HttpClientTestingModule], 34 | providers: [{ provide: TagsApiService, useValue: mockTagsService }], 35 | }); 36 | store = TestBed.inject(TagsStore); 37 | tagsApiService = TestBed.inject(TagsApiService); 38 | }); 39 | 40 | it('should be created', () => { 41 | expect(store).toBeTruthy(); 42 | }); 43 | 44 | it('should set initial state as in constructor', (done) => { 45 | store.state$.subscribe((tags) => { 46 | expect(tags).toEqual({ tags: [] }); 47 | done(); 48 | }); 49 | }); 50 | 51 | it('should change state as in setTags function', (done) => { 52 | store.setTags(mockTags); 53 | store.state$.subscribe((tags) => { 54 | expect(tags.tags.length).toBe(2); 55 | expect(tags.tags[1]).toEqual(mockTags[1]); 56 | done(); 57 | }); 58 | }); 59 | 60 | it('should return the current value of the store tags', (done) => { 61 | store.setState({ tags: mockTags }); 62 | store.tags$.subscribe((tags) => { 63 | expect(tags.length).toBe(2); 64 | expect(tags[1]).toEqual(mockTags[1]); 65 | done(); 66 | }); 67 | }); 68 | 69 | it('should call the updater if effect is started', (done) => { 70 | const spy = jest.spyOn(tagsApiService, 'getTags'); 71 | spy.mockReturnValue(of(mockTags)); 72 | store.tags$.subscribe((tags) => { 73 | expect(tags.length).toBe(2); 74 | expect(tags[1]).toEqual(mockTags[1]); 75 | done(); 76 | }); 77 | store.getTags(); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/services/tags.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { Tag } from '@devto/core/models'; 4 | import { TagsApiService } from './tags-api.service'; 5 | 6 | interface TagsState { 7 | tags: Tag[]; 8 | } 9 | 10 | @Injectable({ 11 | providedIn: 'root', 12 | }) 13 | export class TagsStore extends ComponentStore { 14 | readonly tags$ = this.select((state) => state.tags); 15 | readonly getTags = this.effect(() => 16 | this.tagsApiS.getTags().pipe( 17 | tapResponse( 18 | (tags) => this.setTags(tags), 19 | (error) => this.logError(error) 20 | ) 21 | ) 22 | ); 23 | 24 | readonly setTags = this.updater((state: TagsState, tags: Tag[]) => ({ 25 | ...state, 26 | tags, 27 | })); 28 | 29 | constructor(private readonly tagsApiS: TagsApiService) { 30 | super({ tags: [] }); 31 | } 32 | 33 | logError(error: unknown) { 34 | console.error(error); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-advertisement/__snapshots__/sidebar-advertisement.component.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SidebarAdvertisementComponent should render 1`] = ` 4 | 29 | `; 30 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-advertisement/sidebar-advertisement.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SidebarAdvertisementComponent } from './sidebar-advertisement.component'; 4 | 5 | describe('SidebarAdvertisementComponent', () => { 6 | let component: SidebarAdvertisementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [SidebarAdvertisementComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(SidebarAdvertisementComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | 25 | it('should render', () => { 26 | expect(fixture.nativeElement).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-advertisement/sidebar-advertisement.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sidebar-advertisement', 5 | template: ` `, 16 | styles: [ 17 | ` 18 | .ad-decription { 19 | text-align: center; 20 | line-height: 1.29em; 21 | } 22 | `, 23 | ], 24 | }) 25 | export class SidebarAdvertisementComponent {} 26 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-social-links/sidebar-social-links.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SidebarSocialLinksComponent } from './sidebar-social-links.component'; 4 | 5 | describe('SidebarSocialLinksComponent', () => { 6 | let component: SidebarSocialLinksComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [SidebarSocialLinksComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(SidebarSocialLinksComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | 25 | it('should render', () => { 26 | expect(fixture.nativeElement).toMatchSnapshot(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-social-links/sidebar-social-links.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sidebar-social-links', 5 | templateUrl: './sidebar-social-links.component.html', 6 | styles: [ 7 | ` 8 | :host { 9 | padding: 1rem; 10 | margin-top: 1rem; 11 | display: block; 12 | } 13 | .social-link-icon { 14 | margin: 0 0.5rem; 15 | color: #64707d; 16 | svg, 17 | svg path { 18 | fill: currentColor; 19 | } 20 | } 21 | `, 22 | ], 23 | }) 24 | export class SidebarSocialLinksComponent {} 25 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-tags/__snapshots__/sidebar-tags.component.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SidebarTagsComponent should render 1`] = ` 4 |
7 | 39 |
40 | `; 41 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-tags/sidebar-tags.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { LetModule } from '@rx-angular/template'; 3 | import { of } from 'rxjs'; 4 | import { TagsStore } from '../services/tags.store'; 5 | 6 | import { SidebarTagsComponent } from './sidebar-tags.component'; 7 | 8 | describe('SidebarTagsComponent', () => { 9 | let component: SidebarTagsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | imports: [LetModule], 15 | declarations: [SidebarTagsComponent], 16 | providers: [{ provide: TagsStore, useValue: mockTagsStore }], 17 | }).compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(SidebarTagsComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | 30 | it('should render', () => { 31 | expect(fixture.nativeElement).toMatchSnapshot(); 32 | }); 33 | }); 34 | 35 | const mockTagsStore = { 36 | tags: of([ 37 | { 38 | id: 6, 39 | name: 'javascript', 40 | bg_color_hex: '#F7DF1E', 41 | text_color_hex: '#000000', 42 | }, 43 | { 44 | id: 8, 45 | name: 'webdev', 46 | bg_color_hex: '#562765', 47 | text_color_hex: '#ffffff', 48 | }, 49 | ]), 50 | }; 51 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar-tags/sidebar-tags.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { TagsStore } from '../services/tags.store'; 3 | 4 | @Component({ 5 | selector: 'app-sidebar-tags', 6 | template: ` `, 40 | styles: [ 41 | ` 42 | .tags-title { 43 | font-size: 16px; 44 | font-weight: 700; 45 | color: #202428; 46 | } 47 | 48 | header { 49 | padding: 0.5rem; 50 | } 51 | 52 | .followed-tags { 53 | height: 42vh; 54 | overflow-y: auto; 55 | a { 56 | display: block; 57 | padding: 0.5rem; 58 | color: #202428; 59 | border-radius: 5px; 60 | width: 100%; 61 | &:hover, 62 | &:active { 63 | background-color: rgba(8, 9, 10, 0.05); 64 | color: #323ebe; 65 | } 66 | } 67 | } 68 | `, 69 | ], 70 | }) 71 | export class SidebarTagsComponent { 72 | tags$ = this.tagsStore.tags$; 73 | constructor(private tagsStore: TagsStore) {} 74 | } 75 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar/sidebar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | 5 | import { HomeModule } from '../../home.module'; 6 | import { SidebarAdvertisementComponent } from '../sidebar-advertisement/sidebar-advertisement.component'; 7 | import { SidebarSocialLinksComponent } from '../sidebar-social-links/sidebar-social-links.component'; 8 | import { SidebarTagsComponent } from '../sidebar-tags/sidebar-tags.component'; 9 | import { SidebarComponent } from './sidebar.component'; 10 | 11 | describe('SidebarComponent', () => { 12 | let component: SidebarComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async () => { 16 | await TestBed.configureTestingModule({ 17 | declarations: [ 18 | SidebarComponent, 19 | SidebarAdvertisementComponent, 20 | SidebarTagsComponent, 21 | SidebarSocialLinksComponent, 22 | ], 23 | 24 | imports: [HomeModule, HttpClientTestingModule, RouterTestingModule], 25 | }).compileComponents(); 26 | }); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(SidebarComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | 38 | it('should render', () => { 39 | expect(fixture.nativeElement).toMatchSnapshot(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /libs/home/src/lib/sidebar/sidebar/sidebar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sidebar', 5 | templateUrl: './sidebar.component.html', 6 | styles: [ 7 | ` 8 | :host { 9 | display: block; 10 | width: 240px; 11 | } 12 | a { 13 | width: 100%; 14 | padding: 0.5rem; 15 | display: inline-flex; 16 | align-items: center; 17 | color: #202428; 18 | border-radius: 5px; 19 | &:hover, 20 | &:active { 21 | background-color: rgba(8, 9, 10, 0.05); 22 | color: #323ebe; 23 | } 24 | } 25 | 26 | .sidebar-link-icon { 27 | margin-right: 0.5rem; 28 | vertical-align: middle; 29 | width: 1.5rem; 30 | height: 1.5rem; 31 | font-size: 1.25rem; 32 | } 33 | 34 | .more-link { 35 | padding-left: 2.5rem; 36 | font-size: 0.875rem; 37 | color: #64707d; 38 | } 39 | `, 40 | ], 41 | }) 42 | export class SidebarComponent {} 43 | -------------------------------------------------------------------------------- /libs/home/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/home/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/home/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/home/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/home/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/listings/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": ["devto", "app"], 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": ["devto", "app"], 25 | "style": "kebab-case" 26 | } 27 | ], 28 | "@typescript-eslint/no-non-null-assertion": "off" 29 | } 30 | }, 31 | { 32 | "files": ["*.html"], 33 | "extends": ["plugin:@nrwl/nx/angular-template"], 34 | "rules": {} 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /libs/listings/README.md: -------------------------------------------------------------------------------- 1 | # listings 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test listings` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/listings/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'listings', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/listings', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/listings/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/listings", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/listings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/listings", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.0", 6 | "@angular/core": "^12.1.0" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/listings/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/listings.module'; 2 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/listing-card/listing-card.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ listing.title }} 5 |

6 | 9 | 18 |
19 | 33 |
34 |
35 |
36 | 55 |
56 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/listing-card/listing-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingCardComponent } from './listing-card.component'; 4 | 5 | describe('ListingCardComponent', () => { 6 | let component: ListingCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ListingCardComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ListingCardComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/listing-card/listing-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { AuthorListing } from '@devto/core/models'; 3 | 4 | @Component({ 5 | selector: 'app-listing-card', 6 | templateUrl: './listing-card.component.html', 7 | styleUrls: ['./listing-card.component.scss'], 8 | }) 9 | export class ListingCardComponent { 10 | @Input() listing!: AuthorListing; 11 | } 12 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/listings-content/listings-content.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsContentComponent } from './listings-content.component'; 4 | 5 | describe('ListingsContentComponent', () => { 6 | let component: ListingsContentComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ListingsContentComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ListingsContentComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/listings-content/listings-content.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { map, tap } from 'rxjs/operators'; 4 | import { ListingsStore } from '../service/listings-store'; 5 | 6 | @Component({ 7 | selector: 'app-listings-content', 8 | template: `
9 |
10 | 15 |
16 |
17 | 18 |
19 |
`, 20 | styles: [ 21 | ` 22 | .listing-container { 23 | font-size: 16px; 24 | } 25 | .listings-cards { 26 | display: grid; 27 | grid-gap: 1rem; 28 | grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); 29 | margin-bottom: 1.5rem; 30 | } 31 | `, 32 | ], 33 | }) 34 | export class ListingsContentComponent implements OnInit { 35 | hasPagination = false; 36 | listingStore$ = this.listingsStore.listings$.pipe( 37 | tap((listing) => (this.hasPagination = listing?.length === 75)) 38 | ); 39 | 40 | constructor( 41 | private listingsStore: ListingsStore, 42 | private activatedRoute: ActivatedRoute 43 | ) {} 44 | 45 | ngOnInit(): void { 46 | this.listingsStore.getListings( 47 | this.activatedRoute.params.pipe(map((param) => param.category)) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/masonary.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { MasonaryDirective } from './masonary.directive'; 2 | 3 | describe('MasonaryDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new MasonaryDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/masonary.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appMasonary]', 5 | }) 6 | export class MasonaryDirective implements OnInit, OnDestroy { 7 | private observer = new MutationObserver(() => { 8 | this.cardElments.forEach((cardElement) => { 9 | this.resizeMasonryItem(cardElement as HTMLElement); 10 | }); 11 | }); 12 | 13 | constructor(private gridElement: ElementRef) {} 14 | 15 | get cardElments() { 16 | return Array.from(this.gridElement.nativeElement.children); 17 | } 18 | 19 | ngOnInit() { 20 | this.observer.observe(this.gridElement.nativeElement, { childList: true }); 21 | } 22 | 23 | //Reference:https://w3bits.com/css-grid-masonry/ 24 | resizeMasonryItem = (cardeElment: HTMLElement) => { 25 | /* Get the grid object, its row-gap, and the size of its implicit rows */ 26 | const grid = this.gridElement.nativeElement; 27 | const rowGap = parseInt( 28 | window.getComputedStyle(grid).getPropertyValue('grid-row-gap'), 29 | 10 30 | ); 31 | const rowHeight = 0; 32 | // eslint-disable @typescript-eslint/no-non-null-assertion 33 | const rowSpan = Math.ceil( 34 | (cardeElment.querySelector('.listing-card')!.getBoundingClientRect() 35 | .height + 36 | rowGap) / 37 | (rowHeight + rowGap) 38 | ); 39 | cardeElment.style.gridRowEnd = `span ${rowSpan}`; 40 | }; 41 | 42 | ngOnDestroy() { 43 | this.observer.disconnect(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/service/listings-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsApiService } from './listings-api.service'; 4 | 5 | describe('ListingsApiService', () => { 6 | let service: ListingsApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ListingsApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/service/listings-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpParams } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { ListingCategory, ListingsReponse } from '@devto/core/models'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class ListingsApiService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getListings(category?: ListingCategory): Observable { 13 | const categoryParam = new HttpParams() 14 | .set('category', category || '') 15 | .toString(); 16 | return this.http.get( 17 | `https://dev.to/search/listings?${categoryParam}&listing_search=&page=0&per_page=75&tag_boolean_mode=all` 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libs/listings/src/lib/contents/service/listings-store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { Observable } from 'rxjs'; 4 | import { switchMap } from 'rxjs/operators'; 5 | import { ListingCategory, Listings } from '@devto/core/models'; 6 | import { ListingsApiService } from './listings-api.service'; 7 | 8 | interface ListingsState { 9 | listings: Listings; 10 | } 11 | 12 | @Injectable({ 13 | providedIn: 'root', 14 | }) 15 | export class ListingsStore extends ComponentStore { 16 | readonly listings$: Observable = this.select((state) => { 17 | return state.listings; 18 | }); 19 | readonly addListings = this.updater((state, listings: Listings) => ({ 20 | listings: [...state.listings, ...listings], 21 | })); 22 | 23 | readonly loadListings = this.updater((_, listings: Listings) => ({ 24 | listings: [...listings], 25 | })); 26 | 27 | constructor(private listingsApiS: ListingsApiService) { 28 | super({ listings: [] }); 29 | } 30 | 31 | getListings = this.effect((activatedRoute$: Observable) => 32 | activatedRoute$.pipe( 33 | switchMap((categoryParam) => 34 | this.listingsApiS.getListings(categoryParam).pipe( 35 | tapResponse( 36 | (listings) => { 37 | return this.loadListings(listings.result); 38 | }, 39 | (error) => this.logError(error) 40 | ) 41 | ) 42 | ) 43 | ) 44 | ); 45 | 46 | logError(error: unknown) { 47 | console.error(error); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libs/listings/src/lib/listings-header/listings-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsHeaderComponent } from './listings-header.component'; 4 | 5 | describe('ListingsHeaderComponent', () => { 6 | let component: ListingsHeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ListingsHeaderComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ListingsHeaderComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/listings/src/lib/listings-header/listings-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-listings-header', 5 | template: `
6 |

Listings

7 | 17 |
`, 18 | styles: [ 19 | ` 20 | li { 21 | list-style: none; 22 | } 23 | .listings-main-title { 24 | font-size: 1.875rem; 25 | margin: 0; 26 | } 27 | .listings-header-actions { 28 | margin: 0; 29 | gap: 0.5rem; 30 | } 31 | `, 32 | ], 33 | }) 34 | export class ListingsHeaderComponent {} 35 | -------------------------------------------------------------------------------- /libs/listings/src/lib/listings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsComponent } from './listings.component'; 4 | 5 | describe('ListingsComponent', () => { 6 | let component: ListingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ListingsComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ListingsComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/listings/src/lib/listings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-listings', 5 | template: ` 6 |
7 | 8 |
9 | 10 |
11 |
`, 12 | styles: [ 13 | ` 14 | main { 15 | padding-top: 1rem; 16 | display: grid; 17 | grid-gap: 1rem; 18 | grid-template-columns: 240px 1fr; 19 | } 20 | `, 21 | ], 22 | }) 23 | export class ListingsComponent {} 24 | -------------------------------------------------------------------------------- /libs/listings/src/lib/listings.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ListingsComponent } from './listings.component'; 4 | import { RouterModule } from '@angular/router'; 5 | import { ListingsHeaderComponent } from './listings-header/listings-header.component'; 6 | import { ListingsSidenavComponent } from './sidenav/listings-sidenav/listings-sidenav.component'; 7 | import { ListingsContentComponent } from './contents/listings-content/listings-content.component'; 8 | import { MasonaryDirective } from './contents/masonary.directive'; 9 | import { LetModule, PushModule } from '@rx-angular/template'; 10 | import { ListingCardComponent } from './contents/listing-card/listing-card.component'; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | ListingsComponent, 15 | ListingsHeaderComponent, 16 | ListingsSidenavComponent, 17 | ListingsContentComponent, 18 | MasonaryDirective, 19 | ListingCardComponent, 20 | ], 21 | imports: [ 22 | LetModule, 23 | PushModule, 24 | RouterModule.forChild([ 25 | { 26 | path: '', 27 | component: ListingsComponent, 28 | children: [ 29 | { 30 | path: ':category', 31 | component: ListingsContentComponent, 32 | }, 33 | { 34 | path: '', 35 | component: ListingsContentComponent, 36 | }, 37 | ], 38 | }, 39 | ]), 40 | CommonModule, 41 | ], 42 | }) 43 | export class ListingsModule {} 44 | -------------------------------------------------------------------------------- /libs/listings/src/lib/sidenav/listings-sidenav/listings-sidenav.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ListingsSidenavComponent } from './listings-sidenav.component'; 4 | 5 | describe('ListingsSidenavComponent', () => { 6 | let component: ListingsSidenavComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ListingsSidenavComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ListingsSidenavComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/listings/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/listings/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/listings/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/listings/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/listings/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/styles/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "devto", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "devto", 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/styles/README.md: -------------------------------------------------------------------------------- 1 | # styles 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test styles` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/styles/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'styles', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/styles', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/styles/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/styles", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/styles", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/styles/src/index.scss: -------------------------------------------------------------------------------- 1 | @import "./lib/global"; 2 | @import "./lib/article-detail"; 3 | @import "./lib/comments"; 4 | -------------------------------------------------------------------------------- /libs/styles/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/styles.module'; 2 | -------------------------------------------------------------------------------- /libs/styles/src/lib/_comments.scss: -------------------------------------------------------------------------------- 1 | app-comments { 2 | details { 3 | position: relative; 4 | } 5 | .comment-border { 6 | border-radius: 5px; 7 | background: #fff; 8 | color: rgb(8, 9, 10); 9 | box-shadow: rgba(8, 9, 10, 0.1) 0px 0px 0px 1px; 10 | overflow-wrap: anywhere; 11 | padding: 0.25rem; 12 | width: 100%; 13 | } 14 | 15 | summary.closed { 16 | display: block; 17 | align-items: center; 18 | transform: initial; 19 | top: 0; 20 | cursor: pointer; 21 | font-size: 0.875rem; 22 | color: #64707d; 23 | padding: 0.25rem 0.5rem; 24 | font-style: italic; 25 | border-radius: 5px; 26 | background: #f9fafa; 27 | margin-bottom: 1rem; 28 | } 29 | 30 | .comment-header { 31 | padding: 0.5rem 0.75rem 0 0.25rem; 32 | } 33 | 34 | .comment-username { 35 | padding: 0.5rem; 36 | } 37 | 38 | .comments-body { 39 | margin-bottom: 2rem; 40 | } 41 | 42 | .comment-inner-text { 43 | font-size: 1.125rem; 44 | padding: 0 0.75rem; 45 | margin: 0.5rem 0 1rem; 46 | p { 47 | margin-bottom: 1rem; 48 | } 49 | } 50 | 51 | .comment-avatar { 52 | width: 2rem; 53 | margin-right: 0.5rem; 54 | margin-top: 0.75rem; 55 | height: 2rem; 56 | img { 57 | border-radius: 100%; 58 | height: 100%; 59 | display: inline-block; 60 | vertical-align: bottom; 61 | } 62 | } 63 | .app-comments .app-comments .app-comments .app-comments { 64 | padding-left: 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /libs/styles/src/lib/styles.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [CommonModule], 6 | }) 7 | export class StylesModule {} 8 | -------------------------------------------------------------------------------- /libs/styles/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/styles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/styles/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/styles/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/styles/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/user-profile/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": ["devto","app"], 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": ["devto","app"], 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/user-profile/README.md: -------------------------------------------------------------------------------- 1 | # user-profile 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test user-profile` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/user-profile/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'user-profile', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/user-profile', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/user-profile/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/user-profile", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/user-profile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/user-profile", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/user-profile/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/user-profile.module'; 2 | -------------------------------------------------------------------------------- /libs/user-profile/src/lib/user-header/user-header.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 1rem; 4 | padding-top: 2rem; 5 | margin: 0 auto; 6 | max-width: 1024px; 7 | } 8 | 9 | header { 10 | border-radius: 5px; 11 | background: #fff; 12 | color: #08090a; 13 | box-shadow: 0 0 0 1px rgba(8, 9, 10, 0.1); 14 | overflow-wrap: anywhere; 15 | text-align: center; 16 | } 17 | 18 | .profile-header { 19 | position: relative; 20 | } 21 | 22 | img { 23 | padding: 0.5rem; 24 | width: 8rem; 25 | height: 8rem; 26 | border-radius: 100%; 27 | } 28 | 29 | .profile-header-details { 30 | padding: 1.5rem; 31 | h1 { 32 | font-weight: 800; 33 | font-size: 1.875rem; 34 | margin-bottom: 0.5rem; 35 | } 36 | 37 | .user-summary { 38 | font-size: 1.125rem; 39 | margin-bottom: 1rem; 40 | color: #202408; 41 | } 42 | } 43 | 44 | .profile-header-meta { 45 | font-size: 0.875rem; 46 | color: #64707d; 47 | margin-bottom: 0.5rem; 48 | display: flex; 49 | flex-wrap: wrap; 50 | align-items: center; 51 | justify-content: center; 52 | gap: 1.5rem; 53 | .item { 54 | display: flex; 55 | align-items: center; 56 | } 57 | svg { 58 | margin-right: 0.5rem; 59 | } 60 | } 61 | 62 | a { 63 | color: #64707d; 64 | } 65 | -------------------------------------------------------------------------------- /libs/user-profile/src/lib/user-header/user-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserHeaderComponent } from './user-header.component'; 4 | 5 | describe('UserHeaderComponent', () => { 6 | let component: UserHeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [UserHeaderComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(UserHeaderComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/user-profile/src/lib/user-header/user-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { UserDetails } from '@devto/core/models'; 3 | 4 | @Component({ 5 | selector: 'app-user-header', 6 | templateUrl: './user-header.component.html', 7 | styleUrls: ['./user-header.component.scss'], 8 | }) 9 | export class UserHeaderComponent { 10 | @Input() user!: UserDetails; 11 | } 12 | -------------------------------------------------------------------------------- /libs/user-profile/src/lib/user-profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserProfileComponent } from './user-profile.component'; 4 | 5 | describe('UserProfileComponent', () => { 6 | let component: UserProfileComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [UserProfileComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(UserProfileComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/user-profile/src/lib/user-profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { UserStore } from '@devto/global-services'; 4 | import { UserArticlesStore } from '@devto/global-services'; 5 | 6 | @Component({ 7 | selector: 'app-user-profile', 8 | template: ` 9 | 10 | 11 | 12 | 13 | 14 | `, 15 | styles: [ 16 | ` 17 | app-article-card { 18 | margin-left: auto; 19 | margin-right: auto; 20 | max-width: 994px; 21 | } 22 | `, 23 | ], 24 | viewProviders: [UserStore, UserArticlesStore], 25 | }) 26 | export class UserProfileComponent implements OnInit { 27 | user$ = this.userStore.user$; 28 | articles$ = this.userArticles.articles$; 29 | constructor( 30 | private userStore: UserStore, 31 | private route: ActivatedRoute, 32 | private userArticles: UserArticlesStore 33 | ) {} 34 | ngOnInit() { 35 | this.userStore.getUser(this.route.snapshot.params.username); 36 | this.userArticles.getArticles({ 37 | username: this.route.snapshot.params.username, 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /libs/user-profile/src/lib/user-profile.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { UserProfileComponent } from './user-profile.component'; 4 | import { UserHeaderComponent } from './user-header/user-header.component'; 5 | import { RouterModule } from '@angular/router'; 6 | import { LetModule, PushModule } from '@rx-angular/template'; 7 | import { ArticleCardModule } from '@devto/global-components'; 8 | 9 | @NgModule({ 10 | declarations: [UserProfileComponent, UserHeaderComponent], 11 | imports: [ 12 | ArticleCardModule, 13 | LetModule, 14 | PushModule, 15 | CommonModule, 16 | RouterModule.forChild([ 17 | { 18 | path: '', 19 | component: UserProfileComponent, 20 | }, 21 | ]), 22 | ], 23 | }) 24 | export class UserProfileModule {} 25 | -------------------------------------------------------------------------------- /libs/user-profile/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/user-profile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/user-profile/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/user-profile/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/user-profile/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /libs/videos/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nrwl/nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": ["devto","app"], 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": ["devto","app"], 25 | "style": "kebab-case" 26 | } 27 | ] 28 | } 29 | }, 30 | { 31 | "files": ["*.html"], 32 | "extends": ["plugin:@nrwl/nx/angular-template"], 33 | "rules": {} 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /libs/videos/README.md: -------------------------------------------------------------------------------- 1 | # videos 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test videos` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/videos/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'videos', 3 | preset: '../../jest.preset.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | }, 10 | }, 11 | coverageDirectory: '../../coverage/libs/videos', 12 | transform: { 13 | '^.+\\.(ts|js|html)$': 'jest-preset-angular', 14 | }, 15 | snapshotSerializers: [ 16 | 'jest-preset-angular/build/serializers/no-ng-attributes', 17 | 'jest-preset-angular/build/serializers/ng-snapshot', 18 | 'jest-preset-angular/build/serializers/html-comment', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /libs/videos/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/libs/videos", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/videos/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devto/videos", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^12.1.1", 6 | "@angular/core": "^12.1.1" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libs/videos/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/videos.module'; 2 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/services/videos-list.store.spec.ts: -------------------------------------------------------------------------------- 1 | import { VideosListStore } from './videos-list.store'; 2 | 3 | describe('VideosListStore', () => { 4 | it('should create an instance', () => { 5 | expect(new VideosListStore()).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/services/videos-list.store.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ComponentStore, tapResponse } from '@ngrx/component-store'; 3 | import { Observable } from 'rxjs'; 4 | import { switchMap } from 'rxjs/operators'; 5 | import { VideosList } from '@devto/core/models'; 6 | import { VideoslistApiService } from './videoslist-api.service'; 7 | 8 | interface VideosListState { 9 | VideosList: VideosList[]; 10 | } 11 | 12 | @Injectable({ 13 | providedIn: 'root', 14 | }) 15 | export class VideosListStore extends ComponentStore { 16 | readonly VideosList$ = this.select((state) => state.VideosList); 17 | readonly setVideoslist = this.updater( 18 | (state: VideosListState, VideosList: VideosList[]) => ({ 19 | ...state, 20 | VideosList: [...state.VideosList, ...VideosList], 21 | }) 22 | ); 23 | 24 | readonly getVideoslist = this.effect( 25 | (params: Observable>) => 26 | params.pipe( 27 | switchMap((params) => 28 | this.VideoslistApiS.getVideoslist(params).pipe( 29 | tapResponse( 30 | (VideosList) => this.setVideoslist(VideosList), 31 | (error) => this.logError(error) 32 | ) 33 | ) 34 | ) 35 | ) 36 | ); 37 | constructor(private VideoslistApiS: VideoslistApiService) { 38 | super({ VideosList: [] }); 39 | } 40 | 41 | logError(error: unknown) { 42 | console.error(error); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/services/videoslist-api.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { VideoslistApiService } from './videoslist-api.service'; 4 | 5 | describe('VideoslistApiService', () => { 6 | let service: VideoslistApiService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(VideoslistApiService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/services/videoslist-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { VideosList } from '@devto/core/models'; 5 | import { environment } from '@devto/environments'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class VideoslistApiService { 11 | constructor(private http: HttpClient) {} 12 | 13 | getVideoslist(params?: Record): Observable { 14 | const newParams = new HttpParams({ fromObject: params }).toString(); 15 | return this.http.get( 16 | `${environment.baseApi}/videos?${newParams}` 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/video-card/video-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { VideoCardComponent } from './video-card.component'; 4 | 5 | describe('VideoCardComponent', () => { 6 | let component: VideoCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [VideoCardComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(VideoCardComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/video-card/video-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { VideosList } from '@devto/core/models'; 3 | @Component({ 4 | selector: 'app-video-card', 5 | template: ` `, 23 | styles: [ 24 | ` 25 | .video-collection { 26 | display: inline; 27 | 28 | .single-video-article { 29 | border: solid 1px #dbdbdb; 30 | margin: 5px; 31 | width: 100%; 32 | padding-bottom: 8px; 33 | border-radius: 3px; 34 | display: inline-block; 35 | .video-image { 36 | position: relative; 37 | padding-top: 56%; 38 | border-top-left-radius: 3px; 39 | border-top-right-radius: 3px; 40 | background: #0a0a0a no-repeat center center; 41 | -webkit-background-size: cover; 42 | -moz-background-size: cover; 43 | -o-background-size: cover; 44 | background-size: cover; 45 | } 46 | .video-timestamp { 47 | position: absolute; 48 | font-size: 0.7em; 49 | bottom: 11px; 50 | right: 4px; 51 | background-color: rgba(0, 0, 0, 0.8); 52 | color: #ffffff; 53 | padding: 2px 5px 3px; 54 | font-weight: 500; 55 | border-radius: 3px; 56 | } 57 | 58 | p { 59 | margin: 0px; 60 | padding: 2px 8px; 61 | max-height: 100%; 62 | max-width: 90%; 63 | overflow: hidden; 64 | white-space: nowrap; 65 | text-overflow: ellipsis; 66 | color: #08090a; 67 | font-size: 0.95em; 68 | } 69 | 70 | p.video-username { 71 | color: #64707d; 72 | font-size: 0.88em; 73 | } 74 | } 75 | } 76 | 77 | @media screen and (min-width: 550px) { 78 | .video-collection .single-video-article { 79 | width: 47%; 80 | } 81 | } 82 | @media screen and (min-width: 739px) { 83 | .video-collection .single-video-article { 84 | width: 31%; 85 | } 86 | } 87 | `, 88 | ], 89 | }) 90 | export class VideoCardComponent { 91 | @Input() video!: VideosList; 92 | } 93 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/videos-header/videos-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { VideosHeaderComponent } from './videos-header.component'; 4 | 5 | describe('VideosHeaderComponent', () => { 6 | let component: VideosHeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [VideosHeaderComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(VideosHeaderComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos-list/videos-header/videos-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-videos-header', 5 | template: `
6 |

DEV Community on Video

7 |
`, 8 | styles: [ 9 | ` 10 | .video-page-title { 11 | font-size: calc(0.9vw + 10px); 12 | text-align: center; 13 | } 14 | `, 15 | ], 16 | }) 17 | export class VideosHeaderComponent {} 18 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { VideosComponent } from './videos.component'; 4 | 5 | describe('VideosComponent', () => { 6 | let component: VideosComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [VideosComponent], 12 | }).compileComponents(); 13 | }); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(VideosComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { VideosListStore } from './videos-list/services/videos-list.store'; 3 | 4 | @Component({ 5 | selector: 'app-videos', 6 | template: ` 7 |
8 | 9 | 10 | 11 |
`, 12 | }) 13 | export class VideosComponent implements OnInit { 14 | page = '0'; 15 | videosList$ = this.VideosListStore.VideosList$; 16 | 17 | constructor(private VideosListStore: VideosListStore) {} 18 | 19 | ngOnInit(): void { 20 | this.VideosListStore.getVideoslist({ 21 | page: this.page, 22 | signature: '4072170', 23 | }); 24 | console.log(this.videosList$); 25 | } 26 | 27 | onScrollingFinished() { 28 | this.page = (Number(this.page) + 1).toString(); 29 | this.VideosListStore.getVideoslist({ 30 | page: this.page, 31 | signature: '4072170', 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libs/videos/src/lib/videos.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { LetModule, PushModule } from '@rx-angular/template'; 5 | import { VideosComponent } from './videos.component'; 6 | import { VideosHeaderComponent } from './videos-list/videos-header/videos-header.component'; 7 | import { VideoCardComponent } from './videos-list/video-card/video-card.component'; 8 | import { ScrollTrackerDirective } from '@devto/global-components'; 9 | @NgModule({ 10 | declarations: [ 11 | VideosComponent, 12 | VideosHeaderComponent, 13 | VideoCardComponent, 14 | ScrollTrackerDirective, 15 | ], 16 | imports: [ 17 | LetModule, 18 | PushModule, 19 | CommonModule, 20 | RouterModule.forChild([ 21 | { 22 | path: '', 23 | component: VideosComponent, 24 | }, 25 | ]), 26 | ], 27 | }) 28 | export class VideosModule {} 29 | -------------------------------------------------------------------------------- /libs/videos/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /libs/videos/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "forceConsistentCasingInFileNames": true, 15 | "strict": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true 18 | }, 19 | "angularCompilerOptions": { 20 | "strictInjectionParameters": true, 21 | "strictInputAccessModifiers": true, 22 | "strictTemplates": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libs/videos/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2018"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /libs/videos/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/videos/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../dist/out-tsc", 6 | "module": "commonjs", 7 | "types": [ 8 | "jest", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "src/test-setup.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "devto", 3 | "affected": { 4 | "defaultBase": "main" 5 | }, 6 | "implicitDependencies": { 7 | "angular.json": "*", 8 | "package.json": "*", 9 | "tslint.json": "*", 10 | ".eslintrc.json": "*", 11 | "nx.json": "*", 12 | "tsconfig.base.json": "*" 13 | }, 14 | "projects": { 15 | "article-detail": { 16 | "tags": [] 17 | }, 18 | "core-app-routes": { 19 | "tags": [] 20 | }, 21 | "core-models": { 22 | "tags": [] 23 | }, 24 | "devto": { 25 | "tags": [], 26 | "implicitDependencies": ["styles"] 27 | }, 28 | "devto-e2e": { 29 | "tags": [], 30 | "implicitDependencies": ["devto"] 31 | }, 32 | "environments": { 33 | "tags": [] 34 | }, 35 | "global-components": { 36 | "tags": [] 37 | }, 38 | "global-constants": { 39 | "tags": [] 40 | }, 41 | "global-services": { 42 | "tags": [] 43 | }, 44 | "home": { 45 | "tags": [] 46 | }, 47 | "listings": { 48 | "tags": [] 49 | }, 50 | "styles": { 51 | "tags": [] 52 | }, 53 | "user-profile": { 54 | "tags": [] 55 | }, 56 | "videos": { 57 | "tags": [] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tools/schematics/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajitsinghkaler/devto-clone/f8fa3fd2d220bc25bc6c19d420c15f3135b685d7/tools/schematics/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "sourceMap": true, 11 | "declaration": false, 12 | "downlevelIteration": true, 13 | "experimentalDecorators": true, 14 | "moduleResolution": "node", 15 | "importHelpers": true, 16 | "target": "es2017", 17 | "module": "es2020", 18 | "lib": ["es2018", "dom"], 19 | "paths": { 20 | "@devto/article-detail": ["libs/article-detail/src/index.ts"], 21 | "@devto/core/app-routes": ["libs/core/app-routes/src/index.ts"], 22 | "@devto/core/models": ["libs/core/models/src/index.ts"], 23 | "@devto/environments": ["libs/environments/src/index.ts"], 24 | "@devto/global-components": ["libs/global-components/src/index.ts"], 25 | "@devto/global-constants": ["libs/global-constants/src/index.ts"], 26 | "@devto/global-services": ["libs/global-services/src/index.ts"], 27 | "@devto/home": ["libs/home/src/index.ts"], 28 | "@devto/listings": ["libs/listings/src/index.ts"], 29 | "@devto/styles": ["libs/styles/src/index.ts"], 30 | "@devto/user-profile": ["libs/user-profile/src/index.ts"], 31 | "@devto/videos": ["libs/videos/src/index.ts"] 32 | }, 33 | "rootDir": "." 34 | }, 35 | "angularCompilerOptions": { 36 | "enableI18nLegacyMessageIdFormat": false, 37 | "strictInjectionParameters": true, 38 | "strictInputAccessModifiers": true, 39 | "strictTemplates": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "CommonJS" 5 | } 6 | } 7 | --------------------------------------------------------------------------------