├── static ├── og.png ├── icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png └── fonts │ ├── noto-serif.woff │ ├── noto-serif.woff2 │ ├── YakuHanMP-Medium.woff │ └── YakuHanMP-Medium.woff2 ├── lib └── helpers.js ├── .gitignore ├── jest-puppeteer.config.js ├── config └── constant.js ├── jest.config.js ├── .editorconfig ├── test └── e2e │ └── index.spec.js ├── components ├── LayoutInside.vue ├── TagList.vue ├── ButtonMore.vue ├── PostList.vue ├── PostContainer.vue └── DummyPostList.vue ├── pages ├── error │ └── 404.vue ├── index.vue ├── tag │ └── _id.vue └── entry │ └── _id.vue ├── assets ├── scss │ ├── base │ │ ├── _font.scss │ │ └── _reset.scss │ ├── index.scss │ └── foundation │ │ ├── _variables.scss │ │ └── _mixin.scss └── images │ └── logo.svg ├── layouts ├── partial │ ├── TheError.vue │ ├── TheMain.vue │ ├── TheHeader.vue │ └── TheFooter.vue └── default.vue ├── README.md ├── .eslintrc.js ├── api └── index.js ├── .circleci └── config.yml ├── package.json ├── nuxt.config.js └── store └── index.js /static/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/og.png -------------------------------------------------------------------------------- /static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/icon.png -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | export const uniq = arr => arr.filter((val, i, self) => self.indexOf(val) === i) 2 | -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/favicon-32x32.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | sw.* 3 | .env 4 | .nuxt 5 | node_modules/ 6 | dist/ 7 | coverage/ 8 | static/sitemap.xml 9 | -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/fonts/noto-serif.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/fonts/noto-serif.woff -------------------------------------------------------------------------------- /static/fonts/noto-serif.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/fonts/noto-serif.woff2 -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: { 3 | command: 'yarn serve', 4 | port: 8888 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /static/fonts/YakuHanMP-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/fonts/YakuHanMP-Medium.woff -------------------------------------------------------------------------------- /static/fonts/YakuHanMP-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishinoshake/houga-blog/HEAD/static/fonts/YakuHanMP-Medium.woff2 -------------------------------------------------------------------------------- /config/constant.js: -------------------------------------------------------------------------------- 1 | export const CONTENT_TYPE_POST = 'post' 2 | export const CONTENT_TYPE_TAG = 'tag' 3 | export const POSTS_PER_PAGE = 30 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | testURL: 'http://localhost/', 4 | preset: 'jest-puppeteer', 5 | moduleNameMapper: { 6 | '^@/(.*)$': '/$1' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /test/e2e/index.spec.js: -------------------------------------------------------------------------------- 1 | const buildUrl = path => `http://localhost:8888${path}` 2 | 3 | // 雑 4 | test('トップページにエラーがない', async () => { 5 | const p = await browser.newPage() 6 | 7 | await p.goto(buildUrl('/')) 8 | 9 | const error = await p.$('.error') 10 | 11 | expect(error).toBeNull() 12 | 13 | p.close() 14 | }) 15 | -------------------------------------------------------------------------------- /components/LayoutInside.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /pages/error/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /assets/scss/base/_font.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | @font-face { 4 | font-family: YakuHanMP; 5 | font-style: normal; 6 | font-weight: normal; 7 | src: url(/fonts/YakuHanMP-Medium.woff2) format('woff2'), url(/fonts/YakuHanMP-Medium.woff) format('woff'); 8 | } 9 | 10 | @font-face { 11 | font-family: 'noto-serif'; 12 | font-style: normal; 13 | font-weight: normal; 14 | src: url(/fonts/noto-serif.woff2) format('woff2'), url(/fonts/noto-serif.woff) format('woff'); 15 | } 16 | -------------------------------------------------------------------------------- /assets/scss/index.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // ========================================================================== 4 | // variables & mixin 5 | // ========================================================================== 6 | @import 'foundation/variables'; 7 | @import 'foundation/mixin'; 8 | 9 | // ========================================================================== 10 | // base 11 | // ========================================================================== 12 | @import 'base/reset'; 13 | @import 'base/font'; 14 | -------------------------------------------------------------------------------- /layouts/partial/TheError.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 邦画だってさ 2 | 3 | [Contentful](https://www.contentful.com)と[Nuxt.js](https://ja.nuxtjs.org)で構築したブログです。 4 | ブログを作って満足してしまったので、記事はまだ全部(仮)です。 5 | 6 | https://blog.houga.cc 7 | 8 | ## 使い方 9 | Contentfulのアクセストークンを環境変数に設定しないと動きませんが参考までに。 10 | 11 | ``` 12 | # インストール 13 | yarn 14 | 15 | # 開発 16 | yarn dev 17 | 18 | # 静的ファイル生成 19 | yarn generate 20 | ``` 21 | 22 | ## インフラ構成 23 | Nuxt.jsで静的に生成したファイルをS3へホスティングして、手前にCloudFrontを置いています。 24 | 25 | ## ご意見・ご要望 26 | ブログに関するご意見・ご要望がありましたら、issueにあげていただくか、下記のメールまでご連絡をお願いいたします。 27 | 28 | lawson.and.7.11@gmail.com 29 | -------------------------------------------------------------------------------- /.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 | globals: { 11 | test: true, 12 | browser: true, 13 | expect: true 14 | }, 15 | extends: [ 16 | 'standard', 17 | 'plugin:vue/essential', 18 | 'plugin:prettier/recommended' 19 | ], 20 | plugins: [ 21 | 'vue', 22 | ], 23 | rules: { 24 | 'no-new': 'off', 25 | 'prettier/prettier': ['error', { 26 | 'singleQuote': true, 27 | 'semi': false, 28 | 'printWidth': 120 29 | }] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /components/TagList.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 34 | -------------------------------------------------------------------------------- /components/ButtonMore.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'contentful' 2 | import { CONTENT_TYPE_POST, CONTENT_TYPE_TAG, POSTS_PER_PAGE } from '@/config/constant' 3 | 4 | const client = createClient({ 5 | space: process.env.SPACE_ID, 6 | accessToken: process.env.ACCESS_TOKEN 7 | }) 8 | 9 | export const fetchPosts = ({ page, tagId = null }) => { 10 | let param = { 11 | content_type: CONTENT_TYPE_POST, 12 | order: '-fields.releaseDate', 13 | skip: (page - 1) * POSTS_PER_PAGE, 14 | limit: POSTS_PER_PAGE 15 | } 16 | 17 | if (tagId) { 18 | param['fields.tags.sys.id'] = tagId 19 | } 20 | 21 | return client.getEntries(param) 22 | } 23 | 24 | export const fetchPost = id => client.getEntry(id) 25 | 26 | export const fetchTags = () => 27 | client.getEntries({ 28 | content_type: CONTENT_TYPE_TAG 29 | }) 30 | -------------------------------------------------------------------------------- /layouts/partial/TheMain.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 29 | 41 | -------------------------------------------------------------------------------- /components/PostList.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 43 | -------------------------------------------------------------------------------- /layouts/partial/TheHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 49 | -------------------------------------------------------------------------------- /components/PostContainer.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | -------------------------------------------------------------------------------- /assets/scss/base/_reset.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body, 6 | div, 7 | dl, 8 | dt, 9 | dd, 10 | ul, 11 | ol, 12 | li, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | pre, 20 | code, 21 | form, 22 | fieldset, 23 | legend, 24 | input, 25 | textarea, 26 | p, 27 | blockquote, 28 | th, 29 | td, 30 | button, 31 | figure, 32 | figcaption { 33 | margin: 0; 34 | padding: 0; 35 | } 36 | 37 | main { 38 | display: block; 39 | } 40 | 41 | table { 42 | border-collapse: collapse; 43 | border-spacing: 0; 44 | } 45 | 46 | fieldset, 47 | img { 48 | border: 0; 49 | } 50 | 51 | button { 52 | border: 0; 53 | outline: none; 54 | background-color: transparent; 55 | box-shadow: none; 56 | cursor: pointer; 57 | } 58 | 59 | address, 60 | caption, 61 | cite, 62 | code, 63 | dfn, 64 | em, 65 | strong, 66 | th, 67 | var { 68 | font-style: normal; 69 | font-weight: normal; 70 | } 71 | 72 | ol, 73 | ul { 74 | list-style: none; 75 | } 76 | 77 | h1, 78 | h2, 79 | h3, 80 | h4, 81 | h5, 82 | h6 { 83 | font-size: 100%; 84 | font-weight: normal; 85 | } 86 | 87 | a { 88 | color: inherit; 89 | text-decoration: none; 90 | } 91 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | 30 | 48 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | working_directory: ~/app 6 | docker: 7 | - image: circleci/python:3.6-jessie-node-browsers 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | name: Restore Yarn Package Cache 12 | keys: 13 | - yarn-packages-{{ checksum "yarn.lock" }} 14 | - run: yarn install 15 | - save_cache: 16 | name: Save Yarn Package Cache 17 | key: yarn-packages-{{ checksum "yarn.lock" }} 18 | paths: 19 | - ~/.cache/yarn 20 | - run: yarn generate 21 | - run: yarn test 22 | - run: sudo pip install awscli 23 | - run: aws s3 sync ./dist/ s3://blog.houga.cc --exact-timestamps --delete --exclude "*.html" --cache-control max-age=31536000 24 | - run: aws s3 sync ./dist/ s3://blog.houga.cc --exact-timestamps --delete --exclude "*" --include "*.html" --cache-control no-store 25 | - run: aws cloudfront create-invalidation --distribution-id $CF_DIST_ID --paths "/*" 26 | 27 | workflows: 28 | version: 2 29 | build-deploy: 30 | jobs: 31 | - build: 32 | filters: 33 | branches: 34 | only: master 35 | -------------------------------------------------------------------------------- /components/DummyPostList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 51 | -------------------------------------------------------------------------------- /layouts/partial/TheFooter.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 56 | -------------------------------------------------------------------------------- /pages/tag/_id.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 68 | 69 | 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "houga-blog", 3 | "version": "1.0.0", 4 | "description": "My first-rate Nuxt.js project", 5 | "author": "noplan1989", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "analyze": "nuxt build --analyze", 11 | "generate": "nuxt generate", 12 | "serve": "http-server ./dist -p 8888", 13 | "test": "jest", 14 | "format": "eslint --fix --ext .js,.vue --ignore-path .gitignore .", 15 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", 16 | "precommit": "lint-staged" 17 | }, 18 | "lint-staged": { 19 | "*.{js,vue}": [ 20 | "eslint --fix --ext .js,.vue --ignore-path .gitignore .", 21 | "git add" 22 | ] 23 | }, 24 | "dependencies": { 25 | "@nuxtjs/dotenv": "^1.4.1", 26 | "@nuxtjs/google-analytics": "^2.2.3", 27 | "@nuxtjs/pwa": "^3.0.0-beta.19", 28 | "@nuxtjs/sitemap": "^2.0.1", 29 | "@nuxtjs/style-resources": "^1.0.0", 30 | "contentful": "^7.13.1", 31 | "cross-env": "^7.0.0", 32 | "markdown-it": "^10.0.0", 33 | "nuxt": "^2.11.0" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.8.4", 37 | "babel-eslint": "^10.0.3", 38 | "babel-jest": "^25.1.0", 39 | "eslint": "^6.8.0", 40 | "eslint-config-prettier": "^6.10.0", 41 | "eslint-config-standard": "^14.1.0", 42 | "eslint-plugin-import": "^2.20.0", 43 | "eslint-plugin-node": "^11.0.0", 44 | "eslint-plugin-prettier": "^3.1.2", 45 | "eslint-plugin-promise": "^4.2.1", 46 | "eslint-plugin-standard": "^4.0.1", 47 | "eslint-plugin-vue": "^6.1.2", 48 | "http-server": "^0.12.1", 49 | "husky": "^4.2.1", 50 | "jest": "^25.1.0", 51 | "jest-puppeteer": "^4.4.0", 52 | "lint-staged": "^10.0.6", 53 | "node-sass": "^4.13.1", 54 | "nodemon": "^2.0.2", 55 | "prettier": "^1.19.1", 56 | "puppeteer": "^2.1.0", 57 | "sass-loader": "^8.0.2" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /assets/scss/foundation/_variables.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // ----------------------------------------------------- 4 | // color 5 | // ----------------------------------------------------- 6 | $color-black: #000; 7 | $color-dark: #111; 8 | $color-font: #2a2a2a; 9 | $color-modest: #666; 10 | $color-link: #1976d2; 11 | $color-link-hover: lighten($color-link, 20%); 12 | $color-error: #e05243; 13 | $color-gray: #eee; 14 | $color-snow: #f1f1f1; 15 | $color-white: #fff; 16 | 17 | // ----------------------------------------------------- 18 | // size 19 | // ----------------------------------------------------- 20 | $mq-max: 956px; 21 | $mq-min: $mq-max+1; 22 | 23 | // ----------------------------------------------------- 24 | // easing 25 | // ----------------------------------------------------- 26 | $easeInSine: cubic-bezier(0.47, 0, 0.745, 0.715); 27 | $easeOutSine: cubic-bezier(0.39, 0.575, 0.565, 1); 28 | $easeInOutSine: cubic-bezier(0.445, 0.05, 0.55, 0.95); 29 | $easeInQuad: cubic-bezier(0.55, 0.085, 0.68, 0.53); 30 | $easeOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94); 31 | $easeInOutQuad: cubic-bezier(0.455, 0.03, 0.515, 0.955); 32 | $easeInCubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); 33 | $easeOutCubic: cubic-bezier(0.215, 0.61, 0.355, 1); 34 | $easeInOutCubic: cubic-bezier(0.645, 0.045, 0.355, 1); 35 | $easeInQuart: cubic-bezier(0.895, 0.03, 0.685, 0.22); 36 | $easeOutQuart: cubic-bezier(0.165, 0.84, 0.44, 1); 37 | $easeInOutQuart: cubic-bezier(0.77, 0, 0.175, 1); 38 | $easeInQuint: cubic-bezier(0.755, 0.05, 0.855, 0.06); 39 | $easeOutQuint: cubic-bezier(0.23, 1, 0.32, 1); 40 | $easeInOutQuint: cubic-bezier(0.86, 0, 0.07, 1); 41 | $easeInExpo: cubic-bezier(0.95, 0.05, 0.795, 0.035); 42 | $easeOutExpo: cubic-bezier(0.19, 1, 0.22, 1); 43 | $easeInOutExpo: cubic-bezier(1, 0, 0, 1); 44 | $easeInCirc: cubic-bezier(0.6, 0.04, 0.98, 0.335); 45 | $easeOutCirc: cubic-bezier(0.075, 0.82, 0.165, 1); 46 | $easeInOutCirc: cubic-bezier(0.785, 0.135, 0.15, 0.86); 47 | $easeInBack: cubic-bezier(0.6, -0.28, 0.735, 0.045); 48 | $easeOutBack: cubic-bezier(0.175, 0.885, 0.32, 1.275); 49 | $easeInOutBack: cubic-bezier(0.68, -0.55, 0.265, 1.55); 50 | -------------------------------------------------------------------------------- /assets/scss/foundation/_mixin.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // ----------------------------------------------------- 4 | // Media Query 5 | // ----------------------------------------------------- 6 | @mixin max($width: $mq-max) { 7 | @media only screen and (max-width: $width) { 8 | @content; 9 | } 10 | } 11 | 12 | @mixin between($width-from: $mq-min, $width-to: $width-border-change) { 13 | @media only screen and (min-width: $width-from) and (max-width: $width-to) { 14 | @content; 15 | } 16 | } 17 | 18 | @mixin min($width: $mq-min) { 19 | @media only screen and (min-width: $width) { 20 | @content; 21 | } 22 | } 23 | 24 | // ----------------------------------------------------- 25 | // Position 26 | // ----------------------------------------------------- 27 | @mixin fit-full($position: absolute) { 28 | width: 100%; 29 | height: 100%; 30 | position: $position; 31 | top: 0; 32 | left: 0; 33 | } 34 | 35 | @mixin center($position: absolute) { 36 | position: $position; 37 | top: 50%; 38 | left: 50%; 39 | transform: translate(-50%, -50%); 40 | } 41 | 42 | @mixin center-vertical($position: absolute) { 43 | position: $position; 44 | top: 50%; 45 | transform: translateY(-50%); 46 | } 47 | 48 | @mixin center-horizontal($position: absolute) { 49 | position: $position; 50 | left: 50%; 51 | transform: translateX(-50%); 52 | } 53 | 54 | @mixin center-flex() { 55 | display: flex; 56 | justify-content: center; 57 | align-items: center; 58 | } 59 | 60 | // ----------------------------------------------------- 61 | // Background 62 | // ----------------------------------------------------- 63 | @mixin bg-cover { 64 | background-position: center center; 65 | background-repeat: no-repeat; 66 | background-size: cover; 67 | } 68 | 69 | @mixin bg-contain { 70 | background-position: center center; 71 | background-repeat: no-repeat; 72 | background-size: contain; 73 | } 74 | 75 | // ----------------------------------------------------- 76 | // Text 77 | // ----------------------------------------------------- 78 | @mixin hide-text { 79 | font: 0 / 0; 80 | color: transparent; 81 | } 82 | 83 | @mixin font-serif { 84 | font-family: YakuHanMP, 'noto-serif', serif; 85 | } 86 | 87 | @mixin font-sans { 88 | font-family: 'Gothic A1', sans-serif; 89 | } 90 | 91 | @mixin ellipsis { 92 | overflow: hidden; 93 | text-overflow: ellipsis; 94 | white-space: nowrap; 95 | } 96 | 97 | @mixin link-text { 98 | color: $color-link; 99 | @include min { 100 | &:hover { 101 | color: $color-link-hover; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'contentful' 2 | import { CONTENT_TYPE_POST, CONTENT_TYPE_TAG } from './config/constant' 3 | 4 | require('dotenv').config() 5 | 6 | const CONTENTFUL_SPACE_ID = process.env.SPACE_ID || 'pf2aflscqgg2' 7 | const CONTENTFUL_ACCESS_TOKEN = process.env.ACCESS_TOKEN || '672f6ef9cd1792652afa415fb353482f43f7267fd191ff488869528a70ed0ed1' 8 | 9 | const routes = async function() { 10 | const client = createClient({ 11 | space: CONTENTFUL_SPACE_ID, 12 | accessToken: CONTENTFUL_ACCESS_TOKEN 13 | }) 14 | 15 | const [post, tag] = await Promise.all([ 16 | client.getEntries({ content_type: CONTENT_TYPE_POST, limit: 1000 }), 17 | client.getEntries({ content_type: CONTENT_TYPE_TAG }) 18 | ]) 19 | 20 | return [...post.items.map(item => `/entry/${item.sys.id}`), ...tag.items.map(item => `/tag/${item.sys.id}`)] 21 | } 22 | 23 | const TITLE = '邦画だってさ' 24 | const DESCRIPTION = 25 | 'おもしろかった邦画の感想を書いていく予定です。まだ何も書けていないですが、順次追加していきます。たまには邦画も悪くないですよ。' 26 | 27 | module.exports = { 28 | mode: 'universal', 29 | head: { 30 | title: TITLE, 31 | htmlAttrs: { 32 | lang: 'ja' 33 | }, 34 | meta: [ 35 | { charset: 'utf-8' }, 36 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 37 | { name: 'theme-color', content: '#ffffff' }, 38 | { 39 | hid: 'description', 40 | name: 'description', 41 | content: DESCRIPTION 42 | }, 43 | { 44 | name: 'theme-color', 45 | content: '#ffffff' 46 | }, 47 | { 48 | property: 'og:site_name', 49 | content: TITLE 50 | }, 51 | { 52 | hid: 'og:title', 53 | name: 'og:title', 54 | content: TITLE 55 | }, 56 | { 57 | hid: 'og:description', 58 | name: 'og:description', 59 | content: DESCRIPTION 60 | }, 61 | { 62 | property: 'og:image', 63 | content: 'https://blog.houga.cc/og.png?2018110601' 64 | }, 65 | { 66 | name: 'twitter:card', 67 | content: 'summary_large_image' 68 | }, 69 | { 70 | name: 'twitter:image', 71 | content: 'https://blog.houga.cc/og.png?2018110601' 72 | } 73 | ], 74 | link: [ 75 | { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }, 76 | { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }, 77 | { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' } 78 | ] 79 | }, 80 | env: { 81 | SPACE_ID: CONTENTFUL_SPACE_ID, 82 | ACCESS_TOKEN: CONTENTFUL_ACCESS_TOKEN 83 | }, 84 | loading: false, 85 | css: ['@/assets/scss/index.scss'], 86 | modules: [ 87 | ['@nuxtjs/google-analytics', { id: 'UA-53153991-19' }], 88 | '@nuxtjs/dotenv', 89 | '@nuxtjs/pwa', 90 | '@nuxtjs/sitemap', 91 | '@nuxtjs/style-resources' 92 | ], 93 | styleResources: { 94 | scss: [ 95 | './assets/scss/foundation/_variables.scss', 96 | './assets/scss/foundation/_mixin.scss' 97 | ] 98 | }, 99 | generate: { 100 | routes 101 | }, 102 | sitemap: { 103 | path: '/sitemap.xml', 104 | hostname: 'https://blog.houga.cc', 105 | generate: true, 106 | routes 107 | }, 108 | manifest: { 109 | name: '邦画だってさ', 110 | short_name: '邦画だってさ', 111 | lang: 'ja', 112 | orientation: 'portrait', 113 | theme_color: '#ffffff', 114 | background_color: '#ffffff', 115 | start_url: '/index.html' 116 | }, 117 | workbox: { 118 | dev: true 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex' 2 | import * as api from '@/api' 3 | import { uniq } from '@/lib/helpers' 4 | import { POSTS_PER_PAGE } from '@/config/constant' 5 | 6 | const createIndexState = () => ({ 7 | total: null, 8 | page: null, 9 | lastPage: null, 10 | ids: [], 11 | isFetching: false 12 | }) 13 | 14 | const createStore = () => 15 | new Vuex.Store({ 16 | state: { 17 | entities: { 18 | post: {}, 19 | tag: {} 20 | }, 21 | index: { 22 | post: createIndexState(), 23 | tag: createIndexState() 24 | }, 25 | postId: null, 26 | tagIds: [], 27 | tagId: null, 28 | error: null 29 | }, 30 | getters: { 31 | posts(state) { 32 | return state.index.post.ids.map(id => state.entities.post[id]) 33 | }, 34 | post(state) { 35 | return state.postId ? state.entities.post[state.postId] : null 36 | }, 37 | tagPosts(state) { 38 | return state.index.tag.ids.map(id => state.entities.post[id]) 39 | }, 40 | tags(state) { 41 | return state.tagIds.map(id => state.entities.tag[id]) 42 | }, 43 | tag(state) { 44 | return state.tagId ? state.entities.tag[state.tagId] : null 45 | } 46 | }, 47 | actions: { 48 | async nuxtServerInit({ commit }) { 49 | try { 50 | const { items } = await api.fetchTags() 51 | 52 | commit('mergeTagEntities', { items }) 53 | commit('setTagIds', { items }) 54 | } catch (e) { 55 | commit('setNetwordError') 56 | } 57 | }, 58 | async fetchPosts({ state, commit }, { pageType, page, tagId = null }) { 59 | if (state.index[pageType].isFetching) { 60 | return 61 | } 62 | 63 | commit('setIsFetching', { pageType }) 64 | 65 | try { 66 | const { total, items } = await api.fetchPosts({ page, tagId }) 67 | 68 | commit('clearIsFetching', { pageType }) 69 | commit('mergePostEntities', { items }) 70 | commit('appendPostIds', { pageType, items }) 71 | commit('setPage', { pageType, page }) 72 | 73 | if (page === 1) { 74 | commit('setTotal', { pageType, total }) 75 | commit('setLastPage', { pageType, total }) 76 | } 77 | } catch (e) { 78 | commit('setNetwordError') 79 | } 80 | }, 81 | async fetchPost({ commit }, { id }) { 82 | try { 83 | const item = await api.fetchPost(id) 84 | 85 | commit('mergePostEntities', { items: [item] }) 86 | commit('setPostId', { id: item.sys.id }) 87 | } catch (e) { 88 | commit('setNetwordError') 89 | } 90 | } 91 | }, 92 | mutations: { 93 | mergePostEntities(state, { items }) { 94 | state.entities.post = { 95 | ...state.entities.post, 96 | ...items.reduce((obj, item) => ({ ...obj, [item.sys.id]: item }), {}) 97 | } 98 | }, 99 | mergeTagEntities(state, { items }) { 100 | state.entities.tag = { 101 | ...state.entities.tag, 102 | ...items.reduce((obj, item) => ({ ...obj, [item.sys.id]: item }), {}) 103 | } 104 | }, 105 | appendPostIds(state, { pageType, items }) { 106 | state.index[pageType].ids = uniq([...state.index[pageType].ids, ...items.map(item => item.sys.id)]) 107 | }, 108 | clearPostIds(state, { pageType }) { 109 | state.index[pageType].ids = [] 110 | }, 111 | setTotal(state, { pageType, total }) { 112 | state.index[pageType].total = total 113 | }, 114 | setPage(state, { pageType, page }) { 115 | state.index[pageType].page = page 116 | }, 117 | setLastPage(state, { pageType, total }) { 118 | state.index[pageType].lastPage = Math.ceil(total / POSTS_PER_PAGE) 119 | }, 120 | setIsFetching(state, { pageType }) { 121 | state.index[pageType].isFetching = true 122 | }, 123 | clearIsFetching(state, { pageType }) { 124 | state.index[pageType].isFetching = false 125 | }, 126 | setPostId(state, { id }) { 127 | state.postId = id 128 | }, 129 | clearPostId(state) { 130 | state.postId = null 131 | }, 132 | setTagIds(state, { items }) { 133 | state.tagIds = [...state.tagIds, ...items.map(item => item.sys.id)] 134 | }, 135 | setTagId(state, { id }) { 136 | state.tagId = id 137 | }, 138 | setNetwordError(state) { 139 | state.error = '通信の調子が悪いようで・・・' 140 | } 141 | } 142 | }) 143 | 144 | export default createStore 145 | -------------------------------------------------------------------------------- /pages/entry/_id.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 55 | 56 | 227 | -------------------------------------------------------------------------------- /assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------