Compare commits

..

No commits in common. "8ebafd8bae85b9d46e9f89cef8de66cf881ca2f3" and "64735b5ee5d7cb0a99579d0a46e8750123beded8" have entirely different histories.

52 changed files with 2163 additions and 5934 deletions

1
.browserslistrc Normal file
View File

@ -0,0 +1 @@
> 1%, not ie <= 12

View File

@ -1,5 +0,0 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1 +0,0 @@
port: 3000

1
.gitignore vendored
View File

@ -20,7 +20,6 @@
.byebug_history
/public/assets
/dist
.DS_Store
/public/packs

View File

@ -1 +1 @@
3.0.3
3.0.1

View File

@ -1,4 +1,4 @@
FROM ruby:3.0.3-bullseye
FROM ruby:3.0.1-buster
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
@ -8,10 +8,11 @@ RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \
nodejs \
yarn \
nginx && \
yarn global add @vue/cli && \
gem update --system && gem install bundler && \
apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/*
RUN gem update --system && gem install bundler
# Install nginx config files
RUN rm /etc/nginx/sites-enabled/default
ADD docker/nginx_server.conf /etc/nginx/sites-enabled/parsley.conf
@ -31,12 +32,12 @@ COPY Gemfile* ./
RUN bundle install
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
RUN yarn install --production=true --frozen-lockfile
COPY . .
# Compile assets
RUN yarn build
RUN env RAILS_ENV=production bundle exec rails webpacker:clobber webpacker:compile
ENV PORT=80
EXPOSE 80

14
Gemfile
View File

@ -1,26 +1,28 @@
source 'https://rubygems.org'
gem 'rails', '6.1.4.1'
gem 'rails', '6.1.3.2'
gem 'pg', '~> 1.2.3'
gem 'webpacker', '5.3.0'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'oj', '~> 3.13.9'
gem 'oj', '~> 3.11.5'
gem 'kaminari', '~> 1.2.1'
gem 'unitwise', '~> 2.2.0'
gem 'redcarpet', '~> 3.5.1'
gem 'dalli', '~> 3.0.4'
gem 'puma', '~> 5.5'
gem 'dalli', '~> 2.7.11'
gem 'puma', '~> 5.3'
gem 'bcrypt', '~> 3.1.16'
gem 'tzinfo-data'
group :development, :test do
gem 'sqlite3', '~> 1.4.2'
gem 'listen', '~> 3.3'
gem 'rspec-rails', '~> 5.0.2'
gem 'guard', '~> 2.16.2'
gem 'guard-rspec', require: false
gem 'rspec-rails', '~> 5.0.1'
gem 'rails-controller-testing'
gem 'factory_bot_rails', '~> 6.2.0'
gem 'database_cleaner', '~> 2.0.1'

View File

@ -1,72 +1,73 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
actioncable (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
actionmailbox (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
mail (>= 2.7.1)
actionmailer (6.1.4.1)
actionpack (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activesupport (= 6.1.4.1)
actionmailer (6.1.3.2)
actionpack (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activesupport (= 6.1.3.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.4.1)
actionview (= 6.1.4.1)
activesupport (= 6.1.4.1)
actionpack (6.1.3.2)
actionview (= 6.1.3.2)
activesupport (= 6.1.3.2)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.4.1)
actionpack (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
actiontext (6.1.3.2)
actionpack (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
nokogiri (>= 1.8.5)
actionview (6.1.4.1)
activesupport (= 6.1.4.1)
actionview (6.1.3.2)
activesupport (= 6.1.3.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.1.4.1)
activesupport (= 6.1.4.1)
activejob (6.1.3.2)
activesupport (= 6.1.3.2)
globalid (>= 0.3.6)
activemodel (6.1.4.1)
activesupport (= 6.1.4.1)
activerecord (6.1.4.1)
activemodel (= 6.1.4.1)
activesupport (= 6.1.4.1)
activestorage (6.1.4.1)
actionpack (= 6.1.4.1)
activejob (= 6.1.4.1)
activerecord (= 6.1.4.1)
activesupport (= 6.1.4.1)
activemodel (6.1.3.2)
activesupport (= 6.1.3.2)
activerecord (6.1.3.2)
activemodel (= 6.1.3.2)
activesupport (= 6.1.3.2)
activestorage (6.1.3.2)
actionpack (= 6.1.3.2)
activejob (= 6.1.3.2)
activerecord (= 6.1.3.2)
activesupport (= 6.1.3.2)
marcel (~> 1.0.0)
mini_mime (>= 1.1.0)
activesupport (6.1.4.1)
mini_mime (~> 1.0.2)
activesupport (6.1.3.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
bcrypt (3.1.16)
bootsnap (1.9.3)
bootsnap (1.7.5)
msgpack (~> 1.0)
builder (3.2.4)
concurrent-ruby (1.1.9)
coderay (1.1.3)
concurrent-ruby (1.1.8)
crass (1.0.6)
dalli (3.0.4)
dalli (2.7.11)
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.1)
@ -80,10 +81,25 @@ GEM
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
ffi (1.15.4)
globalid (1.0.0)
activesupport (>= 5.0)
i18n (1.8.11)
ffi (1.15.0)
formatador (0.2.5)
globalid (0.4.2)
activesupport (>= 4.2.0)
guard (2.16.2)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-rspec (4.7.3)
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
kaminari (1.2.1)
activesupport (>= 4.1.0)
@ -98,49 +114,59 @@ GEM
kaminari-core (= 1.2.1)
kaminari-core (1.2.1)
liner (0.2.4)
listen (3.7.0)
listen (3.5.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.12.0)
loofah (2.9.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lumberjack (1.2.8)
mail (2.7.1)
mini_mime (>= 0.1.1)
marcel (1.0.2)
marcel (1.0.1)
memoizable (0.4.2)
thread_safe (~> 0.3, >= 0.3.1)
method_source (1.0.0)
mini_mime (1.1.2)
mini_portile2 (2.6.1)
mini_mime (1.0.3)
mini_portile2 (2.5.1)
minitest (5.14.4)
msgpack (1.4.2)
nio4r (2.5.8)
nokogiri (1.12.5)
mini_portile2 (~> 2.6.1)
nenv (0.3.0)
nio4r (2.5.7)
nokogiri (1.11.3)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
oj (3.13.9)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
oj (3.11.5)
parslet (1.8.2)
pg (1.2.3)
puma (5.5.2)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
puma (5.3.0)
nio4r (~> 2.0)
racc (1.6.0)
racc (1.5.2)
rack (2.2.3)
rack-proxy (0.6.5)
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.1.4.1)
actioncable (= 6.1.4.1)
actionmailbox (= 6.1.4.1)
actionmailer (= 6.1.4.1)
actionpack (= 6.1.4.1)
actiontext (= 6.1.4.1)
actionview (= 6.1.4.1)
activejob (= 6.1.4.1)
activemodel (= 6.1.4.1)
activerecord (= 6.1.4.1)
activestorage (= 6.1.4.1)
activesupport (= 6.1.4.1)
rails (6.1.3.2)
actioncable (= 6.1.3.2)
actionmailbox (= 6.1.3.2)
actionmailer (= 6.1.3.2)
actionpack (= 6.1.3.2)
actiontext (= 6.1.3.2)
actionview (= 6.1.3.2)
activejob (= 6.1.3.2)
activemodel (= 6.1.3.2)
activerecord (= 6.1.3.2)
activestorage (= 6.1.3.2)
activesupport (= 6.1.3.2)
bundler (>= 1.15.0)
railties (= 6.1.4.1)
railties (= 6.1.3.2)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -149,19 +175,23 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.2)
rails-html-sanitizer (1.3.0)
loofah (~> 2.3)
railties (6.1.4.1)
actionpack (= 6.1.4.1)
activesupport (= 6.1.4.1)
railties (6.1.3.2)
actionpack (= 6.1.3.2)
activesupport (= 6.1.3.2)
method_source
rake (>= 0.13)
rake (>= 0.8.7)
thor (~> 1.0)
rake (13.0.6)
rake (13.0.3)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.5.1)
rspec (3.10.0)
rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.1)
@ -170,7 +200,7 @@ GEM
rspec-mocks (3.10.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-rails (5.0.2)
rspec-rails (5.0.1)
actionpack (>= 5.2)
activesupport (>= 5.2)
railties (>= 5.2)
@ -178,31 +208,38 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.10.3)
rspec-support (3.10.2)
semantic_range (3.0.0)
shellany (0.0.1)
signed_multiset (0.2.1)
sprockets (4.0.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.4.1)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.4.2)
thor (1.1.0)
thread_safe (0.3.6)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2021.5)
tzinfo-data (1.2021.1)
tzinfo (>= 1.0.0)
unitwise (2.2.0)
liner (~> 0.2)
memoizable (~> 0.4)
parslet (~> 1.5)
signed_multiset (~> 0.2)
websocket-driver (0.7.5)
webpacker (5.3.0)
activesupport (>= 5.2)
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
websocket-driver (0.7.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
zeitwerk (2.5.1)
zeitwerk (2.4.2)
PLATFORMS
ruby
@ -210,21 +247,23 @@ PLATFORMS
DEPENDENCIES
bcrypt (~> 3.1.16)
bootsnap (>= 1.1.0)
dalli (~> 3.0.4)
dalli (~> 2.7.11)
database_cleaner (~> 2.0.1)
factory_bot_rails (~> 6.2.0)
guard (~> 2.16.2)
guard-rspec
kaminari (~> 1.2.1)
listen (~> 3.3)
oj (~> 3.13.9)
oj (~> 3.11.5)
pg (~> 1.2.3)
puma (~> 5.5)
rails (= 6.1.4.1)
puma (~> 5.3)
rails (= 6.1.3.2)
rails-controller-testing
redcarpet (~> 3.5.1)
rspec-rails (~> 5.0.2)
rspec-rails (~> 5.0.1)
sqlite3 (~> 1.4.2)
tzinfo-data
unitwise (~> 2.2.0)
webpacker (= 5.3.0)
BUNDLED WITH
2.2.32
2.2.17

View File

@ -1,2 +1,2 @@
frontend: yarn serve
rails: bundle exec rails s -b 0.0.0.0
webpacker: bin/webpack-dev-server

View File

@ -5,7 +5,7 @@ A self hosted cookbook
Parsley is released under the MIT License.
Copyright (C) 2021 Dan Elbert
Copyright (C) 2020 Dan Elbert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -5,7 +5,7 @@ class ApplicationSerializer
def initialize(items, serializer, opts = {})
super(items, opts)
@collection_name = 'list'
@collection_name = opts[:collection_name]
@serializer = serializer
end
@ -14,10 +14,10 @@ class ApplicationSerializer
if @collection_name && item.respond_to?(:total_pages)
{
totalCount: item.total_count,
totalPages: item.total_pages,
currentPage: item.current_page,
pageSize: item.limit_value,
total_count: item.total_count,
total_pages: item.total_pages,
current_page: item.current_page,
page_size: item.limit_value,
@collection_name.to_sym => list
}
else

View File

@ -1,5 +1,78 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
};
module.exports = function(api) {
var validEnv = ['development', 'test', 'production']
var currentEnv = api.env()
var isDevelopmentEnv = api.env('development')
var isProductionEnv = api.env('production')
var isTestEnv = api.env('test')
if (!validEnv.includes(currentEnv)) {
throw new Error(
'Please specify a valid `NODE_ENV` or ' +
'`BABEL_ENV` environment variables. Valid values are "development", ' +
'"test", and "production". Instead, received: ' +
JSON.stringify(currentEnv) +
'.'
)
}
return {
presets: [
isTestEnv && [
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
],
(isProductionEnv || isDevelopmentEnv) && [
'@babel/preset-env',
{
forceAllTransforms: true,
useBuiltIns: 'entry',
corejs: 3,
modules: false,
exclude: ['transform-typeof-symbol']
}
]
].filter(Boolean),
plugins: [
'babel-plugin-macros',
'@babel/plugin-syntax-dynamic-import',
isTestEnv && 'babel-plugin-dynamic-import-node',
'@babel/plugin-transform-destructuring',
[
'@babel/plugin-proposal-class-properties',
{
loose: true
}
],
[
'@babel/plugin-proposal-private-methods',
{
"loose": true
}
],
[
'@babel/plugin-proposal-object-rest-spread',
{
useBuiltIns: true
}
],
[
'@babel/plugin-transform-runtime',
{
helpers: false,
regenerator: true,
corejs: false
}
],
[
'@babel/plugin-transform-regenerator',
{
async: false
}
]
].filter(Boolean)
}
}

View File

@ -5,14 +5,13 @@ upstream parsley_app {
server {
listen 80;
root /parsley/dist;
root /parsley/public;
location / {
index index.html index.htm;
try_files $uri $uri/ @rails;
try_files $uri @rails;
}
location ~ ^/(css|img|js)/ {
location ~ ^/(assets|packs)/ {
try_files $uri @rails;
gzip_static on;
expires 1y;
@ -28,4 +27,4 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}

View File

@ -1,90 +1,26 @@
{
"name": "parsley",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"register-service-worker": "^1.7.1",
"vue": "^3.0.0",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
"@rails/webpacker": "5.3.0",
"@tweenjs/tween.js": "^18.6.4",
"autosize": "^4.0.2",
"bulma": "^0.8.2",
"cheerio": "^1.0.0-rc.9",
"css-loader": "^5.2.4",
"lodash": "^4.17.21",
"svg-loader": "^0.0.2",
"url-loader": "4.1.1",
"vue": "^2.6.12",
"vue-loader": "^15.9.6",
"vue-progressbar": "^0.7.5",
"vue-resize": "^1.0.1",
"vue-router": "^3.5.1",
"vue-template-compiler": "^2.6.12",
"vuex": "^3.6.2",
"vuex-router-sync": "^5.0.0",
"webpack": "^4.46.0"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/mocha": "^5.2.4",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-unit-mocha": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^2.0.0-0",
"chai": "^4.1.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^7.0.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~4.1.5"
},
"resolutions": {
"workbox-webpack-plugin": "~6.4.1"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"@vue/standard",
"@vue/typescript/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {
"space-before-function-paren": [
"error",
"never"
],
"semi": [
"error",
"always"
]
},
"overrides": [
{
"files": [
"**/__tests__/*.{j,t}s?(x)",
"**/tests/unit/**/*.spec.{j,t}s?(x)"
],
"env": {
"mocha": true
}
}
]
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}

