@@ -19,9 +19,11 @@ | |||||
"@babel/plugin-transform-react-jsx": "^7.3.0", | "@babel/plugin-transform-react-jsx": "^7.3.0", | ||||
"@babel/preset-env": "^7.5.4", | "@babel/preset-env": "^7.5.4", | ||||
"@babel/register": "^7.4.4", | "@babel/register": "^7.4.4", | ||||
"@vue/cli-plugin-babel": "^4.0.4", | |||||
"@vue/cli-service": "^4.0.4", | |||||
"@vue/test-utils": "^1.0.0-beta.29", | "@vue/test-utils": "^1.0.0-beta.29", | ||||
"babel-core": "7.0.0-bridge.0", | |||||
"axios-mock-adapter": "^1.17.0", | "axios-mock-adapter": "^1.17.0", | ||||
"babel-core": "7.0.0-bridge.0", | |||||
"babel-loader": "^8.0.6", | "babel-loader": "^8.0.6", | ||||
"chrome-webstore-upload-cli": "^1.2.0", | "chrome-webstore-upload-cli": "^1.2.0", | ||||
"clean-webpack-plugin": "^3.0.0", | "clean-webpack-plugin": "^3.0.0", | ||||
@@ -0,0 +1,3 @@ | |||||
module.exports = { | |||||
presets: ["@vue/cli-plugin-babel/preset"] | |||||
}; |
@@ -0,0 +1 @@ | |||||
#menu .text-white,#menu .white-link{color:inherit}#menu .white-link:active,#menu .white-link:focus,#menu .white-link:hover{text-decoration:none;color:inherit}.card-inverse{background-color:#333;border-color:#333}.fade-enter-active{transition:opacity .5s}.fade-leave-active{transition:opacity 2s}.fade-enter,.fade-leave-to{opacity:0}#message{position:absolute;top:49px;left:0;right:0;z-index:20}.close-notification{float:right;position:absolute;top:0;right:1em;cursor:pointer}.lesspass--unbordered #lesspass{border:none}.lesspass--full-width #lesspass{max-width:none!important}#lesspass{color:#464646;max-width:420px}.lesspass__inner-box{min-height:240px}@media (max-width:419px){.lesspass__inner-box{border:none}}#lesspass,#lesspass *{border-radius:0!important}.pointer,button{cursor:pointer}.inner-addon i{position:absolute;padding:10px;pointer-events:none;z-index:10}.inner-addon{position:relative}.left-addon i{left:0}.right-addon i{right:0}.left-addon input{padding-left:30px}.right-addon input{padding-right:30px}.border-blue{border-color:#007bff!important}#fingerprint{min-width:90px;text-align:center;background-color:transparent;color:#fff}#fingerprint i{color:#000;position:relative;padding:0;text-shadow:1px 1px 0 #fff;font-size:1.3em}#signInButton{border-right:none}#registerButton{border-left:none}.awesomplete mark{background-color:transparent!important;padding:0;margin:0;color:inherit}#options input[type=number]{-moz-appearance:textfield}#options input[type=number]::-webkit-inner-spin-button,#options input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}#generated-password{font-family:Consolas,Menlo,Monaco,Courier New,monospace,sans-serif}div.awesomplete{display:block}div.awesomplete>ul{z-index:11}.passwordProfile__avatar{display:inline-block;width:2rem;height:2rem;text-align:center;line-height:2rem;margin-right:1em;text-transform:uppercase;color:#fff;font-family:monospace}.passwordProfile{display:flex;cursor:pointer;margin-bottom:1rem;justify-content:space-between;align-items:center}.passwordProfile__info{display:flex;align-items:center;flex-grow:1}.passwordProfile__meta{font-size:.8rem;line-height:1rem;flex-grow:1}#passwords__list{min-height:11rem}#passwords__pagination .pagination{margin-bottom:0}#passwords__pagination .page-link{cursor:pointer} |
@@ -1,16 +1,4 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta charset="utf-8" /> | |||||
<title>LessPass</title> | |||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> | |||||
<meta | |||||
name="viewport" | |||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" | |||||
/> | |||||
<link rel="shortcut icon" href="dist/favicon.ico" /> | |||||
<style> | |||||
div.center { | |||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><title>LessPass</title><style>div.center { | |||||
max-width: 420px; | max-width: 420px; | ||||
display: block; | display: block; | ||||
margin-left: auto; | margin-left: auto; | ||||
@@ -21,12 +9,4 @@ | |||||
#lesspass { | #lesspass { | ||||
margin-top: 3em; | margin-top: 3em; | ||||
} | } | ||||
} | |||||
</style> | |||||
<link href="lesspass.min.css" rel="stylesheet"></head> | |||||
<body> | |||||
<div class="center lesspass--full-width"> | |||||
<div id="lesspass"></div> | |||||
</div> | |||||
<script type="text/javascript" src="lesspass.min.js"></script></body> | |||||
</html> | |||||
}</style><link href=/css/app.c1c55914.css rel=preload as=style><link href=/css/chunk-vendors.194c8e8d.css rel=preload as=style><link href=/js/app.6752e6e6.js rel=preload as=script><link href=/js/chunk-vendors.c12ad38e.js rel=preload as=script><link href=/css/chunk-vendors.194c8e8d.css rel=stylesheet><link href=/css/app.c1c55914.css rel=stylesheet></head><body><noscript><strong>We're sorry but LessPass doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div class="center lesspass--full-width"><div id=lesspass></div></div><script src=/js/chunk-vendors.c12ad38e.js></script><script src=/js/app.6752e6e6.js></script></body></html> |
@@ -5,23 +5,14 @@ | |||||
"license": "GPL-3.0", | "license": "GPL-3.0", | ||||
"author": "Guillaume Vincent <guillaume@oslab.fr>", | "author": "Guillaume Vincent <guillaume@oslab.fr>", | ||||
"scripts": { | "scripts": { | ||||
"start": "http-server dist -p 8080", | |||||
"build": "rm -rf dist && NODE_ENV=production webpack --mode=production --config webpack.prod.js", | |||||
"start": "vue-cli-service serve --port 8000", | |||||
"build": "vue-cli-service build", | |||||
"build:i18n": "cd scripts && node buildI18n.js", | "build:i18n": "cd scripts && node buildI18n.js", | ||||
"dev": "webpack-dev-server --config webpack.dev.js", | |||||
"cypress:run": "cypress run", | "cypress:run": "cypress run", | ||||
"cypress:open": "cypress open", | "cypress:open": "cypress open", | ||||
"test": "npm run test:unit && npm run test:e2e", | "test": "npm run test:unit && npm run test:e2e", | ||||
"test:unit": "jest", | "test:unit": "jest", | ||||
"test:e2e": "start-server-and-test dev http://localhost:8000 cypress:run" | |||||
}, | |||||
"babel": { | |||||
"presets": [ | |||||
"@babel/preset-env" | |||||
], | |||||
"plugins": [ | |||||
"@babel/plugin-proposal-object-rest-spread" | |||||
] | |||||
"test:e2e": "start-server-and-test start http://localhost:8000 cypress:run" | |||||
}, | }, | ||||
"jest": { | "jest": { | ||||
"testPathIgnorePatterns": [ | "testPathIgnorePatterns": [ | ||||
@@ -37,24 +28,28 @@ | |||||
} | } | ||||
}, | }, | ||||
"dependencies": { | "dependencies": { | ||||
"@oslab/atob": "0.1.0", | |||||
"@oslab/btoa": "0.1.0", | |||||
"awesomplete": "^1.1.4", | |||||
"@oslab/atob": "^0.1.0", | |||||
"@oslab/btoa": "^0.1.0", | |||||
"awesomplete": "^1.1.5", | |||||
"axios": "^0.19.0", | "axios": "^0.19.0", | ||||
"balloon-css": "^1.0.2", | |||||
"balloon-css": "^1.0.3", | |||||
"bootstrap": "^4.3.1", | "bootstrap": "^4.3.1", | ||||
"copy-text-to-clipboard": "^2.0.0", | |||||
"font-awesome": "4.7.0", | |||||
"jwt-decode": "2.2.0", | |||||
"lesspass": "9.0.2", | |||||
"lodash.debounce": "4.0.8", | |||||
"lodash.uniqby": "4.7.0", | |||||
"copy-text-to-clipboard": "^2.1.1", | |||||
"core-js": "^3.1.2", | |||||
"font-awesome": "^4.7.0", | |||||
"jwt-decode": "^2.2.0", | |||||
"lesspass": "^9.0.0", | |||||
"lodash": "^4.17.15", | |||||
"vue": "^2.6.10", | "vue": "^2.6.10", | ||||
"vue-polyglot": "^2.0.1", | "vue-polyglot": "^2.0.1", | ||||
"vue-router": "^3.0.2", | |||||
"vue-router": "^3.1.3", | |||||
"vuejs-paginate": "^2.1.0", | "vuejs-paginate": "^2.1.0", | ||||
"vuex": "^3.1.0", | |||||
"vuex": "^3.1.1", | |||||
"vuex-persistedstate": "^2.5.4", | "vuex-persistedstate": "^2.5.4", | ||||
"vuex-router-sync": "5.0.0" | |||||
} | |||||
"vuex-router-sync": "^5.0.0" | |||||
}, | |||||
"browserslist": [ | |||||
"> 1%", | |||||
"last 2 versions" | |||||
] | |||||
} | } |
@@ -1,14 +1,11 @@ | |||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html> | |||||
<html lang="en"> | |||||
<head> | <head> | ||||
<meta charset="utf-8" /> | |||||
<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>LessPass</title> | <title>LessPass</title> | ||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> | |||||
<meta | |||||
name="viewport" | |||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" | |||||
/> | |||||
<link rel="shortcut icon" href="dist/favicon.ico" /> | |||||
<style> | <style> | ||||
div.center { | div.center { | ||||
max-width: 420px; | max-width: 420px; | ||||
@@ -25,6 +22,9 @@ | |||||
</style> | </style> | ||||
</head> | </head> | ||||
<body> | <body> | ||||
<noscript> | |||||
<strong>We're sorry but LessPass doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |||||
</noscript> | |||||
<div class="center lesspass--full-width"> | <div class="center lesspass--full-width"> | ||||
<div id="lesspass"></div> | <div id="lesspass"></div> | ||||
</div> | </div> |
@@ -1,100 +0,0 @@ | |||||
@import "~bootstrap/scss/functions"; | |||||
@import "~bootstrap/scss/variables"; | |||||
@import "~bootstrap/scss/mixins"; | |||||
@import "~bootstrap/scss/root"; | |||||
@import "~bootstrap/scss/reboot"; | |||||
@import "~bootstrap/scss/type"; | |||||
@import "~bootstrap/scss/images"; | |||||
//@import "~bootstrap/scss/code"; | |||||
@import "~bootstrap/scss/grid"; | |||||
@import "~bootstrap/scss/tables"; | |||||
@import "~bootstrap/scss/forms"; | |||||
@import "~bootstrap/scss/buttons"; | |||||
@import "~bootstrap/scss/transitions"; | |||||
@import "~bootstrap/scss/dropdown"; | |||||
@import "~bootstrap/scss/button-group"; | |||||
@import "~bootstrap/scss/input-group"; | |||||
@import "~bootstrap/scss/custom-forms"; | |||||
@import "~bootstrap/scss/nav"; | |||||
@import "~bootstrap/scss/navbar"; | |||||
@import "~bootstrap/scss/card"; | |||||
//@import "~bootstrap/scss/breadcrumb"; | |||||
@import "~bootstrap/scss/pagination"; | |||||
@import "~bootstrap/scss/badge"; | |||||
@import "~bootstrap/scss/jumbotron"; | |||||
@import "~bootstrap/scss/alert"; | |||||
//@import "~bootstrap/scss/progress"; | |||||
@import "~bootstrap/scss/media"; | |||||
@import "~bootstrap/scss/list-group"; | |||||
@import "~bootstrap/scss/close"; | |||||
//@import "~bootstrap/scss/modal"; | |||||
//@import "~bootstrap/scss/tooltip"; | |||||
//@import "~bootstrap/scss/popover"; | |||||
//@import "~bootstrap/scss/carousel"; | |||||
@import "~bootstrap/scss/utilities"; | |||||
//@import "~bootstrap/scss/print"; | |||||
@import '~font-awesome/css/font-awesome.css'; | |||||
@import '~balloon-css/balloon.css'; | |||||
@import '~awesomplete/awesomplete.css'; | |||||
.lesspass--unbordered #lesspass{ | |||||
border: none; | |||||
} | |||||
.lesspass--full-width #lesspass { | |||||
max-width: none !important; | |||||
} | |||||
#lesspass { | |||||
color: #464646; | |||||
max-width: 420px; | |||||
} | |||||
.lesspass__inner-box { | |||||
min-height: 240px; | |||||
} | |||||
@media (max-width: 419px) { | |||||
.lesspass__inner-box { | |||||
border: none; | |||||
} | |||||
} | |||||
#lesspass, #lesspass * { | |||||
border-radius: 0 !important; | |||||
} | |||||
button, .pointer { | |||||
cursor: pointer; | |||||
} | |||||
.inner-addon i { | |||||
position: absolute; | |||||
padding: 10px; | |||||
pointer-events: none; | |||||
z-index: 10; | |||||
} | |||||
.inner-addon { | |||||
position: relative; | |||||
} | |||||
.left-addon i { | |||||
left: 0; | |||||
} | |||||
.right-addon i { | |||||
right: 0; | |||||
} | |||||
.left-addon input { | |||||
padding-left: 30px; | |||||
} | |||||
.right-addon input { | |||||
padding-right: 30px; | |||||
} | |||||
.border-blue { | |||||
border-color: $blue !important; | |||||
} |
@@ -1,3 +1,68 @@ | |||||
<style> | |||||
.lesspass--unbordered #lesspass { | |||||
border: none; | |||||
} | |||||
.lesspass--full-width #lesspass { | |||||
max-width: none !important; | |||||
} | |||||
#lesspass { | |||||
color: #464646; | |||||
max-width: 420px; | |||||
} | |||||
.lesspass__inner-box { | |||||
min-height: 240px; | |||||
} | |||||
@media (max-width: 419px) { | |||||
.lesspass__inner-box { | |||||
border: none; | |||||
} | |||||
} | |||||
#lesspass, | |||||
#lesspass * { | |||||
border-radius: 0 !important; | |||||
} | |||||
button, | |||||
.pointer { | |||||
cursor: pointer; | |||||
} | |||||
.inner-addon i { | |||||
position: absolute; | |||||
padding: 10px; | |||||
pointer-events: none; | |||||
z-index: 10; | |||||
} | |||||
.inner-addon { | |||||
position: relative; | |||||
} | |||||
.left-addon i { | |||||
left: 0; | |||||
} | |||||
.right-addon i { | |||||
right: 0; | |||||
} | |||||
.left-addon input { | |||||
padding-left: 30px; | |||||
} | |||||
.right-addon input { | |||||
padding-right: 30px; | |||||
} | |||||
.border-blue { | |||||
border-color: #007bff !important; | |||||
} | |||||
</style> | |||||
<template> | <template> | ||||
<div id="lesspass" class="card"> | <div id="lesspass" class="card"> | ||||
<lesspass-menu></lesspass-menu> | <lesspass-menu></lesspass-menu> | ||||
@@ -7,21 +72,21 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | |||||
import './LessPass.scss'; | |||||
import Menu from './components/Menu.vue'; | |||||
import Message from './components/Message.vue'; | |||||
export default { | |||||
name: 'LessPass', | |||||
components: { | |||||
'lesspass-menu': Menu, | |||||
'lesspass-message': Message | |||||
}, | |||||
created(){ | |||||
this.$store.dispatch('cleanMessage'); | |||||
this.$store.dispatch('refreshToken'); | |||||
this.$store.dispatch('resetPassword'); | |||||
} | |||||
<script> | |||||
import Menu from "./components/Menu.vue"; | |||||
import Message from "./components/Message.vue"; | |||||
export default { | |||||
name: "lesspass", | |||||
components: { | |||||
"lesspass-menu": Menu, | |||||
"lesspass-message": Message | |||||
}, | |||||
created() { | |||||
this.$store.dispatch("cleanMessage"); | |||||
this.$store.dispatch("refreshToken"); | |||||
this.$store.dispatch("resetPassword"); | |||||
} | } | ||||
}; | |||||
</script> | </script> | ||||
@@ -1,74 +1,71 @@ | |||||
<style> | <style> | ||||
.passwordProfile__avatar { | |||||
display: inline-block; | |||||
width: 2rem; | |||||
height: 2rem; | |||||
text-align: center; | |||||
line-height: 2rem; | |||||
margin-right: 1em; | |||||
text-transform: uppercase; | |||||
color: white; | |||||
font-family: monospace; | |||||
} | |||||
.passwordProfile__avatar { | |||||
display: inline-block; | |||||
width: 2rem; | |||||
height: 2rem; | |||||
text-align: center; | |||||
line-height: 2rem; | |||||
margin-right: 1em; | |||||
text-transform: uppercase; | |||||
color: white; | |||||
font-family: monospace; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<div v-bind:style="avatarStyle" | |||||
class="passwordProfile__avatar"> | |||||
{{firstLetter}} | |||||
</div> | |||||
<div v-bind:style="avatarStyle" class="passwordProfile__avatar">{{firstLetter}}</div> | |||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | <script type="text/ecmascript-6"> | ||||
export default { | |||||
name: 'avatar', | |||||
props: { | |||||
name: { | |||||
type: String, | |||||
required: true | |||||
} | |||||
}, | |||||
data() { | |||||
export default { | |||||
name: "avatar", | |||||
props: { | |||||
name: { | |||||
type: String, | |||||
required: true | |||||
} | |||||
}, | |||||
data() { | |||||
return { | |||||
alphabetColors: { | |||||
a: "#5A8770", | |||||
b: "#B2B7BB", | |||||
c: "#6FA9AB", | |||||
d: "#F5AF29", | |||||
e: "#0088B9", | |||||
f: "#F18636", | |||||
g: "#D93A37", | |||||
h: "#A6B12E", | |||||
i: "#5C9BBC", | |||||
j: "#F5888D", | |||||
k: "#9A89B5", | |||||
l: "#407887", | |||||
m: "#9A89B5", | |||||
n: "#5A8770", | |||||
o: "#D33F33", | |||||
p: "#A2B01F", | |||||
q: "#F0B126", | |||||
r: "#0087BF", | |||||
s: "#F18636", | |||||
t: "#0087BF", | |||||
u: "#B2B7BB", | |||||
v: "#72ACAE", | |||||
w: "#9C8AB4", | |||||
x: "#5A8770", | |||||
y: "#EEB424", | |||||
z: "#407887" | |||||
}, | |||||
firstLetter: "" | |||||
}; | |||||
}, | |||||
mounted() { | |||||
this.firstLetter = this.$props.name.charAt(0); | |||||
}, | |||||
computed: { | |||||
avatarStyle: function() { | |||||
return { | return { | ||||
alphabetColors: { | |||||
a: "#5A8770", | |||||
b: "#B2B7BB", | |||||
c: "#6FA9AB", | |||||
d: "#F5AF29", | |||||
e: "#0088B9", | |||||
f: "#F18636", | |||||
g: "#D93A37", | |||||
h: "#A6B12E", | |||||
i: "#5C9BBC", | |||||
j: "#F5888D", | |||||
k: "#9A89B5", | |||||
l: "#407887", | |||||
m: "#9A89B5", | |||||
n: "#5A8770", | |||||
o: "#D33F33", | |||||
p: "#A2B01F", | |||||
q: "#F0B126", | |||||
r: "#0087BF", | |||||
s: "#F18636", | |||||
t: "#0087BF", | |||||
u: "#B2B7BB", | |||||
v: "#72ACAE", | |||||
w: "#9C8AB4", | |||||
x: "#5A8770", | |||||
y: "#EEB424", | |||||
z: "#407887" | |||||
}, | |||||
firstLetter: '' | |||||
} | |||||
}, | |||||
mounted() { | |||||
this.firstLetter = this.$props.name.charAt(0); | |||||
}, | |||||
computed: { | |||||
avatarStyle: function() { | |||||
return { | |||||
backgroundColor: this.alphabetColors[this.firstLetter] || '#5A8770' | |||||
} | |||||
} | |||||
backgroundColor: this.alphabetColors[this.firstLetter] || "#5A8770" | |||||
}; | |||||
} | } | ||||
} | } | ||||
}; | |||||
</script> | </script> | ||||
@@ -1,10 +1,10 @@ | |||||
<style> | <style> | ||||
.awesomplete mark { | |||||
background-color: transparent !important; | |||||
padding: 0; | |||||
margin: 0; | |||||
color: inherit; | |||||
} | |||||
.awesomplete mark { | |||||
background-color: transparent !important; | |||||
padding: 0; | |||||
margin: 0; | |||||
color: inherit; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<div class="inputSite"> | <div class="inputSite"> | ||||
@@ -20,69 +20,74 @@ | |||||
autocorrect="off" | autocorrect="off" | ||||
autocapitalize="none" | autocapitalize="none" | ||||
v-bind:placeholder="label" | v-bind:placeholder="label" | ||||
v-model="site"> | |||||
v-model="site" | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import Awesomplete from "awesomplete"; | |||||
import {getSuggestions} from "../services/url-parser"; | |||||
import Awesomplete from "awesomplete"; | |||||
import { getSuggestions } from "../services/url-parser"; | |||||
export default { | |||||
name: "inputSite", | |||||
props: { | |||||
value: String, | |||||
label: String, | |||||
passwords: { | |||||
type: Array, | |||||
default: () => [] | |||||
export default { | |||||
name: "inputSite", | |||||
props: { | |||||
value: String, | |||||
label: String, | |||||
passwords: { | |||||
type: Array, | |||||
default: () => [] | |||||
} | |||||
}, | |||||
mounted() { | |||||
this.awesomplete = new Awesomplete(this.$refs.siteField); | |||||
this.awesomplete.item = (element, input) => { | |||||
let item = Awesomplete.ITEM(element.value.site, input); | |||||
item.innerHTML += ` ${element.value.login}`; | |||||
return item; | |||||
}; | |||||
this.awesomplete.filter = (site, input) => { | |||||
return ( | |||||
Awesomplete.FILTER_CONTAINS(site, input) || | |||||
Awesomplete.FILTER_CONTAINS(input, site) | |||||
); | |||||
}; | |||||
this.awesomplete.data = data => { | |||||
return { label: data.site, value: data }; | |||||
}; | |||||
this.awesomplete.replace = password => { | |||||
this.$refs.siteField.value = password.label; | |||||
if (password.value.suggestion) { | |||||
this.$emit("suggestionSelected", password.value.site); | |||||
} else { | |||||
this.$emit("passwordProfileSelected", password.value); | |||||
} | } | ||||
}, | |||||
mounted() { | |||||
this.awesomplete = new Awesomplete(this.$refs.siteField); | |||||
this.awesomplete.item = (element, input) => { | |||||
let item = Awesomplete.ITEM(element.value.site, input); | |||||
item.innerHTML += ` ${element.value.login}`; | |||||
return item; | |||||
}; | |||||
this.awesomplete.filter = (site, input) => { | |||||
return Awesomplete.FILTER_CONTAINS(site, input) || | |||||
Awesomplete.FILTER_CONTAINS(input, site); | |||||
}; | |||||
this.awesomplete.data = data => { | |||||
return {label: data.site, value: data} | |||||
}; | |||||
this.awesomplete.replace = password => { | |||||
this.$refs.siteField.value = password.label; | |||||
if (password.value.suggestion) { | |||||
this.$emit("suggestionSelected", password.value.site); | |||||
} else { | |||||
this.$emit("passwordProfileSelected", password.value); | |||||
} | |||||
}; | |||||
this.awesomplete.sort = (a,b) => { | |||||
return a.value.site.localeCompare(b.value.site) || | |||||
a.value.login.localeCompare(b.value.login); | |||||
}; | |||||
this.awesomplete.sort = (a, b) => { | |||||
return ( | |||||
a.value.site.localeCompare(b.value.site) || | |||||
a.value.login.localeCompare(b.value.login) | |||||
); | |||||
}; | |||||
}, | |||||
computed: { | |||||
site: { | |||||
get: function() { | |||||
return this.value; | |||||
}, | |||||
set: function(newValue) { | |||||
this.$emit("input", newValue); | |||||
} | } | ||||
}, | |||||
computed: { | |||||
site: { | |||||
get: function () { | |||||
return this.value; | |||||
}, | |||||
set: function (newValue) { | |||||
this.$emit("input", newValue); | |||||
} | |||||
} | |||||
}, | |||||
watch: { | |||||
site: function (newValue, _) { | |||||
const suggestions = getSuggestions(newValue).map(suggestion => { | |||||
return {site: suggestion, suggestion: true, login: ''} | |||||
}); | |||||
this.awesomplete.list = this.passwords.concat(suggestions); | |||||
} | |||||
}, | |||||
methods: {} | |||||
}; | |||||
} | |||||
}, | |||||
watch: { | |||||
site: function (newValue) { | |||||
const suggestions = getSuggestions(newValue).map(suggestion => { | |||||
return { site: suggestion, suggestion: true, login: "" }; | |||||
}); | |||||
this.awesomplete.list = this.passwords.concat(suggestions); | |||||
} | |||||
}, | |||||
methods: {} | |||||
}; | |||||
</script> | </script> |
@@ -1,141 +1,208 @@ | |||||
<style> | <style> | ||||
#fingerprint { | |||||
min-width: 90px; | |||||
text-align: center; | |||||
background-color: transparent; | |||||
color: white; | |||||
} | |||||
#fingerprint { | |||||
min-width: 90px; | |||||
text-align: center; | |||||
background-color: transparent; | |||||
color: white; | |||||
} | |||||
#fingerprint i { | |||||
color: black; | |||||
position: relative; | |||||
padding: 0; | |||||
text-shadow: 1px 1px 0 white; | |||||
font-size: 1.3em; | |||||
} | |||||
#fingerprint i { | |||||
color: black; | |||||
position: relative; | |||||
padding: 0; | |||||
text-shadow: 1px 1px 0 white; | |||||
font-size: 1.3em; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<div class="masterPassword"> | <div class="masterPassword"> | ||||
<div class="input-group inner-addon left-addon"> | <div class="input-group inner-addon left-addon"> | ||||
<label for="passwordField" class="sr-only"> | |||||
{{ label }} | |||||
</label> | |||||
<label for="passwordField" class="sr-only">{{ label }}</label> | |||||
<i class="fa fa-lock"></i> | <i class="fa fa-lock"></i> | ||||
<input id="passwordField" | |||||
name="passwordField" | |||||
ref="passwordField" | |||||
type="password" | |||||
class="form-control" | |||||
autocorrect="off" | |||||
autocapitalize="none" | |||||
v-bind:value="value" | |||||
v-bind:placeholder="label" | |||||
v-on:input="updateValue($event.target.value)" | |||||
v-on:keyup.enter="$emit('keyupEnter')"> | |||||
<span class="input-group-btn" | |||||
v-if="fingerprint && value" | |||||
v-on:click="togglePasswordType"> | |||||
<input | |||||
id="passwordField" | |||||
name="passwordField" | |||||
ref="passwordField" | |||||
type="password" | |||||
class="form-control" | |||||
autocorrect="off" | |||||
autocapitalize="none" | |||||
v-bind:value="value" | |||||
v-bind:placeholder="label" | |||||
v-on:input="updateValue($event.target.value)" | |||||
v-on:keyup.enter="$emit('keyupEnter')" | |||||
/> | |||||
<span class="input-group-btn" v-if="fingerprint && value" v-on:click="togglePasswordType"> | |||||
<button id="fingerprint" class="btn" type="button" tabindex="-1"> | <button id="fingerprint" class="btn" type="button" tabindex="-1"> | ||||
<small> | |||||
<i class="fa fa-fw" v-bind:class="[icon1]" v-bind:style="{ color: color1 }"></i> | |||||
<i class="fa fa-fw" v-bind:class="[icon2]" v-bind:style="{ color: color2 }"></i> | |||||
<i class="fa fa-fw" v-bind:class="[icon3]" v-bind:style="{ color: color3 }"></i> | |||||
</small> | |||||
<small> | |||||
<i class="fa fa-fw" v-bind:class="[icon1]" v-bind:style="{ color: color1 }"></i> | |||||
<i class="fa fa-fw" v-bind:class="[icon2]" v-bind:style="{ color: color2 }"></i> | |||||
<i class="fa fa-fw" v-bind:class="[icon3]" v-bind:style="{ color: color3 }"></i> | |||||
</small> | |||||
</button> | </button> | ||||
</span> | </span> | ||||
</div> | </div> | ||||
<button id="encryptMasterPassword__btn" | |||||
type="button" | |||||
class="btn btn-link btn-sm p-0" | |||||
v-if="showEncryptButton" | |||||
v-on:click="encryptMasterPassword()" | |||||
v-bind:class="{'disabled': email === ''}"> | |||||
<button | |||||
id="encryptMasterPassword__btn" | |||||
type="button" | |||||
class="btn btn-link btn-sm p-0" | |||||
v-if="showEncryptButton" | |||||
v-on:click="encryptMasterPassword()" | |||||
v-bind:class="{'disabled': email === ''}" | |||||
> | |||||
<small>{{ EncryptButtonText }}</small> | <small>{{ EncryptButtonText }}</small> | ||||
</button> | </button> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script> | <script> | ||||
import LessPass from 'lesspass'; | |||||
import debounce from 'lodash.debounce'; | |||||
import defaultPasswordProfile from '../store/defaultPassword'; | |||||
import LessPass from "lesspass"; | |||||
import { debounce } from "lodash"; | |||||
import defaultPasswordProfile from "../store/defaultPassword"; | |||||
export default { | |||||
name: 'masterPassword', | |||||
props: { | |||||
value: String, | |||||
label: String, | |||||
email: String, | |||||
showEncryptButton: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | |||||
EncryptButtonText: String | |||||
export default { | |||||
name: "masterPassword", | |||||
props: { | |||||
value: String, | |||||
label: String, | |||||
email: String, | |||||
showEncryptButton: { | |||||
type: Boolean, | |||||
default: false | |||||
}, | }, | ||||
data(){ | |||||
return { | |||||
fingerprint: null, | |||||
icon1: '', | |||||
icon2: '', | |||||
icon3: '', | |||||
color1: '', | |||||
color2: '', | |||||
color3: '' | |||||
EncryptButtonText: String | |||||
}, | |||||
data() { | |||||
return { | |||||
fingerprint: null, | |||||
icon1: "", | |||||
icon2: "", | |||||
icon3: "", | |||||
color1: "", | |||||
color2: "", | |||||
color3: "" | |||||
}; | |||||
}, | |||||
methods: { | |||||
updateValue(newPassword) { | |||||
const fakePassword = Math.random() | |||||
.toString(36) | |||||
.substring(7); | |||||
this.setFingerprint(fakePassword); | |||||
this.showRealFingerprint(newPassword); | |||||
this.$refs.passwordField.value = newPassword; | |||||
this.$emit("input", newPassword); | |||||
}, | |||||
togglePasswordType() { | |||||
const element = this.$refs.passwordField; | |||||
if (element.type === "password") { | |||||
element.type = "text"; | |||||
} else { | |||||
element.type = "password"; | |||||
} | } | ||||
}, | }, | ||||
methods: { | |||||
updateValue(newPassword){ | |||||
const fakePassword = Math.random().toString(36).substring(7); | |||||
this.setFingerprint(fakePassword); | |||||
this.showRealFingerprint(newPassword); | |||||
this.$refs.passwordField.value = newPassword; | |||||
this.$emit('input', newPassword); | |||||
}, | |||||
togglePasswordType(){ | |||||
const element = this.$refs.passwordField; | |||||
if (element.type === 'password') { | |||||
element.type = 'text'; | |||||
} else { | |||||
element.type = 'password'; | |||||
} | |||||
}, | |||||
hidePassword(){ | |||||
this.$refs.passwordField.type = 'password'; | |||||
}, | |||||
getColor(color) { | |||||
var colors = ['#000000', '#074750', '#009191', '#FF6CB6', '#FFB5DA', '#490092', '#006CDB', '#B66DFF', '#6DB5FE', '#B5DAFE', '#920000', '#924900', '#DB6D00', '#24FE23']; | |||||
var index = parseInt(color, 16) % colors.length; | |||||
return colors[index]; | |||||
}, | |||||
getIcon(hash) { | |||||
var icons = ['fa-hashtag', 'fa-heart', 'fa-hotel', 'fa-university', 'fa-plug', 'fa-ambulance', 'fa-bus', 'fa-car', 'fa-plane', 'fa-rocket', 'fa-ship', 'fa-subway', 'fa-truck', 'fa-jpy', 'fa-eur', 'fa-btc', 'fa-usd', 'fa-gbp', 'fa-archive', 'fa-area-chart', 'fa-bed', 'fa-beer', 'fa-bell', 'fa-binoculars', 'fa-birthday-cake', 'fa-bomb', 'fa-briefcase', 'fa-bug', 'fa-camera', 'fa-cart-plus', 'fa-certificate', 'fa-coffee', 'fa-cloud', 'fa-coffee', 'fa-comment', 'fa-cube', 'fa-cutlery', 'fa-database', 'fa-diamond', 'fa-exclamation-circle', 'fa-eye', 'fa-flag', 'fa-flask', 'fa-futbol-o', 'fa-gamepad', 'fa-graduation-cap']; | |||||
var index = parseInt(hash, 16) % icons.length; | |||||
return icons[index]; | |||||
}, | |||||
setFingerprint(password){ | |||||
LessPass.createFingerprint(password).then(fingerprint => { | |||||
this.icon1 = fingerprint[0].icon; | |||||
this.color1 = fingerprint[0].color; | |||||
hidePassword() { | |||||
this.$refs.passwordField.type = "password"; | |||||
}, | |||||
getColor(color) { | |||||
var colors = [ | |||||
"#000000", | |||||
"#074750", | |||||
"#009191", | |||||
"#FF6CB6", | |||||
"#FFB5DA", | |||||
"#490092", | |||||
"#006CDB", | |||||
"#B66DFF", | |||||
"#6DB5FE", | |||||
"#B5DAFE", | |||||
"#920000", | |||||
"#924900", | |||||
"#DB6D00", | |||||
"#24FE23" | |||||
]; | |||||
var index = parseInt(color, 16) % colors.length; | |||||
return colors[index]; | |||||
}, | |||||
getIcon(hash) { | |||||
var icons = [ | |||||
"fa-hashtag", | |||||
"fa-heart", | |||||
"fa-hotel", | |||||
"fa-university", | |||||
"fa-plug", | |||||
"fa-ambulance", | |||||
"fa-bus", | |||||
"fa-car", | |||||
"fa-plane", | |||||
"fa-rocket", | |||||
"fa-ship", | |||||
"fa-subway", | |||||
"fa-truck", | |||||
"fa-jpy", | |||||
"fa-eur", | |||||
"fa-btc", | |||||
"fa-usd", | |||||
"fa-gbp", | |||||
"fa-archive", | |||||
"fa-area-chart", | |||||
"fa-bed", | |||||
"fa-beer", | |||||
"fa-bell", | |||||
"fa-binoculars", | |||||
"fa-birthday-cake", | |||||
"fa-bomb", | |||||
"fa-briefcase", | |||||
"fa-bug", | |||||
"fa-camera", | |||||
"fa-cart-plus", | |||||
"fa-certificate", | |||||
"fa-coffee", | |||||
"fa-cloud", | |||||
"fa-coffee", | |||||
"fa-comment", | |||||
"fa-cube", | |||||
"fa-cutlery", | |||||
"fa-database", | |||||
"fa-diamond", | |||||
"fa-exclamation-circle", | |||||
"fa-eye", | |||||
"fa-flag", | |||||
"fa-flask", | |||||
"fa-futbol-o", | |||||
"fa-gamepad", | |||||
"fa-graduation-cap" | |||||
]; | |||||
var index = parseInt(hash, 16) % icons.length; | |||||
return icons[index]; | |||||
}, | |||||
setFingerprint(password) { | |||||
LessPass.createFingerprint(password).then(fingerprint => { | |||||
this.icon1 = fingerprint[0].icon; | |||||
this.color1 = fingerprint[0].color; | |||||
this.icon2 = fingerprint[1].icon; | |||||
this.color2 = fingerprint[1].color; | |||||
this.icon2 = fingerprint[1].icon; | |||||
this.color2 = fingerprint[1].color; | |||||
this.icon3 = fingerprint[2].icon; | |||||
this.color3 = fingerprint[2].color; | |||||
this.icon3 = fingerprint[2].icon; | |||||
this.color3 = fingerprint[2].color; | |||||
this.fingerprint = fingerprint; | |||||
}); | |||||
}, | |||||
showRealFingerprint: debounce(function(password) { | |||||
this.setFingerprint(password); | |||||
}, 500), | |||||
encryptMasterPassword(){ | |||||
const password = this.$refs.passwordField.value; | |||||
return LessPass | |||||
.generatePassword('lesspass.com', this.email, password, defaultPasswordProfile) | |||||
.then(generatedPassword => { | |||||
this.updateValue(generatedPassword); | |||||
}); | |||||
} | |||||
this.fingerprint = fingerprint; | |||||
}); | |||||
}, | |||||
showRealFingerprint: debounce(function(password) { | |||||
this.setFingerprint(password); | |||||
}, 500), | |||||
encryptMasterPassword() { | |||||
const password = this.$refs.passwordField.value; | |||||
return LessPass.generatePassword( | |||||
"lesspass.com", | |||||
this.email, | |||||
password, | |||||
defaultPasswordProfile | |||||
).then(generatedPassword => { | |||||
this.updateValue(generatedPassword); | |||||
}); | |||||
} | } | ||||
} | } | ||||
}; | |||||
</script> | </script> |
@@ -1,38 +1,41 @@ | |||||
<style> | <style> | ||||
.fade-enter-active { | |||||
transition: opacity .5s | |||||
} | |||||
.fade-enter-active { | |||||
transition: opacity 0.5s; | |||||
} | |||||
.fade-leave-active { | |||||
transition: opacity 2s | |||||
} | |||||
.fade-leave-active { | |||||
transition: opacity 2s; | |||||
} | |||||
.fade-enter, .fade-leave-to { | |||||
opacity: 0 | |||||
} | |||||
.fade-enter, | |||||
.fade-leave-to { | |||||
opacity: 0; | |||||
} | |||||
#message { | |||||
position: absolute; | |||||
top: 49px; | |||||
left: 0; | |||||
right: 0; | |||||
z-index: 20; | |||||
} | |||||
#message { | |||||
position: absolute; | |||||
top: 49px; | |||||
left: 0; | |||||
right: 0; | |||||
z-index: 20; | |||||
} | |||||
.close-notification { | |||||
float: right; | |||||
position: absolute; | |||||
top: 0; | |||||
right: 1em; | |||||
cursor: pointer; | |||||
} | |||||
.close-notification { | |||||
float: right; | |||||
position: absolute; | |||||
top: 0; | |||||
right: 1em; | |||||
cursor: pointer; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<div id="message" v-on:click="keepMessage"> | <div id="message" v-on:click="keepMessage"> | ||||
<transition name="fade"> | <transition name="fade"> | ||||
<div v-if="message.text"> | <div v-if="message.text"> | ||||
<div class="card-header text-white" | |||||
v-bind:class="{ 'bg-warning': message.status==='warning', 'bg-danger': message.status==='error', 'bg-success': message.status==='success' }"> | |||||
<div | |||||
class="card-header text-white" | |||||
v-bind:class="{ 'bg-warning': message.status==='warning', 'bg-danger': message.status==='error', 'bg-success': message.status==='success' }" | |||||
> | |||||
<div class="row"> | <div class="row"> | ||||
<div class="col-12"> | <div class="col-12"> | ||||
<small>{{message.text}}</small> | <small>{{message.text}}</small> | ||||
@@ -47,20 +50,18 @@ | |||||
</div> | </div> | ||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | <script type="text/ecmascript-6"> | ||||
import {mapState} from 'vuex'; | |||||
import message from '../services/message'; | |||||
import { mapState } from "vuex"; | |||||
import message from "../services/message"; | |||||
export default { | |||||
computed: mapState([ | |||||
'message' | |||||
]), | |||||
methods: { | |||||
keepMessage(){ | |||||
message.keepMessage(); | |||||
}, | |||||
hideMessage(){ | |||||
message.hideMessage(); | |||||
} | |||||
export default { | |||||
computed: mapState(["message"]), | |||||
methods: { | |||||
keepMessage() { | |||||
message.keepMessage(); | |||||
}, | |||||
hideMessage() { | |||||
message.hideMessage(); | |||||
} | } | ||||
} | } | ||||
}; | |||||
</script> | </script> |
@@ -109,7 +109,6 @@ | |||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | <script type="text/ecmascript-6"> | ||||
import message from '../services/message'; | |||||
import {increment, decrement} from "../services/form-validator"; | import {increment, decrement} from "../services/form-validator"; | ||||
import { mapState} from 'vuex'; | import { mapState} from 'vuex'; | ||||
@@ -1,23 +1,23 @@ | |||||
<style> | <style> | ||||
.passwordProfile { | |||||
display: flex; | |||||
cursor: pointer; | |||||
margin-bottom: 1rem; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
} | |||||
.passwordProfile { | |||||
display: flex; | |||||
cursor: pointer; | |||||
margin-bottom: 1rem; | |||||
justify-content: space-between; | |||||
align-items: center; | |||||
} | |||||
.passwordProfile__info { | |||||
display: flex; | |||||
align-items: center; | |||||
flex-grow: 1; | |||||
} | |||||
.passwordProfile__info { | |||||
display: flex; | |||||
align-items: center; | |||||
flex-grow: 1; | |||||
} | |||||
.passwordProfile__meta { | |||||
font-size: 0.8rem; | |||||
line-height: 1rem; | |||||
flex-grow: 1; | |||||
} | |||||
.passwordProfile__meta { | |||||
font-size: 0.8rem; | |||||
line-height: 1rem; | |||||
flex-grow: 1; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<div class="passwordProfile"> | <div class="passwordProfile"> | ||||
@@ -25,39 +25,41 @@ | |||||
<avatar v-bind:name="password.site"></avatar> | <avatar v-bind:name="password.site"></avatar> | ||||
<div class="passwordProfile__meta"> | <div class="passwordProfile__meta"> | ||||
<b>{{password.site}}</b> | <b>{{password.site}}</b> | ||||
<br> | |||||
<br /> | |||||
{{password.login}} | {{password.login}} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="passwordProfile__actions"> | <div class="passwordProfile__actions"> | ||||
<i class="passwordProfile__delete-icon fa fa-trash fa-fw text-danger" | |||||
v-on:click="deletePassword()"></i> | |||||
<i | |||||
class="passwordProfile__delete-icon fa fa-trash fa-fw text-danger" | |||||
v-on:click="deletePassword()" | |||||
></i> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | <script type="text/ecmascript-6"> | ||||
import Avatar from './Avatar.vue'; | |||||
import Avatar from "./Avatar.vue"; | |||||
export default { | |||||
name: 'passwordProfile', | |||||
props: { | |||||
password: { | |||||
type: Object, | |||||
required: true | |||||
} | |||||
}, | |||||
components: { | |||||
Avatar | |||||
export default { | |||||
name: "passwordProfile", | |||||
props: { | |||||
password: { | |||||
type: Object, | |||||
required: true | |||||
} | |||||
}, | |||||
components: { | |||||
Avatar | |||||
}, | |||||
methods: { | |||||
deletePassword() { | |||||
this.$store.dispatch("deletePassword", { id: this.password.id }); | |||||
}, | }, | ||||
methods: { | |||||
deletePassword() { | |||||
this.$store.dispatch('deletePassword', {id: this.password.id}); | |||||
}, | |||||
setPassword() { | |||||
this.$store.dispatch('savePassword', {password: this.password}); | |||||
this.$router.push({name: 'home'}); | |||||
} | |||||
setPassword() { | |||||
this.$store.dispatch("savePassword", { password: this.password }); | |||||
this.$router.push({ name: "home" }); | |||||
} | } | ||||
} | } | ||||
}; | |||||
</script> | </script> | ||||
@@ -1,10 +1,14 @@ | |||||
import Vue from "vue"; | import Vue from "vue"; | ||||
import "./images/favicon.ico"; | |||||
import LessPass from "./LessPass.vue"; | |||||
import Polyglot from "vue-polyglot"; | |||||
import { sync } from "vuex-router-sync"; | import { sync } from "vuex-router-sync"; | ||||
import LessPass from "./LessPass.vue"; | |||||
import store from "./store"; | import store from "./store"; | ||||
import router from "./router"; | import router from "./router"; | ||||
import Polyglot from "vue-polyglot"; | |||||
import "bootstrap/dist/css/bootstrap.css"; | |||||
import "font-awesome/css/font-awesome.css"; | |||||
import "balloon-css/balloon.css"; | |||||
import "awesomplete/awesomplete.css"; | |||||
import frLocales from "./i18n/fr.json"; | import frLocales from "./i18n/fr.json"; | ||||
import esLocales from "./i18n/es.json"; | import esLocales from "./i18n/es.json"; | ||||
@@ -31,9 +35,10 @@ Vue.locales({ | |||||
sync(store, router); | sync(store, router); | ||||
Vue.config.productionTip = true; | |||||
new Vue({ | new Vue({ | ||||
el: "#lesspass", | |||||
store, | store, | ||||
router, | router, | ||||
render: h => h(LessPass) | render: h => h(LessPass) | ||||
}); | |||||
}).$mount("#lesspass"); |
@@ -6,14 +6,14 @@ import PasswordGenerator from "./views/PasswordGenerator.vue"; | |||||
import PasswordReset from "./views/PasswordReset.vue"; | import PasswordReset from "./views/PasswordReset.vue"; | ||||
import PasswordResetConfirm from "./views/PasswordResetConfirm.vue"; | import PasswordResetConfirm from "./views/PasswordResetConfirm.vue"; | ||||
import Passwords from "./views/Passwords.vue"; | import Passwords from "./views/Passwords.vue"; | ||||
import OptionsPage from "./views/OptionsPage.vue"; | |||||
import SettingsPage from "./views/Settings.vue"; | |||||
Vue.use(VueRouter); | Vue.use(VueRouter); | ||||
const routes = [ | const routes = [ | ||||
{ path: "/", name: "home", component: PasswordGenerator }, | { path: "/", name: "home", component: PasswordGenerator }, | ||||
{ path: "/login", name: "login", component: Login }, | { path: "/login", name: "login", component: Login }, | ||||
{ path: "/options", name: "options", component: OptionsPage }, | |||||
{ path: "/settings", name: "settings", component: SettingsPage }, | |||||
{ path: "/passwords/", name: "passwords", component: Passwords }, | { path: "/passwords/", name: "passwords", component: Passwords }, | ||||
{ path: "/password/reset", name: "passwordReset", component: PasswordReset }, | { path: "/password/reset", name: "passwordReset", component: PasswordReset }, | ||||
{ | { | ||||
@@ -6,7 +6,7 @@ export function cleanUrl(url) { | |||||
if (!url) { | if (!url) { | ||||
return ""; | return ""; | ||||
} | } | ||||
var matchesDomainName = url.match(/^(?:https?\:\/\/)([^\/?#]+)(?:[\/?#]|$)/i); | |||||
var matchesDomainName = url.match(/^(?:https?:\/\/)([^/?#]+)(?:[/?#]|$)/i); | |||||
return matchesDomainName && matchesDomainName[1] ? matchesDomainName[1] : ""; | return matchesDomainName && matchesDomainName[1] ? matchesDomainName[1] : ""; | ||||
} | } | ||||
@@ -33,7 +33,7 @@ export const savePassword = ({ commit }, payload) => { | |||||
commit(types.SET_PASSWORD, payload); | commit(types.SET_PASSWORD, payload); | ||||
}; | }; | ||||
export const resetPassword = ({ commit, state }) => { | |||||
export const resetPassword = ({ commit }) => { | |||||
commit(types.RESET_PASSWORD); | commit(types.RESET_PASSWORD); | ||||
}; | }; | ||||
@@ -1,37 +1,41 @@ | |||||
<style> | <style> | ||||
#signInButton { | |||||
border-right: none; | |||||
} | |||||
#signInButton { | |||||
border-right: none; | |||||
} | |||||
#registerButton { | |||||
border-left: none; | |||||
} | |||||
#registerButton { | |||||
border-left: none; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<form v-on:submit.prevent="signIn"> | <form v-on:submit.prevent="signIn"> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<div class="inner-addon left-addon"> | <div class="inner-addon left-addon"> | ||||
<i class="fa fa-globe"></i> | <i class="fa fa-globe"></i> | ||||
<input id="baseURL" | |||||
type="text" | |||||
class="form-control" | |||||
autocapitalize="none" | |||||
v-bind:placeholder="$t('LessPass Database Url')" | |||||
v-model="baseURL"> | |||||
<input | |||||
id="baseURL" | |||||
type="text" | |||||
class="form-control" | |||||
autocapitalize="none" | |||||
v-bind:placeholder="$t('LessPass Database Url')" | |||||
v-model="baseURL" | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group row"> | <div class="form-group row"> | ||||
<div class="col-12"> | <div class="col-12"> | ||||
<div class="inner-addon left-addon"> | <div class="inner-addon left-addon"> | ||||
<i class="fa fa-user"></i> | <i class="fa fa-user"></i> | ||||
<input id="email" | |||||
class="form-control" | |||||
name="username" | |||||
type="email" | |||||
autocapitalize="none" | |||||
v-bind:placeholder="$t('Email')" | |||||
required | |||||
v-model="email"> | |||||
<input | |||||
id="email" | |||||
class="form-control" | |||||
name="username" | |||||
type="email" | |||||
autocapitalize="none" | |||||
v-bind:placeholder="$t('Email')" | |||||
required | |||||
v-model="email" | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -41,96 +45,134 @@ | |||||
v-bind:label="$t('Master Password')" | v-bind:label="$t('Master Password')" | ||||
v-bind:email="email" | v-bind:email="email" | ||||
v-bind:showEncryptButton="true" | v-bind:showEncryptButton="true" | ||||
v-bind:EncryptButtonText="$t('Encrypt my master password')"></master-password> | |||||
v-bind:EncryptButtonText="$t('Encrypt my master password')" | |||||
></master-password> | |||||
</div> | </div> | ||||
<div class="form-group row no-gutters mb-0"> | <div class="form-group row no-gutters mb-0"> | ||||
<div class="col"> | <div class="col"> | ||||
<button id="signInButton" class="btn btn-primary btn-block"> | |||||
{{$t('Sign In')}} | |||||
</button> | |||||
<button id="signInButton" class="btn btn-primary btn-block">{{$t('Sign In')}}</button> | |||||
</div> | </div> | ||||
<div class="col"> | <div class="col"> | ||||
<button id="registerButton" class="btn btn-secondary btn-block" type="button" v-on:click="register"> | |||||
{{$t('Register')}} | |||||
</button> | |||||
<button | |||||
id="registerButton" | |||||
class="btn btn-secondary btn-block" | |||||
type="button" | |||||
v-on:click="register" | |||||
>{{$t('Register')}}</button> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group mb-0"> | <div class="form-group mb-0"> | ||||
<button id="login__forgot-password-btn" | |||||
type="button" | |||||
class="btn btn-link btn-sm p-0" | |||||
v-on:click="$router.push({name: 'passwordReset'})"> | |||||
<button | |||||
id="login__forgot-password-btn" | |||||
type="button" | |||||
class="btn btn-link btn-sm p-0" | |||||
v-on:click="$router.push({name: 'passwordReset'})" | |||||
> | |||||
<small>{{$t('ForgotPassword', 'Forgot your password?')}}</small> | <small>{{$t('ForgotPassword', 'Forgot your password?')}}</small> | ||||
</button> | </button> | ||||
</div> | </div> | ||||
</form> | </form> | ||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | <script type="text/ecmascript-6"> | ||||
import User from '../api/user'; | |||||
import MasterPassword from '../components/MasterPassword.vue'; | |||||
import message from '../services/message'; | |||||
import User from "../api/user"; | |||||
import MasterPassword from "../components/MasterPassword.vue"; | |||||
import message from "../services/message"; | |||||
export default { | |||||
data() { | |||||
return { | |||||
email: '', | |||||
password: '', | |||||
baseURL: 'https://lesspass.com' | |||||
}; | |||||
export default { | |||||
data() { | |||||
return { | |||||
email: "", | |||||
password: "", | |||||
baseURL: "https://lesspass.com" | |||||
}; | |||||
}, | |||||
components: { | |||||
MasterPassword | |||||
}, | |||||
methods: { | |||||
formIsValid() { | |||||
if (!this.email || !this.password || !this.baseURL) { | |||||
message.error( | |||||
this.$t( | |||||
"LoginFormInvalid", | |||||
"LessPass URL, email, and password are mandatory" | |||||
) | |||||
); | |||||
return false; | |||||
} | |||||
return true; | |||||
}, | }, | ||||
components: { | |||||
MasterPassword | |||||
signIn() { | |||||
if (this.formIsValid()) { | |||||
const baseURL = this.baseURL; | |||||
User.login({ email: this.email, password: this.password }, { baseURL }) | |||||
.then(response => { | |||||
this.$store.dispatch("login", { token: response.token, baseURL }); | |||||
this.$router.push({ name: "home" }); | |||||
}) | |||||
.catch(err => { | |||||
if ( | |||||
err.response === undefined && | |||||
baseURL !== "https://lesspass.com" | |||||
) { | |||||
message.error( | |||||
this.$t("DBNotRunning", "Your LessPass Database is not running") | |||||
); | |||||
} else if (err.response && err.response.status === 400) { | |||||
message.error( | |||||
this.$t( | |||||
"LoginIncorrectError", | |||||
"The email and password you entered did not match our records. Please double-check and try again." | |||||
) | |||||
); | |||||
} else { | |||||
message.displayGenericError(); | |||||
} | |||||
}); | |||||
} | |||||
}, | }, | ||||
methods: { | |||||
formIsValid(){ | |||||
if (!this.email || !this.password || !this.baseURL) { | |||||
message.error(this.$t('LoginFormInvalid', 'LessPass URL, email, and password are mandatory')); | |||||
return false; | |||||
} | |||||
return true; | |||||
}, | |||||
signIn(){ | |||||
if (this.formIsValid()) { | |||||
const baseURL = this.baseURL; | |||||
User.login({email: this.email, password: this.password}, {baseURL}) | |||||
.then(response => { | |||||
this.$store.dispatch('login', {token: response.token, baseURL}); | |||||
this.$router.push({name: 'home'}); | |||||
}) | |||||
.catch(err => { | |||||
if (err.response === undefined && baseURL !== "https://lesspass.com") { | |||||
message.error(this.$t('DBNotRunning', 'Your LessPass Database is not running')); | |||||
} else if (err.response && err.response.status === 400) { | |||||
message.error(this.$t('LoginIncorrectError', 'The email and password you entered did not match our records. Please double-check and try again.')); | |||||
} else { | |||||
message.displayGenericError(); | |||||
register() { | |||||
if (this.formIsValid()) { | |||||
const baseURL = this.baseURL; | |||||
User.register( | |||||
{ email: this.email, password: this.password }, | |||||
{ baseURL } | |||||
) | |||||
.then(() => { | |||||
message.success( | |||||
this.$t( | |||||
"WelcomeRegister", | |||||
"Welcome {email}, thank you for signing up.", | |||||
{ email: this.email } | |||||
) | |||||
); | |||||
this.signIn(); | |||||
}) | |||||
.catch(err => { | |||||
if ( | |||||
err.response && | |||||
typeof err.response.data.email !== "undefined" | |||||
) { | |||||
if (err.response.data.email[0].indexOf("already exists") !== -1) { | |||||
message.error( | |||||
this.$t( | |||||
"EmailAlreadyExist", | |||||
"This email is already registered. Want to login or recover your password?" | |||||
) | |||||
); | |||||
} | } | ||||
}); | |||||
} | |||||
}, | |||||
register(){ | |||||
if (this.formIsValid()) { | |||||
const baseURL = this.baseURL; | |||||
User.register({email: this.email, password: this.password}, {baseURL}) | |||||
.then(() => { | |||||
message.success(this.$t('WelcomeRegister', 'Welcome {email}, thank you for signing up.', {email: this.email})); | |||||
this.signIn(); | |||||
}) | |||||
.catch(err => { | |||||
if (err.response && typeof err.response.data.email !== 'undefined') { | |||||
if (err.response.data.email[0].indexOf('already exists') !== -1) { | |||||
message.error(this.$t('EmailAlreadyExist', 'This email is already registered. Want to login or recover your password?')); | |||||
} | |||||
if (err.response.data.email[0].indexOf('valid email') !== -1) { | |||||
message.error(this.$t('EmailInvalid', 'Please enter a valid email')); | |||||
} | |||||
} else { | |||||
message.displayGenericError(); | |||||
if (err.response.data.email[0].indexOf("valid email") !== -1) { | |||||
message.error( | |||||
this.$t("EmailInvalid", "Please enter a valid email") | |||||
); | |||||
} | } | ||||
}); | |||||
} | |||||
} else { | |||||
message.displayGenericError(); | |||||
} | |||||
}); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
}; | |||||
</script> | </script> | ||||
@@ -14,87 +14,104 @@ div.awesomplete > ul { | |||||
<template> | <template> | ||||
<form id="password-generator" v-on:submit.prevent="generatePassword" novalidate> | <form id="password-generator" v-on:submit.prevent="generatePassword" novalidate> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<input-site ref="site" | |||||
v-model="password.site" | |||||
v-bind:passwords="passwords" | |||||
v-bind:label="$t('Site')" | |||||
v-on:suggestionSelected="setSite" | |||||
v-on:passwordProfileSelected="setPasswordProfile"></input-site> | |||||
<input-site | |||||
ref="site" | |||||
v-model="password.site" | |||||
v-bind:passwords="passwords" | |||||
v-bind:label="$t('Site')" | |||||
v-on:suggestionSelected="setSite" | |||||
v-on:passwordProfileSelected="setPasswordProfile" | |||||
></input-site> | |||||
</div> | </div> | ||||
<remove-auto-complete></remove-auto-complete> | <remove-auto-complete></remove-auto-complete> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<label for="login" class="sr-only">{{ $t('Login') }}</label> | <label for="login" class="sr-only">{{ $t('Login') }}</label> | ||||
<div class="inner-addon left-addon"> | <div class="inner-addon left-addon"> | ||||
<i class="fa fa-user"></i> | <i class="fa fa-user"></i> | ||||
<input id="login" | |||||
type="text" | |||||
name="login" | |||||
ref="login" | |||||
class="form-control" | |||||
autocomplete="off" | |||||
autocorrect="off" | |||||
autocapitalize="none" | |||||
v-bind:placeholder="$t('Login')" | |||||
v-model="password.login"> | |||||
<input | |||||
id="login" | |||||
type="text" | |||||
name="login" | |||||
ref="login" | |||||
class="form-control" | |||||
autocomplete="off" | |||||
autocorrect="off" | |||||
autocapitalize="none" | |||||
v-bind:placeholder="$t('Login')" | |||||
v-model="password.login" | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="form-group"> | <div class="form-group"> | ||||
<master-password ref="masterPassword" | |||||
v-model="masterPassword" | |||||
v-on:generatePassword="generatePassword" | |||||
v-bind:label="$t('Master Password')"></master-password> | |||||
<master-password | |||||
ref="masterPassword" | |||||
v-model="masterPassword" | |||||
v-on:generatePassword="generatePassword" | |||||
v-bind:label="$t('Master Password')" | |||||
></master-password> | |||||
</div> | </div> | ||||
<div class="form-group" | |||||
v-bind:class="{ 'mb-0': !showOptions }"> | |||||
<div class="form-group" v-bind:class="{ 'mb-0': !showOptions }"> | |||||
<div v-if="!passwordGenerated"> | <div v-if="!passwordGenerated"> | ||||
<button id="generatePassword__btn" | |||||
type="submit" | |||||
class="btn btn-primary border-blue"> | |||||
{{ $t('Generate') }} | |||||
</button> | |||||
<button type="button" | |||||
class="btn btn-secondary pull-right showOptions__btn" | |||||
v-show="!passwordGenerated" | |||||
v-on:click="showOptions =! showOptions"> | |||||
<button | |||||
id="generatePassword__btn" | |||||
type="submit" | |||||
class="btn btn-primary border-blue" | |||||
>{{ $t('Generate') }}</button> | |||||
<button | |||||
type="button" | |||||
class="btn btn-secondary pull-right showOptions__btn" | |||||
v-show="!passwordGenerated" | |||||
v-on:click="showOptions =! showOptions" | |||||
> | |||||
<i class="fa fa-sliders"></i> | <i class="fa fa-sliders"></i> | ||||
</button> | </button> | ||||
</div> | </div> | ||||
<div class="btn-group" v-show="passwordGenerated"> | <div class="btn-group" v-show="passwordGenerated"> | ||||
<div class="input-group"> | <div class="input-group"> | ||||
<span class="input-group-btn"> | <span class="input-group-btn"> | ||||
<button id="copyPasswordButton" | |||||
class="btn btn-primary border-blue" | |||||
type="button" | |||||
v-on:click="copyPassword()"> | |||||
<button | |||||
id="copyPasswordButton" | |||||
class="btn btn-primary border-blue" | |||||
type="button" | |||||
v-on:click="copyPassword()" | |||||
> | |||||
<i class="fa fa-clipboard"></i> | <i class="fa fa-clipboard"></i> | ||||
</button> | </button> | ||||
</span> | </span> | ||||
<input id="generated-password" | |||||
type="password" | |||||
class="form-control" | |||||
tabindex="-1" | |||||
ref="passwordGenerated" | |||||
v-bind:value="passwordGenerated"> | |||||
<input | |||||
id="generated-password" | |||||
type="password" | |||||
class="form-control" | |||||
tabindex="-1" | |||||
ref="passwordGenerated" | |||||
v-bind:value="passwordGenerated" | |||||
/> | |||||
<span class="input-group-btn"> | <span class="input-group-btn"> | ||||
<button id="revealGeneratedPassword" | |||||
type="button" | |||||
class="btn btn-secondary" | |||||
v-on:click="togglePasswordType($refs.passwordGenerated)"> | |||||
<button | |||||
id="revealGeneratedPassword" | |||||
type="button" | |||||
class="btn btn-secondary" | |||||
v-on:click="togglePasswordType($refs.passwordGenerated)" | |||||
> | |||||
<i class="fa fa-eye"></i> | <i class="fa fa-eye"></i> | ||||
</button> | </button> | ||||
</span> | </span> | ||||
<span class="input-group-btn"> | <span class="input-group-btn"> | ||||
<button id="sharePasswordProfileButton" | |||||
type="button" | |||||
class="btn btn-secondary" | |||||
v-on:click="sharePasswordProfile()"> | |||||
<button | |||||
id="sharePasswordProfileButton" | |||||
type="button" | |||||
class="btn btn-secondary" | |||||
v-on:click="sharePasswordProfile()" | |||||
> | |||||
<i class="fa fa-share-alt pointer"></i> | <i class="fa fa-share-alt pointer"></i> | ||||
</button> | </button> | ||||
</span> | </span> | ||||
<span class="input-group-btn"> | <span class="input-group-btn"> | ||||
<button type="button" | |||||
class="btn btn-secondary showOptions__btn" | |||||
v-on:click="showOptions =! showOptions"> | |||||
<button | |||||
type="button" | |||||
class="btn btn-secondary showOptions__btn" | |||||
v-on:click="showOptions =! showOptions" | |||||
> | |||||
<i class="fa fa-sliders"></i> | <i class="fa fa-sliders"></i> | ||||
</button> | </button> | ||||
</span> | </span> | ||||
@@ -114,7 +131,6 @@ import InputSite from "../components/InputSite.vue"; | |||||
import Options from "../components/Options.vue"; | import Options from "../components/Options.vue"; | ||||
import { showTooltip, hideTooltip } from "../services/tooltip"; | import { showTooltip, hideTooltip } from "../services/tooltip"; | ||||
import message from "../services/message"; | import message from "../services/message"; | ||||
import Awesomplete from "awesomplete"; | |||||
import * as urlParser from "../services/url-parser"; | import * as urlParser from "../services/url-parser"; | ||||
export default { | export default { | ||||
@@ -159,7 +175,7 @@ export default { | |||||
}, | }, | ||||
deep: true | deep: true | ||||
}, | }, | ||||
masterPassword: function (newMasterPassword, oldMasterPassword) { | |||||
masterPassword: function(newMasterPassword) { | |||||
this.masterPassword = newMasterPassword; | this.masterPassword = newMasterPassword; | ||||
this.cleanErrors(); | this.cleanErrors(); | ||||
} | } | ||||
@@ -199,7 +215,7 @@ export default { | |||||
return; | return; | ||||
} | } | ||||
const length = this.password.length; | const length = this.password.length; | ||||
if (length > 35){ | |||||
if (length > 35) { | |||||
message.warning( | message.warning( | ||||
this.$t( | this.$t( | ||||
"LengthDeprecationWarning", | "LengthDeprecationWarning", | ||||
@@ -235,9 +251,8 @@ export default { | |||||
if (site && !site.value) return void site.focus(); | if (site && !site.value) return void site.focus(); | ||||
if (login && !login.value) return void login.focus(); | if (login && !login.value) return void login.focus(); | ||||
masterPassword.$refs.passwordField.focus(); | masterPassword.$refs.passwordField.focus(); | ||||
} | |||||
catch(err) { | |||||
console.error("Can't focus password field") | |||||
} catch (err) { | |||||
console.error("Can't focus password field"); | |||||
} | } | ||||
}, | }, | ||||
copyPassword() { | copyPassword() { | ||||
@@ -248,10 +263,7 @@ export default { | |||||
setTimeout(() => hideTooltip(element), 2000); | setTimeout(() => hideTooltip(element), 2000); | ||||
} else { | } else { | ||||
message.warning( | message.warning( | ||||
this.$t( | |||||
"SorryCopy", | |||||
"Sorry, copying only works in modern browsers." | |||||
) | |||||
this.$t("SorryCopy", "Sorry, copying only works in modern browsers.") | |||||
); | ); | ||||
} | } | ||||
}, | }, | ||||
@@ -267,15 +279,12 @@ export default { | |||||
setTimeout(() => hideTooltip(element), 2000); | setTimeout(() => hideTooltip(element), 2000); | ||||
} else { | } else { | ||||
message.warning( | message.warning( | ||||
this.$t( | |||||
"SorryCopy", | |||||
"Sorry, copying only works in modern browsers." | |||||
) | |||||
this.$t("SorryCopy", "Sorry, copying only works in modern browsers.") | |||||
); | ); | ||||
} | } | ||||
}, | }, | ||||
setSite(site){ | |||||
this.password.site = site | |||||
setSite(site) { | |||||
this.password.site = site; | |||||
}, | }, | ||||
setPasswordProfile(passwordProfile) { | setPasswordProfile(passwordProfile) { | ||||
this.$store | this.$store | ||||
@@ -1,15 +1,15 @@ | |||||
<style> | <style> | ||||
#passwords__list { | |||||
min-height: 11rem; | |||||
} | |||||
#passwords__list { | |||||
min-height: 11rem; | |||||
} | |||||
#passwords__pagination .pagination { | |||||
margin-bottom: 0; | |||||
} | |||||
#passwords__pagination .pagination { | |||||
margin-bottom: 0; | |||||
} | |||||
#passwords__pagination .page-link { | |||||
cursor: pointer; | |||||
} | |||||
#passwords__pagination .page-link { | |||||
cursor: pointer; | |||||
} | |||||
</style> | </style> | ||||
<template> | <template> | ||||
<div id="passwords"> | <div id="passwords"> | ||||
@@ -18,7 +18,13 @@ | |||||
<div class="col"> | <div class="col"> | ||||
<div class="inner-addon left-addon"> | <div class="inner-addon left-addon"> | ||||
<i class="fa fa-search"></i> | <i class="fa fa-search"></i> | ||||
<input class="form-control" type="text" name="search" :placeholder="$t('Search')" v-model="searchQuery"> | |||||
<input | |||||
class="form-control" | |||||
type="text" | |||||
name="search" | |||||
:placeholder="$t('Search')" | |||||
v-model="searchQuery" | |||||
/> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -28,7 +34,9 @@ | |||||
<div class="row"> | <div class="row"> | ||||
<div class="col"> | <div class="col"> | ||||
{{$t('NoPassword', "You don't have any password profile saved in your database.")}} | {{$t('NoPassword', "You don't have any password profile saved in your database.")}} | ||||
<router-link :to="{ name: 'home'}">{{$t('CreatePassword', 'Would you like to create one?')}}</router-link> | |||||
<router-link | |||||
:to="{ name: 'home'}" | |||||
>{{$t('CreatePassword', 'Would you like to create one?')}}</router-link> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -44,7 +52,8 @@ | |||||
v-bind:password="password" | v-bind:password="password" | ||||
v-on:deleted="pagination.currentPage=1" | v-on:deleted="pagination.currentPage=1" | ||||
v-for="password in filteredPasswords" | v-for="password in filteredPasswords" | ||||
:key="password.id"></password-profile> | |||||
:key="password.id" | |||||
></password-profile> | |||||
</div> | </div> | ||||
<div id="passwords__pagination" v-if="pagination.pageCount > 1"> | <div id="passwords__pagination" v-if="pagination.pageCount > 1"> | ||||
<paginate | <paginate | ||||
@@ -60,51 +69,54 @@ | |||||
:prev-link-class="'page-link'" | :prev-link-class="'page-link'" | ||||
:next-link-class="'page-link'" | :next-link-class="'page-link'" | ||||
:prev-text="$t('Previous')" | :prev-text="$t('Previous')" | ||||
:next-text="$t('Next')"> | |||||
</paginate> | |||||
:next-text="$t('Next')" | |||||
></paginate> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script type="text/ecmascript-6"> | <script type="text/ecmascript-6"> | ||||
import PasswordProfile from '../components/PasswordProfile.vue'; | |||||
import {mapState} from 'vuex'; | |||||
import Paginate from 'vuejs-paginate'; | |||||
import PasswordProfile from "../components/PasswordProfile.vue"; | |||||
import { mapState } from "vuex"; | |||||
import Paginate from "vuejs-paginate"; | |||||
export default { | |||||
name: 'passwords-view', | |||||
data() { | |||||
return { | |||||
searchQuery: '', | |||||
pagination: { | |||||
pageCount: 1, | |||||
perPage: 4, | |||||
currentPage: 1 | |||||
}, | |||||
export default { | |||||
name: "passwords-view", | |||||
data() { | |||||
return { | |||||
searchQuery: "", | |||||
pagination: { | |||||
pageCount: 1, | |||||
perPage: 4, | |||||
currentPage: 1 | |||||
} | } | ||||
}, | |||||
components: { | |||||
PasswordProfile, | |||||
Paginate | |||||
}, | |||||
computed: { | |||||
...mapState(['passwords']), | |||||
filteredPasswords() { | |||||
const passwords = this.passwords.filter(password => { | |||||
var loginMatch = password.login.match(new RegExp(this.searchQuery, 'i')); | |||||
var siteMatch = password.site.match(new RegExp(this.searchQuery, 'i')); | |||||
return loginMatch || siteMatch; | |||||
}); | |||||
this.pagination.pageCount = Math.ceil(passwords.length / this.pagination.perPage); | |||||
return passwords.slice( | |||||
this.pagination.currentPage * this.pagination.perPage - this.pagination.perPage, | |||||
this.pagination.currentPage * this.pagination.perPage | |||||
}; | |||||
}, | |||||
components: { | |||||
PasswordProfile, | |||||
Paginate | |||||
}, | |||||
computed: { | |||||
...mapState(["passwords"]), | |||||
filteredPasswords() { | |||||
const passwords = this.passwords.filter(password => { | |||||
var loginMatch = password.login.match( | |||||
new RegExp(this.searchQuery, "i") | |||||
); | ); | ||||
} | |||||
}, | |||||
methods: { | |||||
setCurrentPage(page) { | |||||
this.pagination.currentPage = page; | |||||
} | |||||
var siteMatch = password.site.match(new RegExp(this.searchQuery, "i")); | |||||
return loginMatch || siteMatch; | |||||
}); | |||||
this.pagination.pageCount = Math.ceil(passwords.length / this.pagination.perPage); | |||||
return passwords.slice( | |||||
this.pagination.currentPage * this.pagination.perPage - | |||||
this.pagination.perPage, | |||||
this.pagination.currentPage * this.pagination.perPage | |||||
); | |||||
} | |||||
}, | |||||
methods: { | |||||
setCurrentPage(page) { | |||||
this.pagination.currentPage = page; | |||||
} | } | ||||
} | } | ||||
}; | |||||
</script> | </script> |
@@ -24,7 +24,6 @@ | |||||
<script> | <script> | ||||
import { mapState } from "vuex"; | import { mapState } from "vuex"; | ||||
import { SET_DEFAULT_OPTIONS } from "../store/mutation-types"; | |||||
export default { | export default { | ||||
computed: mapState(["defaultPassword"]), | computed: mapState(["defaultPassword"]), |
@@ -1,55 +0,0 @@ | |||||
const path = require("path"); | |||||
const webpack = require("webpack"); | |||||
const HtmlWebpackPlugin = require("html-webpack-plugin"); | |||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); | |||||
const VueLoaderPlugin = require("vue-loader/lib/plugin"); | |||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); | |||||
const productionMode = process.env.NODE_ENV === "production"; | |||||
module.exports = { | |||||
entry: "./src/main.js", | |||||
resolve: { | |||||
modules: [path.resolve(__dirname, "src"), "node_modules"] | |||||
}, | |||||
plugins: [ | |||||
new MiniCssExtractPlugin({ filename: "lesspass.min.css" }), | |||||
new CleanWebpackPlugin(), | |||||
new HtmlWebpackPlugin({ template: "./src/index.html", inject: "body" }), | |||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), | |||||
new VueLoaderPlugin() | |||||
], | |||||
output: { | |||||
filename: "lesspass.min.js", | |||||
path: path.resolve(__dirname, "dist") | |||||
}, | |||||
module: { | |||||
rules: [ | |||||
{ | |||||
test: /\.vue$/, | |||||
loader: "vue-loader" | |||||
}, | |||||
{ | |||||
test: /\.js$/, | |||||
exclude: /node_modules\/(?!copy-text-to-clipboard)/, | |||||
loader: "babel-loader" | |||||
}, | |||||
{ | |||||
test: /\.(sa|sc|c)ss$/, | |||||
use: [ | |||||
productionMode ? MiniCssExtractPlugin.loader : "style-loader", | |||||
"css-loader", | |||||
"sass-loader" | |||||
] | |||||
}, | |||||
{ | |||||
test: /\.(png|svg|jpg|gif|ico)$/, | |||||
use: ["file-loader?name=[name].[ext]"] | |||||
}, | |||||
{ | |||||
test: /\.(woff|woff2|eot|ttf|otf)$/, | |||||
use: ["file-loader"] | |||||
}, | |||||
{ test: /\.html$/, loader: "raw-loader" } | |||||
] | |||||
} | |||||
}; |
@@ -1,13 +0,0 @@ | |||||
const merge = require("webpack-merge"); | |||||
const common = require("./webpack.common.js"); | |||||
module.exports = merge(common, { | |||||
mode: "development", | |||||
devtool: "inline-source-map", | |||||
devServer: { | |||||
host: "0.0.0.0", | |||||
port: 8000, | |||||
contentBase: "./dist", | |||||
historyApiFallback: true | |||||
} | |||||
}); |
@@ -1,9 +0,0 @@ | |||||
const merge = require("webpack-merge"); | |||||
const common = require("./webpack.common.js"); | |||||
const TerserPlugin = require("terser-webpack-plugin"); | |||||
module.exports = merge(common, { | |||||
mode: "production", | |||||
devtool: "source-map", | |||||
optimization: { minimizer: [new TerserPlugin()] } | |||||
}); |