├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── assets ├── README.md ├── icons │ ├── comments.svg │ ├── dev-to.svg │ ├── external-link.svg │ ├── github.svg │ ├── heart.svg │ ├── nuxt.svg │ ├── twitter.svg │ └── warning.svg └── styles │ ├── app.scss │ ├── base.scss │ ├── highlight.scss │ ├── reset.scss │ └── tokens.scss ├── components ├── CodesandboxBanner.vue ├── GithubBanner.vue ├── TheFooter.vue ├── TheHeader.vue └── blocks │ ├── ArticleBlock.vue │ ├── ArticleCardBlock.vue │ ├── AsideUsernameBlock.vue │ ├── CommentBlock.vue │ ├── CommentsBlock.vue │ ├── InlineErrorBlock.vue │ ├── UsernameArticlesBlock.vue │ └── UsernameBlock.vue ├── layouts ├── README.md ├── default.vue └── error.vue ├── nuxt.config.js ├── package.json ├── pages ├── _username │ ├── _article.vue │ └── index.vue ├── index.vue ├── t │ └── _tag.vue └── top.vue ├── plugins ├── README.md ├── vue-observe-visibility.client.js └── vue-placeholders.js ├── static ├── README.md ├── demo.gif └── favicon.ico ├── store ├── README.md └── index.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | extends: [ 11 | '@nuxtjs', 12 | 'prettier', 13 | 'prettier/vue', 14 | 'plugin:prettier/recommended', 15 | 'plugin:nuxt/recommended' 16 | ], 17 | plugins: ['prettier'], 18 | rules: {} 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "trailingComma": "none" 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dev.to clone built with NuxtJS 2 | 3 | > An articles aggregation app using [DEV.TO](https://dev.to) public [API](https://docs.dev.to/api/), demonstrating capabilities of [NuxtJS](https://nuxtjs.org) new [fetch()](https://nuxtjs.org/api/pages-fetch) 4 | 5 | See [live mode](https://quixotic-scissors.surge.sh/). 6 | 7 | [![Edit dev-to-clone-nuxt](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/bdrtsky/nuxt-dev-to-clone/tree/master/?fontsize=14&hidenavigation=1&theme=dark) 8 | 9 | ## What You’ll Learn 10 | 11 | - use `$fetchState` for showing nice placeholders while data is fetching on the client side 12 | 13 | - use `keep-alive` and `activated` hook to efficiently cache API requests on pages that have already been visited 14 | 15 | - reuse the `fetch` hook with `this.$fetch()` 16 | 17 | - set `fetchOnServer` value to control when we need to render our data on the server side or not 18 | 19 | - find a way to handle errors from `fetch` hook. 20 | 21 | ## Getting Started 22 | 23 | ```sh 24 | # clone the project 25 | git clone https://github.com/bdrtsky/nuxt-dev-to-clone.git 26 | 27 | # install dependencies 28 | npm install 29 | 30 | # start the project 31 | npm run dev 32 | 33 | # go to http://localhost:3000 34 | ``` 35 | 36 | Read full article: https://nuxtjs.org/blog/build-dev-to-clone-with-nuxt-new-fetch 37 | 38 |

39 | 40 |

41 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /assets/icons/comments.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/dev-to.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/nuxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/styles/app.scss: -------------------------------------------------------------------------------- 1 | .vue-content-placeholders-img, 2 | .vue-content-placeholders-text__line, 3 | .vue-content-placeholders-heading__img, 4 | .vue-content-placeholders-heading__title, 5 | .vue-content-placeholders-heading__subtitle { 6 | background: #bfcdec !important; 7 | } 8 | 9 | .vue-content-placeholders-is-animated 10 | .vue-content-placeholders-text__line::before, 11 | .vue-content-placeholders-is-animated .vue-content-placeholders-img::before, 12 | .vue-content-placeholders-is-animated 13 | .vue-content-placeholders-heading__img::before, 14 | .vue-content-placeholders-is-animated 15 | .vue-content-placeholders-heading__title::before, 16 | .vue-content-placeholders-is-animated 17 | .vue-content-placeholders-heading__subtitle::before { 18 | background: linear-gradient( 19 | to right, 20 | transparent 0%, 21 | #d3ddf9 15%, 22 | transparent 30% 23 | ) !important; 24 | } 25 | -------------------------------------------------------------------------------- /assets/styles/base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | font-size: 18px; 4 | -ms-overflow-style: scrollbar; 5 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 6 | -webkit-touch-callout: none; 7 | } 8 | 9 | @media (min-width: $screen-sm) { 10 | html { 11 | font-size: 20px; 12 | } 13 | } 14 | 15 | body { 16 | height: 100%; 17 | min-width: 320px; 18 | font-family: $body-font-family, $sans-serif-fallback-stack; 19 | font-weight: $body-font-weight; 20 | line-height: 1.5; 21 | color: $on-surface-color; 22 | background-color: $surface-color; 23 | -webkit-text-rendering: optimizeLegibility; 24 | text-rendering: optimizeLegibility; // Enable kerning and optional ligatures 25 | font-synthesis: none; 26 | font-kerning: normal; 27 | font-feature-settings: $body-font-feature-settings; 28 | -webkit-font-smoothing: antialiased; // Make type rendering look crisper 29 | -moz-osx-font-smoothing: grayscale; // Make type rendering look crisper 30 | -webkit-overflow-scrolling: touch; 31 | overflow-x: hidden; 32 | overflow-y: scroll; 33 | } 34 | 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6 { 41 | color: $on-surface-color; 42 | font-family: $display-font-family, $sans-serif-fallback-stack; 43 | font-weight: $display-font-weight; 44 | font-feature-settings: $display-font-feature-settings; 45 | line-height: 1.2; 46 | } 47 | -------------------------------------------------------------------------------- /assets/styles/highlight.scss: -------------------------------------------------------------------------------- 1 | pre { 2 | background: #29292e; 3 | border-radius: 2px; 4 | overflow: auto; 5 | padding: 1rem; 6 | color: #eff1f9; 7 | line-height: 1.42em; 8 | font-size: 13px; 9 | // width: calc(100% - 25px); 10 | } 11 | @media screen and (min-width: 380px) { 12 | pre { 13 | font-size: 15px; 14 | } 15 | } 16 | pre code { 17 | background: #29292e; 18 | color: #eff0f9; 19 | white-space: pre; 20 | } 21 | 22 | div.highlight pre.highlight code { 23 | font-size: inherit; 24 | padding: 0px; 25 | } 26 | 27 | div.inner-comment div.body div.highlight pre.highlight { 28 | background: #29292e; 29 | } 30 | 31 | div.inner-comment div.body div.highlight pre.highlight code { 32 | font-size: inherit; 33 | white-space: inherit; 34 | background: inherit; 35 | color: inherit; 36 | } 37 | 38 | .highlight .hll { 39 | background-color: #49483e; 40 | } 41 | 42 | .highlight { 43 | background: #29292e; 44 | color: #f8f8f2; 45 | } 46 | 47 | .highlight .c { 48 | color: #808080; 49 | } 50 | 51 | .highlight .err { 52 | text-shadow: 0 0 7px #f9690e; 53 | } 54 | 55 | .highlight .k { 56 | color: #f39c12; 57 | } 58 | 59 | .highlight .l { 60 | color: #dda0dd; 61 | } 62 | 63 | .highlight .n { 64 | color: #f8f8f2; 65 | } 66 | 67 | .highlight .o { 68 | color: #f9690e; 69 | } 70 | 71 | .highlight .p { 72 | color: #f8f8f2; 73 | } 74 | 75 | .highlight .ch { 76 | color: #808080; 77 | } 78 | 79 | .highlight .cm { 80 | color: #808080; 81 | } 82 | 83 | .highlight .cp { 84 | color: #808080; 85 | } 86 | 87 | .highlight .cpf { 88 | color: #808080; 89 | } 90 | 91 | .highlight .c1 { 92 | color: #808080; 93 | } 94 | 95 | .highlight .cs { 96 | color: #808080; 97 | } 98 | 99 | .highlight .gd { 100 | color: #f9690e; 101 | } 102 | 103 | .highlight .ge { 104 | font-style: italic; 105 | } 106 | 107 | .highlight .gi { 108 | color: #7ed07e; 109 | } 110 | 111 | .highlight .gs { 112 | font-weight: bold; 113 | } 114 | 115 | .highlight .gu { 116 | color: #808080; 117 | } 118 | 119 | .highlight .kc { 120 | color: #f39c12; 121 | } 122 | 123 | .highlight .kd { 124 | color: #f39c12; 125 | } 126 | 127 | .highlight .kn { 128 | color: #f9690e; 129 | } 130 | 131 | .highlight .kp { 132 | color: #f39c12; 133 | } 134 | 135 | .highlight .kr { 136 | color: #f39c12; 137 | } 138 | 139 | .highlight .kt { 140 | color: #f39c12; 141 | } 142 | 143 | .highlight .ld { 144 | color: #f2ca27; 145 | } 146 | 147 | .highlight .m { 148 | color: #dda0dd; 149 | } 150 | 151 | .highlight .s { 152 | color: #f2ca27; 153 | } 154 | 155 | .highlight .na { 156 | color: #7ed07e; 157 | } 158 | 159 | .highlight .nb { 160 | color: #f8f8f2; 161 | } 162 | 163 | .highlight .nc { 164 | color: #7ed07e; 165 | } 166 | 167 | .highlight .no { 168 | color: #f39c12; 169 | } 170 | 171 | .highlight .nd { 172 | color: #7ed07e; 173 | } 174 | 175 | .highlight .ni { 176 | color: #f8f8f2; 177 | } 178 | 179 | .highlight .ne { 180 | color: #7ed07e; 181 | } 182 | 183 | .highlight .nf { 184 | color: #7ed07e; 185 | } 186 | 187 | .highlight .nl { 188 | color: #f8f8f2; 189 | } 190 | 191 | .highlight .nn { 192 | color: #f8f8f2; 193 | } 194 | 195 | .highlight .nx { 196 | color: #7ed07e; 197 | } 198 | 199 | .highlight .py { 200 | color: #f8f8f2; 201 | } 202 | 203 | .highlight .nt { 204 | color: #f9690e; 205 | } 206 | 207 | .highlight .nv { 208 | color: #f8f8f2; 209 | } 210 | 211 | .highlight .ow { 212 | color: #f9690e; 213 | } 214 | 215 | .highlight .w { 216 | color: #f8f8f2; 217 | } 218 | 219 | .highlight .mb { 220 | color: #dda0dd; 221 | } 222 | 223 | .highlight .mf { 224 | color: #dda0dd; 225 | } 226 | 227 | .highlight .mh { 228 | color: #dda0dd; 229 | } 230 | 231 | .highlight .mi { 232 | color: #dda0dd; 233 | } 234 | 235 | .highlight .mo { 236 | color: #dda0dd; 237 | } 238 | 239 | .highlight .sa { 240 | color: #f2ca27; 241 | } 242 | 243 | .highlight .sb { 244 | color: #f2ca27; 245 | } 246 | 247 | .highlight .sc { 248 | color: #f2ca27; 249 | } 250 | 251 | .highlight .dl { 252 | color: #f2ca27; 253 | } 254 | 255 | .highlight .sd { 256 | color: #f2ca27; 257 | } 258 | 259 | .highlight .s2 { 260 | color: #f2ca27; 261 | } 262 | 263 | .highlight .se { 264 | color: #dda0dd; 265 | } 266 | 267 | .highlight .sh { 268 | color: #f2ca27; 269 | } 270 | 271 | .highlight .si { 272 | color: #f2ca27; 273 | } 274 | 275 | .highlight .sx { 276 | color: #f2ca27; 277 | } 278 | 279 | .highlight .sr { 280 | color: #f2ca27; 281 | } 282 | 283 | .highlight .s1 { 284 | color: #f2ca27; 285 | } 286 | 287 | .highlight .ss { 288 | color: #f2ca27; 289 | } 290 | 291 | .highlight .bp { 292 | color: #f8f8f2; 293 | } 294 | 295 | .highlight .fm { 296 | color: #7ed07e; 297 | } 298 | 299 | .highlight .vc { 300 | color: #f8f8f2; 301 | } 302 | 303 | .highlight .vg { 304 | color: #f8f8f2; 305 | } 306 | 307 | .highlight .vi { 308 | color: #f8f8f2; 309 | } 310 | 311 | .highlight .vm { 312 | color: #f8f8f2; 313 | } 314 | 315 | .highlight .il { 316 | color: #dda0dd; 317 | } 318 | -------------------------------------------------------------------------------- /assets/styles/reset.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Manually forked from SUIT CSS Base: https://github.com/suitcss/base 3 | * A thin layer on top of normalize.css that provides a starting point more 4 | * suitable for web applications. 5 | */ 6 | 7 | /** 8 | * 1. Prevent padding and border from affecting element width 9 | * https://goo.gl/pYtbK7 10 | * 2. Change the default font family in all browsers (opinionated) 11 | */ 12 | 13 | html { 14 | box-sizing: border-box; /* 1 */ 15 | font-family: sans-serif; /* 2 */ 16 | -ms-text-size-adjust: 100%; // Disable auto-enlargement of small text in Safari 17 | -webkit-text-size-adjust: 100%; // Disable auto-enlargement of small text in Safari 18 | } 19 | 20 | *, 21 | *::before, 22 | *::after { 23 | box-sizing: inherit; 24 | } 25 | 26 | /** 27 | * 1. Use the system font stack as a sane default. 28 | * 2. Use Tailwind's default "normal" line-height so the user isn't forced 29 | * to override it to ensure consistency even when using the default theme. 30 | */ 31 | 32 | html { 33 | font-family: $sans-serif-fallback-stack; /* 1 */ 34 | line-height: 1.5; /* 2 */ 35 | } 36 | 37 | /** 38 | * Allow adding a border to an element by just adding a border-width. 39 | * 40 | * By default, the way the browser specifies that an element should have no 41 | * border is by setting it's border-style to `none` in the user-agent 42 | * stylesheet. 43 | * 44 | * In order to easily add borders to elements by just setting the `border-width` 45 | * property, we change the default border-style for all elements to `solid`, and 46 | * use border-width to hide them instead. This way our `border` utilities only 47 | * need to set the `border-width` property instead of the entire `border` 48 | * shorthand, making our border utilities much more straightforward to compose. 49 | * 50 | * https://github.com/tailwindcss/tailwindcss/pull/116 51 | */ 52 | *, 53 | *::before, 54 | *::after { 55 | border-width: 0; 56 | border-style: solid; 57 | border-color: #e0e0e0; 58 | } 59 | 60 | body { 61 | margin: 0; 62 | } 63 | 64 | /** 65 | * Removes the default spacing and border for appropriate elements. 66 | */ 67 | 68 | blockquote, 69 | dl, 70 | dd, 71 | h1, 72 | h2, 73 | h3, 74 | h4, 75 | h5, 76 | h6, 77 | figure, 78 | p, 79 | pre { 80 | margin: 0; 81 | } 82 | 83 | button { 84 | background: transparent; 85 | padding: 0; 86 | } 87 | 88 | /** 89 | * Work around a Firefox/IE bug where the transparent `button` background 90 | * results in a loss of the default `button` focus styles. 91 | */ 92 | 93 | button:focus { 94 | outline: 1px dotted; 95 | outline: 5px auto -webkit-focus-ring-color; 96 | } 97 | 98 | fieldset { 99 | margin: 0; 100 | padding: 0; 101 | } 102 | 103 | ol, 104 | ul { 105 | list-style: none; 106 | margin: 0; 107 | padding: 0; 108 | } 109 | 110 | /* 111 | * Ensure horizontal rules are visible by default 112 | */ 113 | hr { 114 | border-width: 1px; 115 | } 116 | 117 | /** 118 | * Undo the `border-style: none` reset that Normalize applies to images so that 119 | * our `border-{width}` utilities have the expected effect. 120 | * 121 | * The Normalize reset is unnecessary for us since we default the border-width 122 | * to 0 on all elements. 123 | * 124 | * https://github.com/tailwindcss/tailwindcss/issues/362 125 | */ 126 | img { 127 | border-style: solid; 128 | } 129 | 130 | textarea { 131 | resize: vertical; 132 | } 133 | 134 | input::placeholder, 135 | textarea::placeholder { 136 | color: inherit; 137 | opacity: 0.5; 138 | } 139 | 140 | button, 141 | [role='button'] { 142 | cursor: pointer; 143 | } 144 | 145 | table { 146 | border-collapse: collapse; 147 | } 148 | 149 | h1, 150 | h2, 151 | h3, 152 | h4, 153 | h5, 154 | h6 { 155 | font-size: inherit; 156 | font-weight: inherit; 157 | font-family: sans-serif; 158 | } 159 | 160 | /** 161 | * Reset links to optimize for opt-in styling instead of 162 | * opt-out. 163 | */ 164 | 165 | a { 166 | color: inherit; 167 | text-decoration: inherit; 168 | } 169 | 170 | /** 171 | * Reset form element properties that are easy to forget to 172 | * style explicitly so you don't inadvertently introduce 173 | * styles that deviate from your design system. These styles 174 | * supplement a partial reset that is already applied by 175 | * normalize.css. 176 | */ 177 | 178 | button, 179 | input, 180 | optgroup, 181 | select, 182 | textarea { 183 | padding: 0; 184 | line-height: inherit; 185 | color: inherit; 186 | font-family: inherit; 187 | font-size: 100%; 188 | } 189 | 190 | /** 191 | * Use the configured 'mono' font family for elements that 192 | * are expected to be rendered with a monospace font, falling 193 | * back to the system monospace stack if there is no configured 194 | * 'mono' font family. 195 | */ 196 | 197 | pre, 198 | code, 199 | kbd, 200 | samp { 201 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 202 | 'Courier New', monospace; 203 | overflow: auto; 204 | word-break: break-word; 205 | white-space: normal; 206 | } 207 | 208 | /** 209 | * Make replaced elements `display: block` by default as that's 210 | * the behavior you want almost all of the time. Inspired by 211 | * CSS Remedy, with `svg` added as well. 212 | * 213 | * https://github.com/mozdevs/cssremedy/issues/14 214 | */ 215 | 216 | img, 217 | svg, 218 | video, 219 | canvas, 220 | audio, 221 | iframe, 222 | embed, 223 | object { 224 | display: block; 225 | vertical-align: middle; 226 | } 227 | 228 | /** 229 | * Constrain images and videos to the parent width and preserve 230 | * their instrinsic aspect ratio. 231 | * 232 | * https://github.com/mozdevs/cssremedy/issues/14 233 | */ 234 | 235 | img, 236 | video { 237 | max-width: 100%; 238 | height: auto; 239 | } 240 | -------------------------------------------------------------------------------- /assets/styles/tokens.scss: -------------------------------------------------------------------------------- 1 | $body-font-family: 'Inter'; 2 | $body-font-weight: 400; 3 | $bold-body-font-weight: 500; 4 | $body-font-feature-settings: 'normal'; 5 | $display-font-family: 'Inter'; 6 | $display-font-weight: 600; 7 | $display-font-feature-settings: 'normal'; 8 | $primary-color: #6e87d2; 9 | $primary-dark: #d4dfe8; 10 | $surface-color: #eff4f7; 11 | $on-surface-color: #000000; 12 | $hovered-surface-color: linear-gradient( 13 | 135deg, 14 | rgba(0, 0, 0, 0.09), 15 | rgba(255, 255, 255, 0) 16 | ); 17 | $elevated-surface-color: #dfe8ef; 18 | $on-elevated-surface-color: yellow; 19 | $gray-color: #999999; 20 | $shadow: -9px -9px 16px #f8fafe, 9px 9px 16px #ced2db; 21 | $inner-shadow: inset -9px -9px 16px #f0f3f9, inset 9px 9px 16px #ced2db; 22 | $small-shadow: -4px -4px 8px #f8fafe, 4px 4px 8px #ced2db; 23 | $small-inner-shadow: inset -4px -4px 8px #f0f3f9, inset 4px 4px 8px #ced2db, 24 | inset -1px -1px 4px #8e8e8e; 25 | $screen-sm: 640px; 26 | $screen-md: 834px; 27 | $screen-lg: 1024px; 28 | $screen-xl: 1280px; 29 | $text-ssss: 0.333333rem; 30 | $text-sss: 0.5rem; 31 | $text-ss: 0.666666rem; 32 | $text-xs: 0.75rem; 33 | $text-sm: 0.875rem; 34 | $text-base: 1rem; 35 | $text-lg: 1.125rem; 36 | $text-xl: 1.25rem; 37 | $text-2xl: 1.5rem; 38 | $text-3xl: 1.875rem; 39 | $text-4xl: 2.25rem; 40 | $-ls5: 5 * -0.0125rem; 41 | $-ls4: 4 * -0.0125rem; 42 | $-ls3: 3 * -0.0125rem; 43 | $-ls2: 2 * -0.0125rem; 44 | $-ls1: -0.0125rem; 45 | $ls1: 0.0125rem; 46 | $ls2: 2 * 0.0125rem; 47 | $ls3: 3 * 0.0125rem; 48 | $ls4: 4 * 0.0125rem; 49 | $ls5: 5 * 0.0125rem; 50 | $sans-serif-fallback-stack: -apple-system, BlinkMacSystemFont, 'Segoe UI', 51 | Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 52 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 53 | -------------------------------------------------------------------------------- /components/CodesandboxBanner.vue: -------------------------------------------------------------------------------- 1 | 161 | 162 | 170 | -------------------------------------------------------------------------------- /components/GithubBanner.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 64 | -------------------------------------------------------------------------------- /components/TheFooter.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | 56 | -------------------------------------------------------------------------------- /components/TheHeader.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 41 | 42 | 89 | -------------------------------------------------------------------------------- /components/blocks/ArticleBlock.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 104 | 105 | 246 | -------------------------------------------------------------------------------- /components/blocks/ArticleCardBlock.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 70 | 71 | 145 | -------------------------------------------------------------------------------- /components/blocks/AsideUsernameBlock.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 107 | 108 | 186 | -------------------------------------------------------------------------------- /components/blocks/CommentBlock.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 78 | 79 | 177 | -------------------------------------------------------------------------------- /components/blocks/CommentsBlock.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 50 | 51 | 72 | -------------------------------------------------------------------------------- /components/blocks/InlineErrorBlock.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | 24 | 42 | -------------------------------------------------------------------------------- /components/blocks/UsernameArticlesBlock.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 56 | 57 | 73 | -------------------------------------------------------------------------------- /components/blocks/UsernameBlock.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 118 | 119 | 248 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 29 | 61 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mode: 'universal', 3 | head: { 4 | title: 'Dev.to clone with NuxtJS', 5 | meta: [ 6 | { charset: 'utf-8' }, 7 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 8 | { 9 | hid: 'description', 10 | name: 'description', 11 | content: 'Building a dev.to clone with Nuxt.js and new fetch() hook' 12 | } 13 | ], 14 | link: [ 15 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 16 | { 17 | rel: 'stylesheet', 18 | href: 19 | 'https://fonts.googleapis.com/css?family=Inter:400,500,600&display=swap' 20 | } 21 | ] 22 | }, 23 | loading: false, // disable loading bar 24 | css: [ 25 | '~/assets/styles/reset.scss', 26 | '~/assets/styles/base.scss', 27 | '~/assets/styles/highlight.scss', 28 | '~/assets/styles/app.scss' 29 | ], 30 | styleResources: { 31 | scss: ['~/assets/styles/tokens.scss'] 32 | }, 33 | plugins: [ 34 | '~/plugins/vue-placeholders.js', 35 | '~/plugins/vue-observe-visibility.client.js' 36 | ], 37 | buildModules: [ 38 | '@nuxtjs/eslint-module', 39 | '@nuxtjs/svg', 40 | '@nuxtjs/style-resources' 41 | ], 42 | modules: ['nuxt-ackee'], 43 | ackee: { 44 | server: 'https://ackee.nuxtjs.com', 45 | domainId: '6336379b-8d3e-4069-9d2e-897be6a7ed4e' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-dev-to-clone", 3 | "version": "1.0.0", 4 | "description": "Dev.to clone with NuxtJS", 5 | "author": "Sergey Bedritsky", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate", 12 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore ." 13 | }, 14 | "dependencies": { 15 | "nuxt": "^2.12.2", 16 | "nuxt-ackee": "^2.0.0", 17 | "vue-content-placeholders": "^0.2.1", 18 | "vue-observe-visibility": "^0.4.6" 19 | }, 20 | "devDependencies": { 21 | "@nuxtjs/eslint-config": "^3.0.0", 22 | "@nuxtjs/eslint-module": "^2.0.0", 23 | "@nuxtjs/style-resources": "^1.0.0", 24 | "@nuxtjs/svg": "^0.1.11", 25 | "babel-eslint": "^10.0.1", 26 | "eslint": "^7.0.0", 27 | "eslint-config-prettier": "^6.11.0", 28 | "eslint-plugin-nuxt": ">=1.0.0", 29 | "eslint-plugin-prettier": "^3.1.3", 30 | "prettier": "^2.0.5", 31 | "sass": "^1.26.5", 32 | "sass-loader": "^8.0.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pages/_username/_article.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | 27 | 75 | -------------------------------------------------------------------------------- /pages/_username/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | 20 | 27 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 87 | 88 | 114 | -------------------------------------------------------------------------------- /pages/t/_tag.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 101 | 102 | 126 | -------------------------------------------------------------------------------- /pages/top.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 87 | 88 | 113 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /plugins/vue-observe-visibility.client.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueObserveVisibility from 'vue-observe-visibility' 3 | 4 | Vue.use(VueObserveVisibility) 5 | -------------------------------------------------------------------------------- /plugins/vue-placeholders.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueContentPlaceholders from 'vue-content-placeholders' 3 | 4 | Vue.use(VueContentPlaceholders) 5 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /static/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdrtsky/nuxt-dev-to-clone/9829ed118f9bfe319cbc979423a5eef313c2094f/static/demo.gif -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdrtsky/nuxt-dev-to-clone/9829ed118f9bfe319cbc979423a5eef313c2094f/static/favicon.ico -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export const state = () => ({ 7 | currentArticle: null 8 | }) 9 | 10 | export const mutations = { 11 | SET_CURRENT_ARTICLE(state, article) { 12 | state.currentArticle = article 13 | } 14 | } 15 | --------------------------------------------------------------------------------