12
postcss.config.js Normal file
View File

@ -0,0 +1,12 @@
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3
})
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 215 B

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,30 +0,0 @@
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</template>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,67 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa" target="_blank" rel="noopener">pwa</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha" target="_blank" rel="noopener">unit-mocha</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
@Options({
props: {
msg: String
}
})
export default class HelloWorld extends Vue {
msg!: string
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -1,147 +0,0 @@
import * as Errors from './errors';
import { PagedCollection, Recipe } from '@/lib/models';
type UpdateHandler = {
url: string,
component: unknown,
handler: (res: Response) => void
}
class Api {
cacheFirstUpdateHandlers: UpdateHandler[] = [];
constructor() {
navigator.serviceWorker.addEventListener('message', async(event) => {
if (event.data.type === 'CACHE_UPDATED' && event.data.meta === 'workbox-broadcast-update') {
const { cacheName, updatedURL } = event.data.payload;
const interestedHandlers = this.cacheFirstUpdateHandlers.filter(h => h.url === updatedURL);
if (interestedHandlers.length > 0) {
const cache = await caches.open(cacheName);
const updatedResponse = await cache.match(updatedURL);
if (updatedResponse !== undefined) {
interestedHandlers.forEach(h => h.handler(updatedResponse));
}
}
}
});
}
async checkStatus(response: Response): Promise<Response> {
if (response.ok) {
return response;
} else if (response.status === 404) {
throw new Errors.ApiNotFoundError(response.statusText, response);
} else if (response.status === 422) {
let json = null;
try {
json = await response.json();
} catch (e) {
// no-op
}
throw new Errors.ApiValidationError('Validation Error', response, json);
} else {
throw new Errors.ApiServerError(response.statusText || 'Unknown Server Error', response);
}
}
performRequest<TParams, TReturns>(url: string, method: string, params: TParams, headers: {[index: string]: string} = {}): Promise<TReturns> {
const hasBody = (params !== null && params !== undefined) && Object.keys(params).length !== 0;
let body: string | null = null;
const reqHeaders = new Headers();
reqHeaders.append('Accept', 'application/json');
reqHeaders.append('Content-Type', 'application/json');
for (const key in headers) {
reqHeaders.append(key, headers[key]);
}
if (hasBody) {
body = JSON.stringify(params);
}
return fetch(url, {
headers: reqHeaders,
method: method,
credentials: 'same-origin',
body: body
}).then(this.checkStatus)
.then(res => res.json());
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
objectToUrlParams(obj: any, queryParams: string[] = [], prefixes: string[] = []): string[] {
for (const key in obj) {
const val = obj[key];
const paramName = prefixes.join('[') + '['.repeat(Math.min(prefixes.length, 1)) + encodeURIComponent(key) + ']'.repeat(prefixes.length);
if (Array.isArray(val)) {
for (const x of val) {
queryParams.push(paramName + '[]=' + (x === null ? '' : encodeURIComponent(x.toString())));
}
} else if (val !== null && typeof (val) === 'object') {
this.objectToUrlParams(val, queryParams, prefixes.concat([key]));
} else {
queryParams.push(paramName + '=' + (val === null ? '' : encodeURIComponent(val)));
}
}
return queryParams;
}
buildGetUrl<TParams>(url: string, params: TParams): string {
const queryParams = this.objectToUrlParams(params);
if (queryParams.length) {
url = url + '?' + queryParams.join('&');
}
return url;
}
get<TParams, TReturn>(url: string, params: TParams): Promise<TReturn> {
return this.performRequest<null, TReturn>(this.buildGetUrl(url, params), 'GET', null);
}
setupCacheFirstGet<TParams, TReturn>(url: string, params: TParams, component: unknown, dataHandler: (data: TReturn) => void): () => void {
const fullUrl = this.buildGetUrl(url, params);
this.cacheFirstUpdateHandlers.push({
url: fullUrl,
component: component,
handler: res => res.json().then(data => dataHandler(data))
});
this.performRequest<null, TReturn>(fullUrl, 'GET', null, { 'Stale-While-Revalidate': 'true' })
.then((data: TReturn) => dataHandler(data));
return () => {
this.disableCacheFirstGet(url, params, component);
};
}
disableCacheFirstGet<TParams>(url: string | null, params: TParams, component: unknown) {
let fullUrl: string | null = null;
if (url !== null) {
fullUrl = this.buildGetUrl(url, params);
}
this.cacheFirstUpdateHandlers = this.cacheFirstUpdateHandlers.filter(h => !(h.component === component && (fullUrl === null || fullUrl === h.url)));
}
getRecipeList(component: unknown, page: number, per: number, sortColumn: string | null, sortDirection: string | null, name: string | null, tags: string[] | null, dataHandler: (json: PagedCollection<Recipe>) => void): () => void {
const params = {
criteria: {
page: page,
per: per,
sort_column: sortColumn,
sort_direction: sortDirection,
name: name,
tags: tags
}
};
return this.setupCacheFirstGet('/recipes', params, component, dataHandler);
}
}
export default new Api();

View File

@ -1,57 +0,0 @@
type ValidationErrors = {
[key: string]: string[]
}
export class ApiError extends Error {
public response: Response;
constructor(message: string, response: Response) {
super(message);
this.name = 'ApiError';
this.response = response;
}
public get responseCode(): number {
if (this.response) {
return this.response.status;
} else {
return 0;
}
}
}
export class ApiServerError extends ApiError {
constructor(message: string, response: Response) {
super(message, response);
this.name = 'ApiServerError';
}
}
export class ApiNotFoundError extends ApiError {
constructor(message: string, response: Response) {
super(message, response);
this.name = 'ApiNotFoundError';
}
}
export class ApiValidationError extends ApiError {
json?: { [index: string]: string[] };
constructor(message: string, response: Response, json?: { [index: string]: string[] }) {
super(message, response);
this.name = 'ApiValidationError';
this.json = json;
}
validationErrors(): ValidationErrors {
const errors = {} as ValidationErrors;
if (this.json !== null) {
for (const key in this.json) {
errors[key] = this.json[key] as string[];
}
} else {
errors.base = ['unknown error'];
}
return errors;
}
}

View File

@ -1,12 +0,0 @@
export interface PagedCollection<T> {
totalCount: number
totalPages: number
currentPage: number
pageSize: number
list: T[]
}
export interface Recipe {
id: number
name: string
}

View File

@ -1,8 +0,0 @@
import { createApp } from 'vue';
import App from './App.vue';
import { registerServiceWorker } from './registerServiceWorker';
import router from './router';
import store from './store';
registerServiceWorker(store);
createApp(App).use(store).use(router).mount('#app');

View File

@ -1,38 +0,0 @@
/* eslint-disable no-console */
import { register } from 'register-service-worker';
import { Store } from 'vuex';
import { State } from '@/store/state';
export function registerServiceWorker(store: Store<State>): void {
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
store.commit('setUpdateAvailable', false);
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
);
},
registered() {
console.log('Service worker has been registered.');
},
cached() {
console.log('Content has been cached for offline use.');
},
updatefound() {
console.log('New content is downloading.');
},
updated() {
store.commit('setUpdateAvailable', true);
console.log('New content is available; please refresh.');
},
offline() {
console.log('No internet connection found. App is running in offline mode.');
},
error(error) {
console.error('Error during service worker registration:', error);
}
});
}
}

