basic login
This commit is contained in:
parent
bb223af9ae
commit
97eca6d319
@ -1,6 +1,17 @@
|
||||
class UsersController < ApplicationController
|
||||
|
||||
before_action :ensure_valid_user, except: [:login, :verify_login, :new, :create]
|
||||
UserProxy = Struct.new(:user_id)
|
||||
|
||||
before_action :ensure_valid_user, except: [:show, :login, :verify_login, :new, :create]
|
||||
skip_before_action :verify_authenticity_token, only: [:verify_login]
|
||||
|
||||
def show
|
||||
if current_user
|
||||
render json: { id: current_user.id, name: current_user.display_name }
|
||||
else
|
||||
render json: nil
|
||||
end
|
||||
end
|
||||
|
||||
def login
|
||||
|
||||
@ -14,13 +25,16 @@ class UsersController < ApplicationController
|
||||
end
|
||||
|
||||
def verify_login
|
||||
if user = User.authenticate(params[:username], params[:password])
|
||||
set_current_user(user)
|
||||
flash[:notice] = "Welcome, #{user.display_name}"
|
||||
redirect_to root_path
|
||||
else
|
||||
flash[:error] = "Invalid credentials"
|
||||
render :login
|
||||
|
||||
respond_to do |format|
|
||||
if user = User.authenticate(params[:username], params[:password])
|
||||
set_current_user(user)
|
||||
format.html { redirect_to root_path, notice: "Welcome, #{user.display_name}" }
|
||||
format.json { render json: { success: true, user: { id: user.id, name: user.display_name } } }
|
||||
else
|
||||
format.html { flash[:error] = "Invalid credentials"; render :login }
|
||||
format.json { render json: { success: false, message: 'Invalid Credentials', user: nil } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -15,17 +15,33 @@
|
||||
|
||||
<script>
|
||||
|
||||
import { mapState } from "vuex";
|
||||
import { mapMutations, mapState } from "vuex";
|
||||
import AppNavbar from "./AppNavbar";
|
||||
import api from "../lib/Api";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
hasError: state => state.error !== null,
|
||||
error: state => state.error
|
||||
error: state => state.error,
|
||||
authChecked: state => state.authChecked
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'setUser'
|
||||
])
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.user === null && this.authChecked === false) {
|
||||
this.loadResource(api.getCurrentUser(), user => {
|
||||
this.setUser(user);
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AppNavbar
|
||||
}
|
||||
|
100
app/javascript/components/AppIcon.vue
Normal file
100
app/javascript/components/AppIcon.vue
Normal file
@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<span class="icon" :class="sizeClass" @click="$emit('click', $event)">
|
||||
<svg v-html="svgContent" v-bind="svgAttributes"></svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import X from "open-iconic/svg/x";
|
||||
import Person from "open-iconic/svg/person";
|
||||
import LockLocked from "open-iconic/svg/lock-locked";
|
||||
import LockUnlocked from "open-iconic/svg/lock-unlocked";
|
||||
|
||||
const iconMap = {
|
||||
x: X,
|
||||
person: Person,
|
||||
'lock-locked': LockLocked,
|
||||
'lock-unlocked': LockUnlocked
|
||||
};
|
||||
|
||||
const sizeMap = {
|
||||
sm: 'is-small',
|
||||
md: '' ,
|
||||
lg: 'is-medium',
|
||||
xl: 'is-large'
|
||||
};
|
||||
|
||||
export default {
|
||||
props: {
|
||||
icon: {
|
||||
validator: (i) => iconMap[i] !== undefined
|
||||
},
|
||||
size: {
|
||||
required: false,
|
||||
type: String,
|
||||
validator: (s) => sizeMap[s] !== undefined,
|
||||
default: 'md'
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
svgObj() {
|
||||
return iconMap[this.icon];
|
||||
},
|
||||
svgAttributes() {
|
||||
const attrs = {
|
||||
class: this.size
|
||||
};
|
||||
|
||||
for (let a of ['viewBox', 'xmlns']) {
|
||||
if (this.svgObj.attributes[a]) {
|
||||
attrs[a] = this.svgObj.attributes[a];
|
||||
}
|
||||
}
|
||||
|
||||
return attrs;
|
||||
},
|
||||
svgContent() {
|
||||
return this.svgObj.content;
|
||||
},
|
||||
sizeClass() {
|
||||
return sizeMap[this.size];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.icon {
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: currentColor;
|
||||
|
||||
&.sm {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
&.md {
|
||||
width: 1.33em;
|
||||
height: 1.33em;
|
||||
}
|
||||
|
||||
&.lg {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
&.xl {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
59
app/javascript/components/AppModal.vue
Normal file
59
app/javascript/components/AppModal.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div ref="container">
|
||||
<div ref="modal" :class="['popup', 'modal', { 'is-active': open }]">
|
||||
<div class="modal-background" @click="close"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<slot name="title">
|
||||
<p class="modal-card-title">{{ title }}</p>
|
||||
<app-icon icon="x" aria-label="close" @click="close"></app-icon>
|
||||
</slot>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body">
|
||||
<slot></slot>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import AppIcon from "./AppIcon";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: String
|
||||
},
|
||||
|
||||
mounted() {
|
||||
document.body.appendChild(this.$refs.modal);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.$refs.container.appendChild(this.$refs.modal);
|
||||
},
|
||||
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("dismiss");
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AppIcon
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
|
||||
|
||||
</style>
|
@ -23,17 +23,23 @@
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item has-dropdown is-hoverable" >
|
||||
<a class="navbar-link" href="#" @click.prevent>
|
||||
Dan
|
||||
</a>
|
||||
<div class="navbar-dropdown is-boxed">
|
||||
<a class="navbar-item" href="#">
|
||||
Profile
|
||||
</a>
|
||||
<a class="navbar-item" href="#">
|
||||
Logout
|
||||
<div v-if="isLoggedIn">
|
||||
<a class="navbar-link" href="#" @click.prevent>
|
||||
{{ user.name }}
|
||||
</a>
|
||||
<div class="navbar-dropdown is-boxed">
|
||||
<a class="navbar-item" href="#">
|
||||
Profile
|
||||
</a>
|
||||
<a class="navbar-item" href="#">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<user-login></user-login>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,11 +49,16 @@
|
||||
|
||||
<script>
|
||||
|
||||
import UserLogin from "./UserLogin";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
menuActive: false
|
||||
};
|
||||
},
|
||||
components: {
|
||||
UserLogin
|
||||
}
|
||||
}
|
||||
|
||||
|
99
app/javascript/components/UserLogin.vue
Normal file
99
app/javascript/components/UserLogin.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<button class="button" type="button" @click="showLogin = true">
|
||||
Login
|
||||
</button>
|
||||
|
||||
<app-modal title="Login" :open="showLogin" @dismiss="showLogin = false">
|
||||
<div>
|
||||
<form @submit.prevent="login">
|
||||
|
||||
<div v-if="error" class="notification is-danger">
|
||||
{{error}}
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="text" placeholder="username" v-model="username">
|
||||
<app-icon icon="person" size="sm" class="is-left"></app-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Password</label>
|
||||
<div class="control has-icons-left">
|
||||
<input class="input" type="password" placeholder="password" v-model="password">
|
||||
<app-icon icon="lock-locked" size="sm" class="is-left"></app-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary" :disabled="!enableSubmit">Login</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-secondary" @click="showLogin = false">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</app-modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import AppModal from "./AppModal";
|
||||
import AppIcon from "./AppIcon";
|
||||
import api from "../lib/Api";
|
||||
import { mapMutations } from "vuex";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
showLogin: false,
|
||||
error: '',
|
||||
username: '',
|
||||
password: ''
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
enableSubmit() {
|
||||
return this.username !== '' && this.password !== '' && !this.isLoading;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'setUser'
|
||||
]),
|
||||
|
||||
login() {
|
||||
if (this.username !== '' && this.password != '') {
|
||||
this.loadResource(api.postLogin(this.username, this.password), data => {
|
||||
if (data.success) {
|
||||
this.setUser(data.user);
|
||||
this.showLogin = false;
|
||||
} else {
|
||||
this.error = data.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AppIcon,
|
||||
AppModal
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
@ -33,7 +33,8 @@ class Api {
|
||||
|
||||
const opts = {
|
||||
headers,
|
||||
method: method
|
||||
method: method,
|
||||
credentials: "same-origin"
|
||||
};
|
||||
|
||||
if (hasBody) {
|
||||
@ -66,6 +67,10 @@ class Api {
|
||||
return this.performRequest(url, "GET");
|
||||
}
|
||||
|
||||
post(url, params = {}) {
|
||||
return this.performRequest(url, "POST", params);
|
||||
}
|
||||
|
||||
getRecipeList(page, per, sortColumn, sortDirection, name, tags) {
|
||||
const params = {
|
||||
page: page || null,
|
||||
@ -78,6 +83,19 @@ class Api {
|
||||
|
||||
return this.get("/recipes", params);
|
||||
}
|
||||
|
||||
postLogin(username, password) {
|
||||
const params = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
return this.post("/login", params);
|
||||
}
|
||||
|
||||
getCurrentUser() {
|
||||
return this.get("/user")
|
||||
}
|
||||
}
|
||||
|
||||
const api = new Api();
|
||||
|
@ -1,12 +1,16 @@
|
||||
|
||||
import Vue from 'vue';
|
||||
import { mapMutations, mapState } from 'vuex';
|
||||
import { mapGetters, mapMutations, mapState } from 'vuex';
|
||||
|
||||
Vue.mixin({
|
||||
computed: {
|
||||
...mapState({
|
||||
isLoading: state => state.loading
|
||||
})
|
||||
...mapGetters([
|
||||
"isLoading",
|
||||
"isLoggedIn"
|
||||
]),
|
||||
...mapState([
|
||||
"user"
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
|
@ -7,10 +7,17 @@ export default new Vuex.Store({
|
||||
strict: process.env.NODE_ENV !== 'production',
|
||||
state: {
|
||||
loading: false,
|
||||
error: null
|
||||
error: null,
|
||||
authChecked: false,
|
||||
user: null
|
||||
},
|
||||
getters: {
|
||||
|
||||
isLoading(state) {
|
||||
return state.loading === true;
|
||||
},
|
||||
isLoggedIn(state) {
|
||||
return state.user !== null;
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
setLoading(state, value) {
|
||||
@ -19,6 +26,11 @@ export default new Vuex.Store({
|
||||
|
||||
setError(state, value) {
|
||||
state.error = value;
|
||||
},
|
||||
|
||||
setUser(state, user) {
|
||||
state.authChecked = true;
|
||||
state.user = user;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -3,6 +3,7 @@
|
||||
@import "~bulma/sass/utilities/_all";
|
||||
@import "~bulma/sass/base/_all";
|
||||
@import "~bulma/sass/components/navbar";
|
||||
@import "~bulma/sass/components/modal";
|
||||
@import "~bulma/sass/elements/_all";
|
||||
@import "~bulma/sass/grid/columns";
|
||||
@import "~bulma/sass/layout/section";
|
||||
|
2
app/views/users/show.json.jbuilder
Normal file
2
app/views/users/show.json.jbuilder
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
json.ex
|
@ -35,6 +35,7 @@ Rails.application.routes.draw do
|
||||
get '/login' => 'users#login', as: :login
|
||||
post '/login' => 'users#verify_login'
|
||||
get '/logout' => 'users#logout', as: :logout
|
||||
get '/user' => 'users#show', as: :show
|
||||
|
||||
get '/about' => 'home#about', as: :about
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
const { environment } = require('@rails/webpacker');
|
||||
const vue = require('./loaders/vue');
|
||||
const svg = require('./loaders/svg');
|
||||
|
||||
environment.loaders.append('vue', vue);
|
||||
environment.loaders.append('svg', svg);
|
||||
|
||||
const fileLoader = environment.loaders.get('file');
|
||||
fileLoader.exclude = /\.(svg)$/i;
|
||||
|
||||
environment.loaders.append('vue', vue)
|
||||
module.exports = environment;
|
||||
|
7
config/webpack/loaders/svg.js
Normal file
7
config/webpack/loaders/svg.js
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
module.exports = {
|
||||
test: /\.svg$/,
|
||||
use: [{
|
||||
loader: 'svg-loader'
|
||||
}]
|
||||
};
|
@ -4,6 +4,8 @@
|
||||
"bulma": "^0.6.2",
|
||||
"caniuse-lite": "^1.0.30000815",
|
||||
"css-loader": "^0.28.11",
|
||||
"open-iconic": "^1.1.1",
|
||||
"svg-loader": "^0.0.2",
|
||||
"vue": "^2.5.16",
|
||||
"vue-loader": "^14.2.2",
|
||||
"vue-router": "^3.0.1",
|
||||
|
@ -1,5 +1,5 @@
|
||||
Arguments:
|
||||
/home/dan/.nvm/versions/node/v8.7.0/bin/node /home/dan/.nvm/versions/node/v8.7.0/bin/yarn install
|
||||
/home/dan/.nvm/versions/node/v8.7.0/bin/node /home/dan/.nvm/versions/node/v8.7.0/bin/yarn add svg-router
|
||||
|
||||
PATH:
|
||||
/home/dan/.rvm/gems/ruby-2.5.1/bin:/home/dan/.rvm/gems/ruby-2.5.1@global/bin:/home/dan/.rvm/rubies/ruby-2.5.1/bin:/home/dan/.rvm/bin:/home/dan/.cargo/bin:/home/dan/.nvm/versions/node/v8.7.0/bin:/home/dan/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
|
||||
@ -17,6 +17,7 @@ npm manifest:
|
||||
{
|
||||
"dependencies": {
|
||||
"@rails/webpacker": "^3.4.1",
|
||||
"bulma": "^0.6.2",
|
||||
"caniuse-lite": "^1.0.30000815",
|
||||
"css-loader": "^0.28.11",
|
||||
"vue": "^2.5.16",
|
||||
@ -24,7 +25,7 @@ npm manifest:
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.5.16",
|
||||
"vuex": "^3.0.1",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-dev-server": "^2.11.2"
|
||||
@ -1065,6 +1066,10 @@ Lockfile:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
|
||||
bulma@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.6.2.tgz#f4b1d11d5acc51a79644eb0a2b0b10649d3d71f5"
|
||||
|
||||
bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
@ -2663,7 +2668,7 @@ Lockfile:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
||||
|
||||
http-errors@1.6.2, http-errors@~1.6.2:
|
||||
http-errors@1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
|
||||
dependencies:
|
||||
@ -2672,6 +2677,15 @@ Lockfile:
|
||||
setprototypeof "1.0.3"
|
||||
statuses ">= 1.3.1 < 2"
|
||||
|
||||
http-errors@~1.6.2:
|
||||
version "1.6.3"
|
||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
|
||||
dependencies:
|
||||
depd "~1.1.2"
|
||||
inherits "2.0.3"
|
||||
setprototypeof "1.1.0"
|
||||
statuses ">= 1.4.0 < 2"
|
||||
|
||||
http-parser-js@>=0.4.0:
|
||||
version "0.4.11"
|
||||
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.11.tgz#5b720849c650903c27e521633d94696ee95f3529"
|
||||
@ -5398,7 +5412,7 @@ Lockfile:
|
||||
define-property "^0.2.5"
|
||||
object-copy "^0.1.0"
|
||||
|
||||
"statuses@>= 1.3.1 < 2":
|
||||
"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
|
||||
@ -5989,7 +6003,7 @@ Lockfile:
|
||||
source-list-map "^2.0.0"
|
||||
source-map "~0.6.1"
|
||||
|
||||
webpack@^3.10.0:
|
||||
webpack@^3.10.0, webpack@^3.11.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.11.0.tgz#77da451b1d7b4b117adaf41a1a93b5742f24d894"
|
||||
dependencies:
|
||||
@ -6174,10 +6188,11 @@ Lockfile:
|
||||
window-size "0.1.0"
|
||||
|
||||
Trace:
|
||||
SyntaxError: /home/dan/Development/parsley/package.json: Unexpected token } in JSON at position 292
|
||||
at JSON.parse (<anonymous>)
|
||||
at /home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:1036:59
|
||||
Error: Couldn't find package "svg-router" on the "npm" registry.
|
||||
at new MessageError (/home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:186:110)
|
||||
at NpmResolver.<anonymous> (/home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:51625:15)
|
||||
at Generator.next (<anonymous>)
|
||||
at step (/home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:98:30)
|
||||
at /home/dan/.nvm/versions/node/v8.7.0/lib/node_modules/yarn/lib/cli.js:109:13
|
||||
at <anonymous>
|
||||
at process._tickCallback (internal/process/next_tick.js:188:7)
|
||||
|
@ -3779,6 +3779,10 @@ onecolor@^3.0.4:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-3.0.5.tgz#36eff32201379efdf1180fb445e51a8e2425f9f6"
|
||||
|
||||
open-iconic@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/open-iconic/-/open-iconic-1.1.1.tgz#9dcfc8c7cd3c61cdb4a236b1a347894c97adc0c6"
|
||||
|
||||
opn@^5.1.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c"
|
||||
@ -5516,6 +5520,10 @@ supports-color@^5.1.0, supports-color@^5.3.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
svg-loader@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/svg-loader/-/svg-loader-0.0.2.tgz#601ab2fdaa1dadae3ca9975b550de92a07e1d92b"
|
||||
|
||||
svgo@^0.7.0:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
|
||||
|
Loading…
Reference in New Issue
Block a user