View File

@ -1,25 +0,0 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Recipes from '../views/Recipes.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Recipes',
component: Recipes
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
];
const router = createRouter({
history: createWebHashHistory(),
routes
});
export default router;

View File

@ -1,32 +0,0 @@
import { NetworkFirst, NetworkOnly, StaleWhileRevalidate } from 'workbox-strategies';
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update/BroadcastUpdatePlugin';
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
import { setDefaultHandler, registerRoute } from 'workbox-routing';
import { setCacheNameDetails } from 'workbox-core';
setCacheNameDetails({
prefix: 'parsley'
});
cleanupOutdatedCaches();
precacheAndRoute(self.__WB_MANIFEST);
const isStaleWhileRevalidate = ({ request }) => {
return request.headers.get('Cache-Then-Network') === 'true';
};
registerRoute(
isStaleWhileRevalidate,
new StaleWhileRevalidate({
plugins: [
new BroadcastUpdatePlugin()
]
})
);
registerRoute(
new RegExp('^(?!http).+'),
new NetworkOnly()
);
setDefaultHandler(new NetworkFirst());

6
src/shims-vue.d.ts vendored
View File

@ -1,6 +0,0 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@ -1,17 +0,0 @@
import { createStore } from 'vuex';
import { State } from './state';
export default createStore<State>({
state: {
updateAvailable: false
},
mutations: {
setUpdateAvailable(state, payload) {
state.updateAvailable = payload;
}
},
actions: {
},
modules: {
}
});

View File

@ -1,3 +0,0 @@
export interface State {
updateAvailable: boolean
}

View File

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -1,18 +0,0 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src
@Options({
components: {
HelloWorld
}
})
export default class Home extends Vue {}
</script>

View File

@ -1,41 +0,0 @@
<template>
<div class="recipes">
<ul v-if="recipes !== null">
<li v-for="r in recipes.list" :key="r.id">
{{ r.name }}
</li>
</ul>
</div>
</template>
<script lang="ts">
import api from '@/lib/api';
import { PagedCollection, Recipe } from '@/lib/models';
import { Options, Vue } from 'vue-class-component';
@Options({
components: {
}
})
export default class Recipes extends Vue {
recipes: (PagedCollection<Recipe> | null) = null
cancelCacheUpdate: (() => void) | null = null
updateRecipes(): void {
if (this.cancelCacheUpdate !== null) {
this.cancelCacheUpdate();
}
this.cancelCacheUpdate = api.getRecipeList(this, 1, 20, null, null, null, null, data => { this.recipes = data; });
}
mounted(): void {
this.updateRecipes();
}
unmounted(): void {
if (this.cancelCacheUpdate !== null) {
this.cancelCacheUpdate();
}
}
}
</script>

9
src/vuex.d.ts vendored
View File

@ -1,9 +0,0 @@
import { Store } from 'vuex';
import { State } from '@/store/state';
declare module '@vue/runtime-core' {
// provide typings for `this.$store`
interface ComponentCustomProperties {
$store: Store<State>
}
}

View File

@ -1,13 +0,0 @@
import { expect } from 'chai';
import { shallowMount } from '@vue/test-utils';
import HelloWorld from '@/components/HelloWorld.vue';
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message';
const wrapper = shallowMount(HelloWorld, {
props: { msg }
});
expect(wrapper.text()).to.include(msg);
});
});

View File

@ -1,42 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env",
"mocha",
"chai"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@ -1,19 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');
// In development, the Rails app will run on a port 100 higher than this one (default way foreman assigns ports)
const devRailsPort = (parseInt(process.env.PORT || '3000') + 100).toString();
module.exports = {
pwa: {
workboxPluginMode: 'InjectManifest',
workboxOptions: {
swSrc: path.join(__dirname, 'src/service-worker.js'),
compileSrc: true
}
},
devServer: {
proxy: `http://localhost:${devRailsPort}`
}
};

7010
yarn.lock

File diff suppressed because it is too large Load Diff