feat: optional secret field (#1115)
* fix: add grab cursor to drag indicator * feat: optional secret field * chore: increment version * chore: format files * fix: import React automatically * fix: qr code button not clickable * fix: show entry types after title * fix: remove stylekit references * fix: import react * chore: update yarn.lock * fix: update webpack dev config * fix: styles import path * fix: skalert import * fix: misc * fix: build components using build:components script * fix(tmp): @standardnotes/styles as a dependency for @standardnotes/components-meta Co-authored-by: Johnny Almonte <johnny243@users.noreply.github.com>
This commit is contained in:
5
.github/workflows/pr.components.yml
vendored
5
.github/workflows/pr.components.yml
vendored
@@ -23,9 +23,10 @@ jobs:
|
||||
- name: Lint components
|
||||
run: yarn lint
|
||||
working-directory: packages/components
|
||||
|
||||
- name: Build components
|
||||
run: yarn build
|
||||
working-directory: packages/components
|
||||
run: yarn build:components
|
||||
|
||||
- name: Test components
|
||||
run: yarn test
|
||||
working-directory: packages/components
|
||||
|
||||
BIN
.yarn/cache/@otplib-core-npm-12.0.1-4b9787d379-b3c34bc20b.zip
vendored
Normal file
BIN
.yarn/cache/@otplib-core-npm-12.0.1-4b9787d379-b3c34bc20b.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@otplib-plugin-crypto-npm-12.0.1-d0dc5d1d98-6867c74ee8.zip
vendored
Normal file
BIN
.yarn/cache/@otplib-plugin-crypto-npm-12.0.1-d0dc5d1d98-6867c74ee8.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@otplib-plugin-thirty-two-npm-12.0.1-b85109b20e-920099e40d.zip
vendored
Normal file
BIN
.yarn/cache/@otplib-plugin-thirty-two-npm-12.0.1-b85109b20e-920099e40d.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@otplib-preset-default-npm-12.0.1-77f04f54c4-8133231384.zip
vendored
Normal file
BIN
.yarn/cache/@otplib-preset-default-npm-12.0.1-77f04f54c4-8133231384.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/@otplib-preset-v11-npm-12.0.1-df44c202c1-367cb09397.zip
vendored
Normal file
BIN
.yarn/cache/@otplib-preset-v11-npm-12.0.1-df44c202c1-367cb09397.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.yarn/cache/otplib-npm-12.0.1-77263e8084-4a1b91cf1b.zip
vendored
Normal file
BIN
.yarn/cache/otplib-npm-12.0.1-77263e8084-4a1b91cf1b.zip
vendored
Normal file
Binary file not shown.
BIN
.yarn/cache/sass-loader-npm-13.0.0-0cbb09f3b3-f7af03813d.zip
vendored
Normal file
BIN
.yarn/cache/sass-loader-npm-13.0.0-0cbb09f3b3-f7af03813d.zip
vendored
Normal file
Binary file not shown.
Binary file not shown.
@@ -17,11 +17,13 @@
|
||||
"package": "node scripts/package.mjs",
|
||||
"version": "./scripts/VERSION.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standardnotes/styles": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@standardnotes/deterministic-zip": "^1.2.0",
|
||||
"@standardnotes/eslint-config-extensions": "^1.0.4",
|
||||
"@standardnotes/features": "^1.45.1",
|
||||
"@standardnotes/styles": "workspace:*",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"minimatch": "^5.1.0",
|
||||
|
||||
@@ -3,10 +3,16 @@
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
"modules": false,
|
||||
"targets": "defaults"
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
[
|
||||
"@babel/preset-react",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
|
||||
@@ -22,5 +22,9 @@
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "off",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## LICENSE
|
||||
|
||||
As of version 2.0, TokenVault houses a public-source, but not open-source presence. This means you are free to browse the code and even use the code *privately*, but you may not redistribute, repackage, or otherwise use its public distributable/release assets on GitHub, for free or for profit.
|
||||
As of version 2.0, TokenVault houses a public-source, but not open-source presence. This means you are free to browse the code and even use the code _privately_, but you may not redistribute, repackage, or otherwise use its public distributable/release assets on GitHub, for free or for profit.
|
||||
|
||||
For more information, read [this blog post](https://blog.standardnotes.com/why-tokenvault-is-going-public-source/).
|
||||
|
||||
Previous versions of TokenVault retain the license they were released with.
|
||||
Previous versions of TokenVault retain the license they were released with.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'regenerator-runtime/runtime';
|
||||
|
||||
import React from 'react';
|
||||
import Home from '@Components/Home';
|
||||
|
||||
export default class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Home />;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { totp } from '@Lib/otp';
|
||||
import CountdownPie from '@Components/CountdownPie';
|
||||
import AuthMenu from '@Components/AuthMenu';
|
||||
import DragIndicator from '../assets/svg/drag-indicator.svg';
|
||||
import { getEntryColor, getVarColorForContrast, hexColorToRGB } from '@Lib/utils';
|
||||
import AuthMenu from '@Components/AuthMenu'
|
||||
import CountdownPie from '@Components/CountdownPie'
|
||||
import { totp } from '@Lib/otp'
|
||||
import { getEntryColor, getVarColorForContrast, hexColorToRGB } from '@Lib/utils'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import DragIndicator from '../assets/svg/drag-indicator.svg'
|
||||
|
||||
export default class AuthEntry extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
token: '',
|
||||
@@ -16,106 +16,109 @@ export default class AuthEntry extends React.Component {
|
||||
entryStyle: {
|
||||
color: '',
|
||||
backgroundColor: '',
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
this.updateToken();
|
||||
this.updateToken()
|
||||
}
|
||||
|
||||
getTimeLeft() {
|
||||
const seconds = new Date().getSeconds();
|
||||
return seconds > 29 ? 60 - seconds : 30 - seconds;
|
||||
const seconds = new Date().getSeconds()
|
||||
return seconds > 29 ? 60 - seconds : 30 - seconds
|
||||
}
|
||||
|
||||
updateToken = async () => {
|
||||
const { secret } = this.props.entry;
|
||||
const token = await totp.gen(secret);
|
||||
const { secret } = this.props.entry
|
||||
if (!secret) {
|
||||
return
|
||||
}
|
||||
|
||||
const timeLeft = this.getTimeLeft();
|
||||
const token = await totp.gen(secret)
|
||||
const timeLeft = this.getTimeLeft()
|
||||
this.setState({
|
||||
token,
|
||||
timeLeft
|
||||
});
|
||||
timeLeft,
|
||||
})
|
||||
|
||||
this.timer = setTimeout(this.updateToken, timeLeft * 1000);
|
||||
this.timer = setTimeout(this.updateToken, timeLeft * 1000)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateEntryStyle();
|
||||
this.updateEntryStyle()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// If the secret changed make sure to recalculate token
|
||||
if (prevProps.entry.secret !== this.props.entry.secret) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.updateToken, 0);
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(this.updateToken, 0)
|
||||
}
|
||||
|
||||
if (prevProps.lastUpdated !== this.props.lastUpdated) {
|
||||
this.updateEntryStyle(true);
|
||||
this.updateEntryStyle(true)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timer);
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
|
||||
handleInputChange = event => {
|
||||
const target = event.target;
|
||||
const name = target.name;
|
||||
handleInputChange = (event) => {
|
||||
const target = event.target
|
||||
const name = target.name
|
||||
|
||||
this.props.onEntryChange({
|
||||
id: this.props.id,
|
||||
name,
|
||||
value: target.value
|
||||
});
|
||||
value: target.value,
|
||||
})
|
||||
}
|
||||
|
||||
copyToClipboard = (value) => {
|
||||
const textField = document.createElement('textarea');
|
||||
textField.innerText = value;
|
||||
document.body.appendChild(textField);
|
||||
textField.select();
|
||||
document.execCommand('copy');
|
||||
textField.remove();
|
||||
this.props.onCopyValue();
|
||||
const textField = document.createElement('textarea')
|
||||
textField.innerText = value
|
||||
document.body.appendChild(textField)
|
||||
textField.select()
|
||||
document.execCommand('copy')
|
||||
textField.remove()
|
||||
this.props.onCopyValue()
|
||||
}
|
||||
|
||||
updateEntryStyle = (useDelay = false) => {
|
||||
/**
|
||||
* A short amount of time to wait in order to prevent reading
|
||||
* A short amount of time to wait in order to prevent reading
|
||||
* stale information from the DOM after a theme is activated.
|
||||
*/
|
||||
const DELAY_BEFORE_READING_PROPERTIES = useDelay ? 0 : 50;
|
||||
const DELAY_BEFORE_READING_PROPERTIES = useDelay ? 0 : 50
|
||||
|
||||
setTimeout(() => {
|
||||
const { entryStyle } = this.state;
|
||||
const entryColor = getEntryColor(document, this.props.entry);
|
||||
const { entryStyle } = this.state
|
||||
const entryColor = getEntryColor(document, this.props.entry)
|
||||
|
||||
if (entryColor) {
|
||||
// The background color for the entry.
|
||||
entryStyle.backgroundColor = entryColor;
|
||||
entryStyle.backgroundColor = entryColor
|
||||
|
||||
const rgbColor = hexColorToRGB(entryColor);
|
||||
const varColor = getVarColorForContrast(rgbColor);
|
||||
const rgbColor = hexColorToRGB(entryColor)
|
||||
const varColor = getVarColorForContrast(rgbColor)
|
||||
|
||||
// The foreground color for the entry.
|
||||
entryStyle.color = `var(${varColor})`;
|
||||
entryStyle.color = `var(${varColor})`
|
||||
}
|
||||
|
||||
this.setState({
|
||||
entryStyle
|
||||
});
|
||||
}, DELAY_BEFORE_READING_PROPERTIES);
|
||||
entryStyle,
|
||||
})
|
||||
}, DELAY_BEFORE_READING_PROPERTIES)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { service, account, notes, password } = this.props.entry;
|
||||
const { id, onEdit, onRemove, canEdit, style, innerRef, ...divProps } = this.props;
|
||||
const { token, timeLeft, entryStyle } = this.state;
|
||||
const { service, account, notes, password, secret } = this.props.entry
|
||||
const { id, onEdit, onRemove, canEdit, style, innerRef, ...divProps } = this.props
|
||||
const { token, timeLeft, entryStyle } = this.state
|
||||
|
||||
delete divProps.onCopyValue;
|
||||
delete divProps.lastUpdated;
|
||||
delete divProps.onCopyValue
|
||||
delete divProps.lastUpdated
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -123,14 +126,14 @@ export default class AuthEntry extends React.Component {
|
||||
className="sk-notification sk-base-custom"
|
||||
style={{
|
||||
...entryStyle,
|
||||
...style
|
||||
...style,
|
||||
}}
|
||||
ref={innerRef}
|
||||
>
|
||||
<div className="auth-entry">
|
||||
{canEdit && (
|
||||
<div className="auth-drag-indicator-container">
|
||||
<DragIndicator />
|
||||
<DragIndicator className="grab-cursor" />
|
||||
</div>
|
||||
)}
|
||||
<div className="auth-details">
|
||||
@@ -146,27 +149,29 @@ export default class AuthEntry extends React.Component {
|
||||
{password && (
|
||||
<div className="auth-password-row">
|
||||
<div className="auth-password" onClick={() => this.copyToClipboard(password)}>
|
||||
••••••••••••
|
||||
{'•'.repeat(password.length)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="auth-token-info">
|
||||
<div className="auth-token" onClick={() => this.copyToClipboard(token)}>
|
||||
<div>{token.slice(0, 3)}</div>
|
||||
<div>{token.slice(3, 6)}</div>
|
||||
{secret && (
|
||||
<div className="auth-token-info">
|
||||
<div className="auth-token" onClick={() => this.copyToClipboard(token)}>
|
||||
<div>{token.slice(0, 3)}</div>
|
||||
<div>{token.slice(3, 6)}</div>
|
||||
</div>
|
||||
<div className="auth-countdown">
|
||||
<CountdownPie
|
||||
token={token}
|
||||
timeLeft={timeLeft}
|
||||
total={30}
|
||||
bgColor={entryStyle.backgroundColor}
|
||||
fgColor={entryStyle.color}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="auth-countdown">
|
||||
<CountdownPie
|
||||
token={token}
|
||||
timeLeft={timeLeft}
|
||||
total={30}
|
||||
bgColor={entryStyle.backgroundColor}
|
||||
fgColor={entryStyle.color}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canEdit && (
|
||||
<div className="auth-options">
|
||||
@@ -179,7 +184,7 @@ export default class AuthEntry extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,5 +198,5 @@ AuthEntry.propTypes = {
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
innerRef: PropTypes.func.isRequired,
|
||||
lastUpdated: PropTypes.number.isRequired,
|
||||
style: PropTypes.object.isRequired
|
||||
};
|
||||
style: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
export default class AuthMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
show: false
|
||||
};
|
||||
show: false,
|
||||
}
|
||||
}
|
||||
|
||||
onToggle = () => {
|
||||
this.setState({
|
||||
show: !this.state.show
|
||||
});
|
||||
show: !this.state.show,
|
||||
})
|
||||
}
|
||||
|
||||
onEdit = () => {
|
||||
this.onToggle();
|
||||
this.props.onEdit();
|
||||
this.onToggle()
|
||||
this.props.onEdit()
|
||||
}
|
||||
|
||||
onRemove = () => {
|
||||
this.onToggle();
|
||||
this.props.onRemove();
|
||||
this.onToggle()
|
||||
this.props.onRemove()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { buttonColor } = this.props;
|
||||
const { buttonColor } = this.props
|
||||
|
||||
const buttonStyle = {};
|
||||
const buttonStyle = {}
|
||||
if (buttonColor) {
|
||||
buttonStyle.color = buttonColor;
|
||||
buttonStyle.color = buttonColor
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -39,24 +39,25 @@ export default class AuthMenu extends React.Component {
|
||||
<div className="sk-button" onClick={this.onToggle} style={buttonStyle}>
|
||||
<div className="sk-label">•••</div>
|
||||
</div>
|
||||
{this.state.show && (
|
||||
<div className="auth-overlay" onClick={this.onToggle} />,
|
||||
<div className="sk-menu-panel">
|
||||
<div className="sk-menu-panel-row" onClick={this.onEdit}>
|
||||
<div className="sk-label">Edit</div>
|
||||
{this.state.show &&
|
||||
((<div className="auth-overlay" onClick={this.onToggle} />),
|
||||
(
|
||||
<div className="sk-menu-panel">
|
||||
<div className="sk-menu-panel-row" onClick={this.onEdit}>
|
||||
<div className="sk-label">Edit</div>
|
||||
</div>
|
||||
<div className="sk-menu-panel-row" onClick={this.onRemove}>
|
||||
<div className="sk-label">Remove</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sk-menu-panel-row" onClick={this.onRemove}>
|
||||
<div className="sk-label">Remove</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AuthMenu.propTypes = {
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
buttonColor: PropTypes.string
|
||||
};
|
||||
buttonColor: PropTypes.string,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const ConfirmDialog = ({ title, message, onConfirm, onCancel }) => (
|
||||
<div className="auth-overlay">
|
||||
@@ -26,13 +25,13 @@ const ConfirmDialog = ({ title, message, onConfirm, onCancel }) => (
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
ConfirmDialog.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
};
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default ConfirmDialog;
|
||||
export default ConfirmDialog
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const CopyNotification = ({ isVisible }) => (
|
||||
<div
|
||||
className={`auth-copy-notification ${isVisible ? 'visible' : 'hidden'}`}
|
||||
>
|
||||
<div className={`auth-copy-notification ${isVisible ? 'visible' : 'hidden'}`}>
|
||||
<div className="sk-panel">
|
||||
<div className="sk-font-small sk-bold">Copied value to clipboard.</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
CopyNotification.propTypes = {
|
||||
CopyNotification.propTypes = {
|
||||
isVisible: PropTypes.bool.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
export default CopyNotification;
|
||||
export default CopyNotification
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from 'prop-types'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
const animationName = (token) => `countdown${token}`;
|
||||
const animationName = (token) => `countdown${token}`
|
||||
|
||||
const rotaAnimation = (token, offset) => `@keyframes rota_${animationName(
|
||||
token
|
||||
)} {
|
||||
const rotaAnimation = (token, offset) => `@keyframes rota_${animationName(token)} {
|
||||
0% {
|
||||
transform: rotate(${offset}deg);
|
||||
}
|
||||
@@ -13,11 +11,9 @@ const rotaAnimation = (token, offset) => `@keyframes rota_${animationName(
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}`;
|
||||
}`
|
||||
|
||||
const opaAnimation = (token, offset) => `@keyframes opa_${animationName(
|
||||
token
|
||||
)} {
|
||||
const opaAnimation = (token, offset) => `@keyframes opa_${animationName(token)} {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -26,12 +22,9 @@ const opaAnimation = (token, offset) => `@keyframes opa_${animationName(
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}`;
|
||||
}`
|
||||
|
||||
const opaReverseAnimation = (
|
||||
token,
|
||||
offset
|
||||
) => `@keyframes opa_r_${animationName(token)} {
|
||||
const opaReverseAnimation = (token, offset) => `@keyframes opa_r_${animationName(token)} {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -40,81 +33,81 @@ const opaReverseAnimation = (
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}`;
|
||||
}`
|
||||
|
||||
function calculateOpaOffset(timeLeft, total) {
|
||||
const percentage = calculatePercentage(timeLeft, total) * 100;
|
||||
const percTo50 = 50 - percentage;
|
||||
const percentage = calculatePercentage(timeLeft, total) * 100
|
||||
const percTo50 = 50 - percentage
|
||||
// 8 is an offset because the animation is not in sync otherwise
|
||||
return percTo50 < 0 ? 0 : Math.ceil(Math.min(percTo50 + 8, 50));
|
||||
return percTo50 < 0 ? 0 : Math.ceil(Math.min(percTo50 + 8, 50))
|
||||
}
|
||||
|
||||
function calculateRotaOffset(timeLeft, total) {
|
||||
return calculatePercentage(timeLeft, total) * 360;
|
||||
return calculatePercentage(timeLeft, total) * 360
|
||||
}
|
||||
|
||||
function calculatePercentage(timeLeft, total) {
|
||||
return (total - timeLeft) / total;
|
||||
return (total - timeLeft) / total
|
||||
}
|
||||
|
||||
function useRotateAnimation(token, timeLeft, total) {
|
||||
useEffect(
|
||||
function createRotateAnimation() {
|
||||
const style = document.createElement('style');
|
||||
document.head.appendChild(style);
|
||||
const styleSheet = style.sheet;
|
||||
const style = document.createElement('style')
|
||||
document.head.appendChild(style)
|
||||
const styleSheet = style.sheet
|
||||
|
||||
const rotaKeyframes = rotaAnimation(
|
||||
token,
|
||||
calculateRotaOffset(timeLeft, total)
|
||||
);
|
||||
const opaKeyframes = opaAnimation(token, calculateOpaOffset(timeLeft, total));
|
||||
const opaReverseKeyframes = opaReverseAnimation(
|
||||
token,
|
||||
calculateOpaOffset(timeLeft, total)
|
||||
);
|
||||
const rotaKeyframes = rotaAnimation(token, calculateRotaOffset(timeLeft, total))
|
||||
const opaKeyframes = opaAnimation(token, calculateOpaOffset(timeLeft, total))
|
||||
const opaReverseKeyframes = opaReverseAnimation(token, calculateOpaOffset(timeLeft, total))
|
||||
|
||||
styleSheet.insertRule(rotaKeyframes, styleSheet.cssRules.length);
|
||||
styleSheet.insertRule(opaKeyframes, styleSheet.cssRules.length);
|
||||
styleSheet.insertRule(opaReverseKeyframes, styleSheet.cssRules.length);
|
||||
styleSheet.insertRule(rotaKeyframes, styleSheet.cssRules.length)
|
||||
styleSheet.insertRule(opaKeyframes, styleSheet.cssRules.length)
|
||||
styleSheet.insertRule(opaReverseKeyframes, styleSheet.cssRules.length)
|
||||
|
||||
function cleanup() {
|
||||
style.remove();
|
||||
style.remove()
|
||||
}
|
||||
|
||||
const timer = setTimeout(cleanup, timeLeft * 1000);
|
||||
const timer = setTimeout(cleanup, timeLeft * 1000)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
cleanup();
|
||||
};
|
||||
clearTimeout(timer)
|
||||
cleanup()
|
||||
}
|
||||
},
|
||||
[token, timeLeft, total]
|
||||
);
|
||||
[token, timeLeft, total],
|
||||
)
|
||||
}
|
||||
|
||||
const CountdownPie = ({ token, timeLeft, total, bgColor, fgColor }) => {
|
||||
useRotateAnimation(token, timeLeft, total);
|
||||
useRotateAnimation(token, timeLeft, total)
|
||||
|
||||
return (
|
||||
<div className="countdown-pie" style={{
|
||||
backgroundColor: bgColor
|
||||
}}>
|
||||
<div
|
||||
className="countdown-pie"
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="pie spinner"
|
||||
style={{
|
||||
animation: `rota_${animationName(token)} ${timeLeft}s linear`,
|
||||
backgroundColor: fgColor
|
||||
backgroundColor: fgColor,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="pie background"
|
||||
style={{
|
||||
backgroundColor: fgColor,
|
||||
}}
|
||||
/>
|
||||
<div className="pie background" style={{
|
||||
backgroundColor: fgColor
|
||||
}} />
|
||||
<div
|
||||
className="pie filler"
|
||||
style={{
|
||||
animation: `opa_r_${animationName(token)} ${timeLeft}s steps(1, end)`,
|
||||
backgroundColor: fgColor
|
||||
backgroundColor: fgColor,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
@@ -124,15 +117,15 @@ const CountdownPie = ({ token, timeLeft, total, bgColor, fgColor }) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
CountdownPie.propTypes = {
|
||||
token: PropTypes.string.isRequired,
|
||||
timeLeft: PropTypes.number.isRequired,
|
||||
total: PropTypes.number.isRequired,
|
||||
bgColor: PropTypes.string,
|
||||
fgColor: PropTypes.string
|
||||
};
|
||||
fgColor: PropTypes.string,
|
||||
}
|
||||
|
||||
export default CountdownPie;
|
||||
export default CountdownPie
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
const DataErrorAlert = () => (
|
||||
<div className="auth-overlay">
|
||||
<div className="auth-dialog sk-panel">
|
||||
@@ -10,15 +8,14 @@ const DataErrorAlert = () => (
|
||||
<div className="sk-panel-section sk-panel-hero">
|
||||
<div className="sk-panel-row">
|
||||
<div className="sk-h1">
|
||||
The note you selected already has existing data that is not valid
|
||||
with this editor. Please clear the note, or select a new one, and
|
||||
try again.
|
||||
The note you selected already has existing data that is not valid with this editor. Please clear the note,
|
||||
or select a new one, and try again.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
|
||||
export default DataErrorAlert;
|
||||
export default DataErrorAlert
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import QRCodeReader from '@Components/QRCodeReader';
|
||||
import { secretPattern } from '@Lib/otp';
|
||||
import { TwitterPicker } from 'react-color';
|
||||
import { SKAlert } from 'sn-stylekit';
|
||||
import { contextualColors, defaultBgColor, getAllContextualColors, getEntryColor } from '@Lib/utils';
|
||||
import QRCodeReader from '@Components/QRCodeReader'
|
||||
import { secretPattern } from '@Lib/otp'
|
||||
import { contextualColors, defaultBgColor, getAllContextualColors, getEntryColor } from '@Lib/utils'
|
||||
import { SKAlert } from '@standardnotes/styles'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { TwitterPicker } from 'react-color'
|
||||
|
||||
export default class EditEntry extends React.Component {
|
||||
static defaultProps = {
|
||||
@@ -12,93 +12,102 @@ export default class EditEntry extends React.Component {
|
||||
service: '',
|
||||
account: '',
|
||||
secret: '',
|
||||
notes: ''
|
||||
}
|
||||
};
|
||||
notes: '',
|
||||
},
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
super(props)
|
||||
|
||||
const { id, entry } = props
|
||||
|
||||
this.state = {
|
||||
id: this.props.id,
|
||||
entry: this.props.entry,
|
||||
id: id,
|
||||
entry,
|
||||
showColorPicker: false,
|
||||
qrCodeError: false
|
||||
};
|
||||
qrCodeError: false,
|
||||
is2fa: id !== undefined ? !!entry.secret : true,
|
||||
}
|
||||
}
|
||||
|
||||
formatSecret(secret) {
|
||||
return secret.replace(/\s/g, '').toUpperCase();
|
||||
return secret.replace(/\s/g, '').toUpperCase()
|
||||
}
|
||||
|
||||
handleInputChange = event => {
|
||||
const target = event.target;
|
||||
const name = target.name;
|
||||
handleInputChange = (event) => {
|
||||
const target = event.target
|
||||
const name = target.name
|
||||
|
||||
const value = name === 'secret' ?
|
||||
this.formatSecret(target.value) : target.value;
|
||||
const value = name === 'secret' ? this.formatSecret(target.value) : target.value
|
||||
|
||||
this.setState(state => ({
|
||||
this.setState((state) => ({
|
||||
entry: {
|
||||
...state.entry,
|
||||
[name]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
[name]: value,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
handleSwatchClick = () => {
|
||||
this.setState({
|
||||
showColorPicker: !this.state.showColorPicker
|
||||
});
|
||||
};
|
||||
showColorPicker: !this.state.showColorPicker,
|
||||
})
|
||||
}
|
||||
|
||||
handleColorPickerClose = () => {
|
||||
this.setState({
|
||||
showColorPicker: false
|
||||
});
|
||||
};
|
||||
showColorPicker: false,
|
||||
})
|
||||
}
|
||||
|
||||
removeColor = () => {
|
||||
this.setState((state) => {
|
||||
delete state.entry.color;
|
||||
delete state.entry.color
|
||||
return {
|
||||
entry: state.entry
|
||||
};
|
||||
});
|
||||
};
|
||||
entry: state.entry,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onSave = () => {
|
||||
const { id, entry } = this.state;
|
||||
this.props.onSave({ id, entry });
|
||||
};
|
||||
const { id, entry, is2fa } = this.state
|
||||
this.props.onSave({
|
||||
id,
|
||||
entry: {
|
||||
...entry,
|
||||
secret: is2fa ? entry.secret : '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
onQRCodeSuccess = otpData => {
|
||||
const { issuer: labelIssuer, account } = otpData.label;
|
||||
const { issuer: queryIssuer, secret } = otpData.query;
|
||||
onQRCodeSuccess = (otpData) => {
|
||||
const { issuer: labelIssuer, account } = otpData.label
|
||||
const { issuer: queryIssuer, secret } = otpData.query
|
||||
|
||||
this.setState({
|
||||
entry: {
|
||||
service: labelIssuer || queryIssuer || '',
|
||||
account,
|
||||
secret: this.formatSecret(secret)
|
||||
}
|
||||
});
|
||||
};
|
||||
secret: this.formatSecret(secret),
|
||||
},
|
||||
is2fa: true,
|
||||
})
|
||||
}
|
||||
|
||||
onQRCodeError = message => {
|
||||
onQRCodeError = (message) => {
|
||||
this.setState({
|
||||
qrCodeError: message
|
||||
});
|
||||
};
|
||||
qrCodeError: message,
|
||||
})
|
||||
}
|
||||
|
||||
dismissQRCodeError = () => {
|
||||
this.setState({
|
||||
qrCodeError: false
|
||||
});
|
||||
};
|
||||
qrCodeError: false,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, entry, showColorPicker, qrCodeError } = this.state;
|
||||
const { id, entry, showColorPicker, qrCodeError, is2fa } = this.state
|
||||
|
||||
const qrCodeAlert = new SKAlert({
|
||||
title: 'Error',
|
||||
@@ -107,63 +116,69 @@ export default class EditEntry extends React.Component {
|
||||
{
|
||||
text: 'OK',
|
||||
style: 'info',
|
||||
action: this.dismissQRCodeError
|
||||
}
|
||||
]
|
||||
});
|
||||
action: this.dismissQRCodeError,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (qrCodeError) {
|
||||
qrCodeAlert.present();
|
||||
qrCodeAlert.present()
|
||||
}
|
||||
|
||||
const entryColor = getEntryColor(document, entry);
|
||||
const entryColor = getEntryColor(document, entry)
|
||||
const swatchStyle = {
|
||||
width: '36px',
|
||||
height: '14px',
|
||||
borderRadius: '2px',
|
||||
background: `${entryColor ?? defaultBgColor}`,
|
||||
};
|
||||
}
|
||||
|
||||
const themeColors = getAllContextualColors(document);
|
||||
const defaultColorOptions = [
|
||||
...themeColors,
|
||||
'#658bdb',
|
||||
'#4CBBFC',
|
||||
'#FF794D',
|
||||
'#EF5276',
|
||||
'#91B73D',
|
||||
'#9B7ECF'
|
||||
];
|
||||
const themeColors = getAllContextualColors(document)
|
||||
const defaultColorOptions = [...themeColors, '#658bdb', '#4CBBFC', '#FF794D', '#EF5276', '#91B73D', '#9B7ECF']
|
||||
|
||||
const handleColorChange = (color) => {
|
||||
let selectedColor = color.hex.toUpperCase();
|
||||
const colorIndex = defaultColorOptions.indexOf(selectedColor);
|
||||
let selectedColor = color.hex.toUpperCase()
|
||||
const colorIndex = defaultColorOptions.indexOf(selectedColor)
|
||||
|
||||
if (colorIndex > -1 && colorIndex <= themeColors.length - 1) {
|
||||
selectedColor = contextualColors[colorIndex];
|
||||
selectedColor = contextualColors[colorIndex]
|
||||
}
|
||||
|
||||
this.setState(state => ({
|
||||
this.setState((state) => ({
|
||||
entry: {
|
||||
...state.entry,
|
||||
color: selectedColor
|
||||
}
|
||||
}));
|
||||
};
|
||||
color: selectedColor,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const handleTypeChange = ({ target }) => {
|
||||
this.setState({
|
||||
is2fa: target.value === '2fa',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="auth-edit sk-panel">
|
||||
<div className="sk-panel-content">
|
||||
<div className="sk-panel-section">
|
||||
<div className="sk-panel-section-title sk-panel-row">
|
||||
{id != null ? 'Edit entry' : 'Add new entry'}
|
||||
<div className="sk-panel-row">
|
||||
<div className="left-header">
|
||||
<div className="sk-panel-section-title pr-4">{id != null ? 'Edit entry' : 'Add new entry'}</div>
|
||||
<div className="sk-input-group" onChange={handleTypeChange}>
|
||||
<label>
|
||||
<input className="sk-input" type="radio" value="2fa" name="type" defaultChecked={is2fa} /> 2FA
|
||||
</label>
|
||||
<label>
|
||||
<input className="sk-input" type="radio" value="password" name="type" defaultChecked={!is2fa} />{' '}
|
||||
Password only
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sk-panel-section-title sk-panel-row">
|
||||
{id == null && (
|
||||
<QRCodeReader
|
||||
onSuccess={this.onQRCodeSuccess}
|
||||
onError={this.onQRCodeError}
|
||||
/>
|
||||
)}
|
||||
{id == null && <QRCodeReader onSuccess={this.onQRCodeSuccess} onError={this.onQRCodeError} />}
|
||||
<>
|
||||
{entryColor && (
|
||||
<div className="sk-button danger" onClick={this.removeColor}>
|
||||
@@ -195,15 +210,26 @@ export default class EditEntry extends React.Component {
|
||||
onChange={this.handleInputChange}
|
||||
type="text"
|
||||
/>
|
||||
{is2fa && (
|
||||
<input
|
||||
name="secret"
|
||||
className="sk-input contrast"
|
||||
placeholder="Secret"
|
||||
value={entry.secret}
|
||||
onChange={this.handleInputChange}
|
||||
type="text"
|
||||
pattern={secretPattern}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
name="secret"
|
||||
name="password"
|
||||
className="sk-input contrast"
|
||||
placeholder="Secret"
|
||||
value={entry.secret}
|
||||
placeholder={`Password ${is2fa ? '(optional)' : ''}`}
|
||||
value={entry.password}
|
||||
onChange={this.handleInputChange}
|
||||
type="text"
|
||||
pattern={secretPattern}
|
||||
required
|
||||
required={!is2fa}
|
||||
/>
|
||||
<input
|
||||
name="notes"
|
||||
@@ -213,14 +239,6 @@ export default class EditEntry extends React.Component {
|
||||
onChange={this.handleInputChange}
|
||||
type="text"
|
||||
/>
|
||||
<input
|
||||
name="password"
|
||||
className="sk-input contrast"
|
||||
placeholder="Password (optional)"
|
||||
value={entry.password}
|
||||
onChange={this.handleInputChange}
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{showColorPicker && (
|
||||
<div className="color-picker-popover">
|
||||
@@ -231,12 +249,9 @@ export default class EditEntry extends React.Component {
|
||||
onChangeComplete={handleColorChange}
|
||||
triangle="top-right"
|
||||
onSwatchHover={(color, event) => {
|
||||
const hoveredColor = color.hex.toUpperCase();
|
||||
const hoveredColor = color.hex.toUpperCase()
|
||||
if (themeColors.includes(hoveredColor)) {
|
||||
event.target.setAttribute(
|
||||
'title',
|
||||
'This color will change depending on your active theme.'
|
||||
);
|
||||
event.target.setAttribute('title', 'This color will change depending on your active theme.')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -248,9 +263,7 @@ export default class EditEntry extends React.Component {
|
||||
<div className="sk-label">Cancel</div>
|
||||
</button>
|
||||
<button type="submit" className="sk-button info">
|
||||
<div className="sk-label">
|
||||
{id != null ? 'Save' : 'Create'}
|
||||
</div>
|
||||
<div className="sk-label">{id != null ? 'Save' : 'Create'}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -258,7 +271,7 @@ export default class EditEntry extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,5 +279,5 @@ EditEntry.propTypes = {
|
||||
id: PropTypes.number,
|
||||
entry: PropTypes.object.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired
|
||||
};
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import update from 'immutability-helper';
|
||||
import EditEntry from '@Components/EditEntry';
|
||||
import ViewEntries from '@Components/ViewEntries';
|
||||
import ConfirmDialog from '@Components/ConfirmDialog';
|
||||
import DataErrorAlert from '@Components/DataErrorAlert';
|
||||
import EditorKit from '@standardnotes/editor-kit';
|
||||
import ReorderIcon from '../assets/svg/reorder-icon.svg';
|
||||
import CopyNotification from './CopyNotification';
|
||||
import ConfirmDialog from '@Components/ConfirmDialog'
|
||||
import DataErrorAlert from '@Components/DataErrorAlert'
|
||||
import EditEntry from '@Components/EditEntry'
|
||||
import ViewEntries from '@Components/ViewEntries'
|
||||
import EditorKit from '@standardnotes/editor-kit'
|
||||
import update from 'immutability-helper'
|
||||
import React from 'react'
|
||||
import ReorderIcon from '../assets/svg/reorder-icon.svg'
|
||||
import CopyNotification from './CopyNotification'
|
||||
|
||||
const initialState = {
|
||||
text: '',
|
||||
@@ -19,31 +19,31 @@ const initialState = {
|
||||
displayCopy: false,
|
||||
canEdit: true,
|
||||
searchValue: '',
|
||||
lastUpdated: 0
|
||||
};
|
||||
lastUpdated: 0,
|
||||
}
|
||||
|
||||
export default class Home extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.configureEditorKit();
|
||||
this.state = initialState;
|
||||
super(props)
|
||||
this.configureEditorKit()
|
||||
this.state = initialState
|
||||
}
|
||||
|
||||
configureEditorKit() {
|
||||
const delegate = {
|
||||
setEditorRawText: text => {
|
||||
let parseError = false;
|
||||
let entries = [];
|
||||
setEditorRawText: (text) => {
|
||||
let parseError = false
|
||||
let entries = []
|
||||
|
||||
if (text) {
|
||||
try {
|
||||
entries = this.parseNote(text);
|
||||
entries = this.parseNote(text)
|
||||
} catch (e) {
|
||||
// Couldn't parse the content
|
||||
parseError = true;
|
||||
parseError = true
|
||||
this.setState({
|
||||
parseError: true
|
||||
});
|
||||
parseError: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,225 +51,225 @@ export default class Home extends React.Component {
|
||||
...initialState,
|
||||
text,
|
||||
parseError,
|
||||
entries
|
||||
});
|
||||
entries,
|
||||
})
|
||||
},
|
||||
generateCustomPreview: text => {
|
||||
let entries = [];
|
||||
generateCustomPreview: (text) => {
|
||||
let entries = []
|
||||
try {
|
||||
entries = this.parseNote(text);
|
||||
entries = this.parseNote(text)
|
||||
} finally {
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return {
|
||||
html: `<div><strong>${entries.length}</strong> TokenVault Entries </div>`,
|
||||
plain: `${entries.length} TokenVault Entries`,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
clearUndoHistory: () => { },
|
||||
clearUndoHistory: () => {},
|
||||
getElementsBySelector: () => [],
|
||||
onNoteLockToggle: (isLocked) => {
|
||||
this.setState({
|
||||
canEdit: !isLocked
|
||||
});
|
||||
canEdit: !isLocked,
|
||||
})
|
||||
},
|
||||
onThemesChange: () => {
|
||||
this.setState({
|
||||
lastUpdated: Date.now(),
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
this.editorKit = new EditorKit(delegate, {
|
||||
mode: 'json',
|
||||
supportsFileSafe: false
|
||||
});
|
||||
supportsFileSafe: false,
|
||||
})
|
||||
}
|
||||
|
||||
parseNote(text) {
|
||||
const entries = JSON.parse(text);
|
||||
const entries = JSON.parse(text)
|
||||
|
||||
if (entries instanceof Array) {
|
||||
if (entries.length === 0) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!('service' in entry)) {
|
||||
throw Error('Service key is missing for an entry.');
|
||||
throw Error('Service key is missing for an entry.')
|
||||
}
|
||||
|
||||
if (!('secret' in entry)) {
|
||||
throw Error('Secret key is missing for an entry.');
|
||||
if (!('secret' in entry) && !('password' in entry)) {
|
||||
throw Error('An entry does not have a secret key or a password.')
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
return entries
|
||||
}
|
||||
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
|
||||
saveNote(entries) {
|
||||
this.editorKit.onEditorValueChanged(JSON.stringify(entries, null, 2));
|
||||
this.editorKit.onEditorValueChanged(JSON.stringify(entries, null, 2))
|
||||
}
|
||||
|
||||
// Entry operations
|
||||
addEntry = entry => {
|
||||
this.setState(state => {
|
||||
const entries = state.entries.concat([entry]);
|
||||
this.saveNote(entries);
|
||||
addEntry = (entry) => {
|
||||
this.setState((state) => {
|
||||
const entries = state.entries.concat([entry])
|
||||
this.saveNote(entries)
|
||||
|
||||
return {
|
||||
editMode: false,
|
||||
editEntry: null,
|
||||
entries
|
||||
};
|
||||
});
|
||||
};
|
||||
entries,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
editEntry = ({ id, entry }) => {
|
||||
this.setState(state => {
|
||||
const entries = update(state.entries, { [id]: { $set: entry } });
|
||||
this.saveNote(entries);
|
||||
this.setState((state) => {
|
||||
const entries = update(state.entries, { [id]: { $set: entry } })
|
||||
this.saveNote(entries)
|
||||
|
||||
return {
|
||||
editMode: false,
|
||||
editEntry: null,
|
||||
entries
|
||||
};
|
||||
});
|
||||
};
|
||||
entries,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
removeEntry = id => {
|
||||
this.setState(state => {
|
||||
const entries = update(state.entries, { $splice: [[id, 1]] });
|
||||
this.saveNote(entries);
|
||||
removeEntry = (id) => {
|
||||
this.setState((state) => {
|
||||
const entries = update(state.entries, { $splice: [[id, 1]] })
|
||||
this.saveNote(entries)
|
||||
|
||||
return {
|
||||
confirmRemove: false,
|
||||
editEntry: null,
|
||||
entries
|
||||
};
|
||||
});
|
||||
};
|
||||
entries,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Event Handlers
|
||||
onAddNew = () => {
|
||||
if (!this.state.canEdit) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
editMode: true,
|
||||
editEntry: null
|
||||
});
|
||||
};
|
||||
editEntry: null,
|
||||
})
|
||||
}
|
||||
|
||||
onEdit = id => {
|
||||
onEdit = (id) => {
|
||||
if (!this.state.canEdit) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
this.setState(state => ({
|
||||
this.setState((state) => ({
|
||||
editMode: true,
|
||||
editEntry: {
|
||||
id,
|
||||
entry: state.entries[id]
|
||||
}
|
||||
}));
|
||||
};
|
||||
entry: state.entries[id],
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
onCancel = () => {
|
||||
this.setState({
|
||||
confirmRemove: false,
|
||||
confirmReorder: false,
|
||||
editMode: false,
|
||||
editEntry: null
|
||||
});
|
||||
};
|
||||
editEntry: null,
|
||||
})
|
||||
}
|
||||
|
||||
onRemove = id => {
|
||||
onRemove = (id) => {
|
||||
if (!this.state.canEdit) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
this.setState(state => ({
|
||||
this.setState((state) => ({
|
||||
confirmRemove: true,
|
||||
editEntry: {
|
||||
id,
|
||||
entry: state.entries[id]
|
||||
}
|
||||
}));
|
||||
};
|
||||
entry: state.entries[id],
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
onSave = ({ id, entry }) => {
|
||||
// If there's no ID it's a new note
|
||||
if (id != null) {
|
||||
this.editEntry({ id, entry });
|
||||
this.editEntry({ id, entry })
|
||||
} else {
|
||||
this.addEntry(entry);
|
||||
this.addEntry(entry)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
onCopyValue = () => {
|
||||
this.setState({
|
||||
displayCopy: true
|
||||
});
|
||||
displayCopy: true,
|
||||
})
|
||||
|
||||
if (this.clearTooltipTimer) {
|
||||
clearTimeout(this.clearTooltipTimer);
|
||||
clearTimeout(this.clearTooltipTimer)
|
||||
}
|
||||
|
||||
this.clearTooltipTimer = setTimeout(() => {
|
||||
this.setState({
|
||||
displayCopy: false
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
displayCopy: false,
|
||||
})
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
updateEntries = (entries) => {
|
||||
this.saveNote(entries);
|
||||
this.saveNote(entries)
|
||||
this.setState({
|
||||
entries
|
||||
});
|
||||
};
|
||||
entries,
|
||||
})
|
||||
}
|
||||
|
||||
onReorderEntries = () => {
|
||||
if (!this.state.canEdit) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
confirmReorder: true
|
||||
});
|
||||
};
|
||||
confirmReorder: true,
|
||||
})
|
||||
}
|
||||
|
||||
onSearchChange = event => {
|
||||
const target = event.target;
|
||||
onSearchChange = (event) => {
|
||||
const target = event.target
|
||||
this.setState({
|
||||
searchValue: target.value.toLowerCase()
|
||||
});
|
||||
};
|
||||
searchValue: target.value.toLowerCase(),
|
||||
})
|
||||
}
|
||||
|
||||
clearSearchValue = () => {
|
||||
this.setState({
|
||||
searchValue: ''
|
||||
});
|
||||
searchValue: '',
|
||||
})
|
||||
}
|
||||
|
||||
reorderEntries = () => {
|
||||
const { entries } = this.state;
|
||||
const { entries } = this.state
|
||||
const orderedEntries = entries.sort((a, b) => {
|
||||
const serviceA = a.service.toLowerCase();
|
||||
const serviceB = b.service.toLowerCase();
|
||||
return (serviceA < serviceB) ? -1 : (serviceA > serviceB) ? 1 : 0;
|
||||
});
|
||||
this.saveNote(orderedEntries);
|
||||
const serviceA = a.service.toLowerCase()
|
||||
const serviceB = b.service.toLowerCase()
|
||||
return serviceA < serviceB ? -1 : serviceA > serviceB ? 1 : 0
|
||||
})
|
||||
this.saveNote(orderedEntries)
|
||||
this.setState({
|
||||
entries: orderedEntries,
|
||||
confirmReorder: false
|
||||
});
|
||||
};
|
||||
confirmReorder: false,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const editEntry = this.state.editEntry || {};
|
||||
const editEntry = this.state.editEntry || {}
|
||||
const {
|
||||
canEdit,
|
||||
displayCopy,
|
||||
@@ -279,15 +279,15 @@ export default class Home extends React.Component {
|
||||
confirmRemove,
|
||||
confirmReorder,
|
||||
searchValue,
|
||||
lastUpdated
|
||||
} = this.state;
|
||||
lastUpdated,
|
||||
} = this.state
|
||||
|
||||
if (parseError) {
|
||||
return (
|
||||
<div className="sn-component">
|
||||
<DataErrorAlert />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -327,12 +327,7 @@ export default class Home extends React.Component {
|
||||
)}
|
||||
<div id="content">
|
||||
{editMode ? (
|
||||
<EditEntry
|
||||
id={editEntry.id}
|
||||
entry={editEntry.entry}
|
||||
onSave={this.onSave}
|
||||
onCancel={this.onCancel}
|
||||
/>
|
||||
<EditEntry id={editEntry.id} entry={editEntry.entry} onSave={this.onSave} onCancel={this.onCancel} />
|
||||
) : (
|
||||
<ViewEntries
|
||||
entries={entries}
|
||||
@@ -363,6 +358,6 @@ export default class Home extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +1,85 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import jsQR from 'jsqr';
|
||||
import { parseKeyUri } from '@Lib/otp';
|
||||
import { parseKeyUri } from '@Lib/otp'
|
||||
import jsQR from 'jsqr'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const convertToGrayScale = (imageData) => {
|
||||
if (!imageData) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||
const count = imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
|
||||
let color = 0;
|
||||
const count = imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2]
|
||||
let color = 0
|
||||
|
||||
if (count > 510) {
|
||||
color = 255;
|
||||
color = 255
|
||||
} else if (count > 255) {
|
||||
color = 127.5;
|
||||
color = 127.5
|
||||
}
|
||||
|
||||
imageData.data[i] = color;
|
||||
imageData.data[i + 1] = color;
|
||||
imageData.data[i + 2] = color;
|
||||
imageData.data[i + 3] = 255;
|
||||
imageData.data[i] = color
|
||||
imageData.data[i + 1] = color
|
||||
imageData.data[i + 2] = color
|
||||
imageData.data[i + 3] = 255
|
||||
}
|
||||
|
||||
return imageData;
|
||||
};
|
||||
return imageData
|
||||
}
|
||||
|
||||
export default class QRCodeReader extends React.Component {
|
||||
onImageSelected = evt => {
|
||||
const file = evt.target.files[0];
|
||||
const url = URL.createObjectURL(file);
|
||||
const img = new Image();
|
||||
const self = this;
|
||||
onImageSelected = (evt) => {
|
||||
const file = evt.target.files[0]
|
||||
const url = URL.createObjectURL(file)
|
||||
const img = new Image()
|
||||
const self = this
|
||||
|
||||
img.onload = function() {
|
||||
URL.revokeObjectURL(this.src);
|
||||
img.onload = function () {
|
||||
URL.revokeObjectURL(this.src)
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.width = this.width;
|
||||
canvas.height = this.height;
|
||||
context.drawImage(this, 0, 0);
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
canvas.width = this.width
|
||||
canvas.height = this.height
|
||||
context.drawImage(this, 0, 0)
|
||||
|
||||
let imageData = context.getImageData(0, 0, this.width, this.height);
|
||||
imageData = convertToGrayScale(imageData);
|
||||
let imageData = context.getImageData(0, 0, this.width, this.height)
|
||||
imageData = convertToGrayScale(imageData)
|
||||
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height);
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height)
|
||||
|
||||
const { onError, onSuccess } = self.props;
|
||||
const { onError, onSuccess } = self.props
|
||||
|
||||
if (code) {
|
||||
const otpData = parseKeyUri(code.data);
|
||||
const otpData = parseKeyUri(code.data)
|
||||
if (otpData.type !== 'totp') {
|
||||
onError(`The '${otpData.type}' type is not supported.`);
|
||||
onError(`The '${otpData.type}' type is not supported.`)
|
||||
} else {
|
||||
onSuccess(otpData);
|
||||
onSuccess(otpData)
|
||||
}
|
||||
} else {
|
||||
onError('Error reading QR code from image. Please try again.');
|
||||
onError('Error reading QR code from image. Please try again.')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
img.src = url;
|
||||
img.src = url
|
||||
|
||||
return false;
|
||||
};
|
||||
return false
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="qr-code-reader-container">
|
||||
<div className="sk-button info">
|
||||
<label className="no-style">
|
||||
<input
|
||||
type="file"
|
||||
style={{ display: 'none' }}
|
||||
onChange={this.onImageSelected}
|
||||
/>
|
||||
<div className="sk-label">Upload QR Code</div>
|
||||
</label>
|
||||
<div className="sk-label">Upload QR Code</div>
|
||||
<input type="file" style={{ display: 'none' }} onChange={this.onImageSelected} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
QRCodeReader.propTypes = {
|
||||
onError: PropTypes.func.isRequired,
|
||||
onSuccess: PropTypes.func.isRequired
|
||||
};
|
||||
onSuccess: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AuthEntry from '@Components/AuthEntry';
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import AuthEntry from '@Components/AuthEntry'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'
|
||||
|
||||
const reorderEntries = (list, startIndex, endIndex) => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
const result = Array.from(list)
|
||||
const [removed] = result.splice(startIndex, 1)
|
||||
result.splice(endIndex, 0, removed)
|
||||
|
||||
return result;
|
||||
};
|
||||
return result
|
||||
}
|
||||
|
||||
const ViewEntries = ({ entries, onEdit, onRemove, onCopyValue, canEdit, updateEntries, searchValue, lastUpdated }) => {
|
||||
const onDragEnd = (result) => {
|
||||
const droppedOutsideList = !result.destination;
|
||||
const droppedOutsideList = !result.destination
|
||||
if (droppedOutsideList) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const orderedEntries = reorderEntries(
|
||||
entries,
|
||||
result.source.index,
|
||||
result.destination.index
|
||||
);
|
||||
const orderedEntries = reorderEntries(entries, result.source.index, result.destination.index)
|
||||
|
||||
updateEntries(orderedEntries);
|
||||
};
|
||||
updateEntries(orderedEntries)
|
||||
}
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable" isDropDisabled={!canEdit}>
|
||||
{(provided) => (
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
className="auth-list"
|
||||
>
|
||||
<div {...provided.droppableProps} ref={provided.innerRef} className="auth-list">
|
||||
{entries.map((entry, index) => {
|
||||
/**
|
||||
* Filtering entries by account, service and notes properties.
|
||||
*/
|
||||
const combinedString = `${entry.account}${entry.service}${entry.notes}`.toLowerCase();
|
||||
const combinedString = `${entry.account}${entry.service}${entry.notes}`.toLowerCase()
|
||||
if (searchValue && !combinedString.includes(searchValue)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
return (
|
||||
<Draggable
|
||||
@@ -67,17 +58,17 @@ const ViewEntries = ({ entries, onEdit, onRemove, onCopyValue, canEdit, updateEn
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
)
|
||||
})}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
ViewEntries.propTypes = {
|
||||
ViewEntries.propTypes = {
|
||||
entries: PropTypes.arrayOf(PropTypes.object),
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
@@ -85,7 +76,7 @@ ViewEntries.propTypes = {
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
lastUpdated: PropTypes.number.isRequired,
|
||||
updateEntries: PropTypes.func.isRequired,
|
||||
searchValue: PropTypes.string
|
||||
};
|
||||
searchValue: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ViewEntries;
|
||||
export default ViewEntries
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import Home from '@Components/Home'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
ReactDOM.render(<Home />, document.body.appendChild(document.createElement('div')))
|
||||
@@ -1,12 +1,5 @@
|
||||
import {
|
||||
base32ToHex,
|
||||
leftpad,
|
||||
decToHex,
|
||||
bufToHex,
|
||||
hextoBuf,
|
||||
hexToBytes
|
||||
} from '@Lib/utils';
|
||||
export { secretPattern, parseKeyUri } from '@Lib/utils';
|
||||
import { base32ToHex, bufToHex, decToHex, hextoBuf, hexToBytes, leftpad } from '@Lib/utils'
|
||||
export { parseKeyUri, secretPattern } from '@Lib/utils'
|
||||
|
||||
class Hotp {
|
||||
/**
|
||||
@@ -25,25 +18,25 @@ class Hotp {
|
||||
*
|
||||
*/
|
||||
async gen(secret, opt) {
|
||||
var key = base32ToHex(secret) || '';
|
||||
opt = opt || {};
|
||||
var counter = opt.counter || 0;
|
||||
var key = base32ToHex(secret) || ''
|
||||
opt = opt || {}
|
||||
var counter = opt.counter || 0
|
||||
|
||||
var hexCounter = leftpad(decToHex(counter), 16, '0');
|
||||
var digest = await this.createHmac('SHA-1', key, hexCounter);
|
||||
var h = hexToBytes(digest);
|
||||
var hexCounter = leftpad(decToHex(counter), 16, '0')
|
||||
var digest = await this.createHmac('SHA-1', key, hexCounter)
|
||||
var h = hexToBytes(digest)
|
||||
|
||||
// Truncate
|
||||
var offset = h[h.length - 1] & 0xf;
|
||||
var offset = h[h.length - 1] & 0xf
|
||||
var v =
|
||||
((h[offset] & 0x7f) << 24) |
|
||||
((h[offset + 1] & 0xff) << 16) |
|
||||
((h[offset + 2] & 0xff) << 8) |
|
||||
(h[offset + 3] & 0xff);
|
||||
(h[offset + 3] & 0xff)
|
||||
|
||||
v = (v % 1000000) + '';
|
||||
v = (v % 1000000) + ''
|
||||
|
||||
return Array(7 - v.length).join('0') + v;
|
||||
return Array(7 - v.length).join('0') + v
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,23 +69,23 @@ class Hotp {
|
||||
*
|
||||
*/
|
||||
async verify(token, key, opt) {
|
||||
opt = opt || {};
|
||||
var window = opt.window || 50;
|
||||
var counter = opt.counter || 0;
|
||||
opt = opt || {}
|
||||
var window = opt.window || 50
|
||||
var counter = opt.counter || 0
|
||||
|
||||
// Now loop through from C to C + W to determine if there is
|
||||
// a correct code
|
||||
for (var i = counter - window; i <= counter + window; ++i) {
|
||||
opt.counter = i;
|
||||
opt.counter = i
|
||||
if ((await this.gen(key, opt)) === token) {
|
||||
// We have found a matching code, trigger callback
|
||||
// and pass offset
|
||||
return { delta: i - counter };
|
||||
return { delta: i - counter }
|
||||
}
|
||||
}
|
||||
|
||||
// If we get to here then no codes have matched, return null
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
async createHmac(alg, key, str) {
|
||||
@@ -102,17 +95,17 @@ class Hotp {
|
||||
{
|
||||
// algorithm details
|
||||
name: 'HMAC',
|
||||
hash: { name: alg }
|
||||
hash: { name: alg },
|
||||
},
|
||||
false, // export = false
|
||||
['sign'] // what this key can do
|
||||
);
|
||||
const sig = await window.crypto.subtle.sign('HMAC', hmacKey, hextoBuf(str));
|
||||
return bufToHex(sig);
|
||||
['sign'], // what this key can do
|
||||
)
|
||||
const sig = await window.crypto.subtle.sign('HMAC', hmacKey, hextoBuf(str))
|
||||
return bufToHex(sig)
|
||||
}
|
||||
}
|
||||
|
||||
export const hotp = new Hotp();
|
||||
export const hotp = new Hotp()
|
||||
|
||||
class Totp {
|
||||
/**
|
||||
@@ -133,15 +126,15 @@ class Totp {
|
||||
*
|
||||
*/
|
||||
async gen(key, opt) {
|
||||
opt = opt || {};
|
||||
var time = opt.time || 30;
|
||||
var _t = Date.now();
|
||||
opt = opt || {}
|
||||
var time = opt.time || 30
|
||||
var _t = Date.now()
|
||||
|
||||
// Determine the value of the counter, C
|
||||
// This is the number of time steps in seconds since T0
|
||||
opt.counter = Math.floor(_t / 1000 / time);
|
||||
opt.counter = Math.floor(_t / 1000 / time)
|
||||
|
||||
return hotp.gen(key, opt);
|
||||
return hotp.gen(key, opt)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,16 +169,16 @@ class Totp {
|
||||
*
|
||||
*/
|
||||
async verify(token, key, opt) {
|
||||
opt = opt || {};
|
||||
var time = opt.time || 30;
|
||||
var _t = Date.now();
|
||||
opt = opt || {}
|
||||
var time = opt.time || 30
|
||||
var _t = Date.now()
|
||||
|
||||
// Determine the value of the counter, C
|
||||
// This is the number of time steps in seconds since T0
|
||||
opt.counter = Math.floor(_t / 1000 / time);
|
||||
opt.counter = Math.floor(_t / 1000 / time)
|
||||
|
||||
return hotp.verify(token, key, opt);
|
||||
return hotp.verify(token, key, opt)
|
||||
}
|
||||
}
|
||||
|
||||
export const totp = new Totp();
|
||||
export const totp = new Totp()
|
||||
|
||||
@@ -1,58 +1,56 @@
|
||||
const base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||
export const secretPattern = `^[${base32chars}]{16,}$`;
|
||||
const base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
export const secretPattern = `^[${base32chars}]{16,}$`
|
||||
|
||||
export function hexToBytes(hex) {
|
||||
var bytes = [];
|
||||
var bytes = []
|
||||
for (var c = 0, C = hex.length; c < C; c += 2) {
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16))
|
||||
}
|
||||
return bytes;
|
||||
return bytes
|
||||
}
|
||||
|
||||
export function decToHex(s) {
|
||||
return (s < 15.5 ? '0' : '') + Math.round(s).toString(16);
|
||||
return (s < 15.5 ? '0' : '') + Math.round(s).toString(16)
|
||||
}
|
||||
|
||||
export function bufToHex(buf) {
|
||||
return Array.prototype.map
|
||||
.call(new Uint8Array(buf), x => ('00' + x.toString(16)).slice(-2))
|
||||
.join('');
|
||||
return Array.prototype.map.call(new Uint8Array(buf), (x) => ('00' + x.toString(16)).slice(-2)).join('')
|
||||
}
|
||||
|
||||
export function hextoBuf(hex) {
|
||||
var view = new Uint8Array(hex.length / 2);
|
||||
var view = new Uint8Array(hex.length / 2)
|
||||
|
||||
for (var i = 0; i < hex.length; i += 2) {
|
||||
view[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
||||
view[i / 2] = parseInt(hex.substring(i, i + 2), 16)
|
||||
}
|
||||
|
||||
return view.buffer;
|
||||
return view.buffer
|
||||
}
|
||||
|
||||
export function base32ToHex(base32) {
|
||||
var bits, chunk, hex, i, val;
|
||||
bits = '';
|
||||
hex = '';
|
||||
i = 0;
|
||||
var bits, chunk, hex, i, val
|
||||
bits = ''
|
||||
hex = ''
|
||||
i = 0
|
||||
while (i < base32.length) {
|
||||
val = base32chars.indexOf(base32.charAt(i).toUpperCase());
|
||||
bits += leftpad(val.toString(2), 5, '0');
|
||||
i++;
|
||||
val = base32chars.indexOf(base32.charAt(i).toUpperCase())
|
||||
bits += leftpad(val.toString(2), 5, '0')
|
||||
i++
|
||||
}
|
||||
i = 0;
|
||||
i = 0
|
||||
while (i + 4 <= bits.length) {
|
||||
chunk = bits.substr(i, 4);
|
||||
hex = hex + parseInt(chunk, 2).toString(16);
|
||||
i += 4;
|
||||
chunk = bits.substr(i, 4)
|
||||
hex = hex + parseInt(chunk, 2).toString(16)
|
||||
i += 4
|
||||
}
|
||||
return hex;
|
||||
return hex
|
||||
}
|
||||
|
||||
export function leftpad(str, len, pad) {
|
||||
if (len + 1 >= str.length) {
|
||||
str = Array(len + 1 - str.length).join(pad) + str;
|
||||
str = Array(len + 1 - str.length).join(pad) + str
|
||||
}
|
||||
return str;
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,45 +74,45 @@ export function leftpad(str, len, pad) {
|
||||
*/
|
||||
export function parseKeyUri(uri) {
|
||||
// Quick sanity check
|
||||
if (typeof uri !== 'string' || uri.length < 7) return null;
|
||||
if (typeof uri !== 'string' || uri.length < 7) return null
|
||||
|
||||
// I would like to just use new URL(), but the behavior is different between node and browsers, so
|
||||
// we have to do some of the work manually with regex.
|
||||
const parts = /otpauth:\/\/([A-Za-z]+)\/([^?]+)\??(.*)?/i.exec(uri);
|
||||
const parts = /otpauth:\/\/([A-Za-z]+)\/([^?]+)\??(.*)?/i.exec(uri)
|
||||
|
||||
if (!parts || parts.length < 3) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [fullUri, type, fullLabel] = parts;
|
||||
const [fullUri, type, fullLabel] = parts
|
||||
|
||||
// Sanity check type and label
|
||||
if (!type || !fullLabel) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
// Parse the label
|
||||
const decodedLabel = decodeURIComponent(fullLabel);
|
||||
const decodedLabel = decodeURIComponent(fullLabel)
|
||||
|
||||
const labelParts = decodedLabel.split(/: ?/);
|
||||
const labelParts = decodedLabel.split(/: ?/)
|
||||
|
||||
const label =
|
||||
labelParts && labelParts.length === 2
|
||||
? { issuer: labelParts[0], account: labelParts[1] }
|
||||
: { issuer: '', account: decodedLabel };
|
||||
: { issuer: '', account: decodedLabel }
|
||||
|
||||
// Parse query string
|
||||
const qs = parts[3] ? new URLSearchParams(parts[3]) : [];
|
||||
const qs = parts[3] ? new URLSearchParams(parts[3]) : []
|
||||
|
||||
const query = [...qs].reduce((acc, [key, value]) => {
|
||||
acc[key] = value;
|
||||
acc[key] = value
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
// Returned the parsed parts of the URI
|
||||
return { type: type.toLowerCase(), label, query };
|
||||
return { type: type.toLowerCase(), label, query }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,19 +120,21 @@ export function parseKeyUri(uri) {
|
||||
*/
|
||||
export function hexColorToRGB(hexColor) {
|
||||
// Expand the shorthand form (e.g. "0AB") to full form (e.g. "00AABB")
|
||||
const shortHandFormRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
hexColor = hexColor.replace(shortHandFormRegex, function(m, red, green, blue) {
|
||||
return red + red + green + green + blue + blue;
|
||||
});
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
|
||||
return result ? {
|
||||
red: parseInt(result[1], 16),
|
||||
green: parseInt(result[2], 16),
|
||||
blue: parseInt(result[3], 16)
|
||||
} : null;
|
||||
const shortHandFormRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
|
||||
hexColor = hexColor.replace(shortHandFormRegex, function (m, red, green, blue) {
|
||||
return red + red + green + green + blue + blue
|
||||
})
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor)
|
||||
return result
|
||||
? {
|
||||
red: parseInt(result[1], 16),
|
||||
green: parseInt(result[2], 16),
|
||||
blue: parseInt(result[3], 16),
|
||||
}
|
||||
: null
|
||||
}
|
||||
|
||||
export const defaultBgColor = '#FFF';
|
||||
export const defaultBgColor = '#FFF'
|
||||
|
||||
/**
|
||||
* Gets the color variable to be used based on the calculated constrast of a color.
|
||||
@@ -142,48 +142,44 @@ export const defaultBgColor = '#FFF';
|
||||
export function getVarColorForContrast(backgroundColor) {
|
||||
const styleKitColors = {
|
||||
foreground: '--sn-stylekit-contrast-foreground-color',
|
||||
background: '--sn-stylekit-contrast-background-color'
|
||||
};
|
||||
if (!backgroundColor) {
|
||||
return styleKitColors.foreground;
|
||||
background: '--sn-stylekit-contrast-background-color',
|
||||
}
|
||||
const colorContrast = Math.round(((parseInt(backgroundColor.red) * 299) + (parseInt(backgroundColor.green) * 587) + (parseInt(backgroundColor.blue) * 114)) / 1000);
|
||||
return (colorContrast > 70) ? styleKitColors.background : styleKitColors.foreground;
|
||||
if (!backgroundColor) {
|
||||
return styleKitColors.foreground
|
||||
}
|
||||
const colorContrast = Math.round(
|
||||
(parseInt(backgroundColor.red) * 299 +
|
||||
parseInt(backgroundColor.green) * 587 +
|
||||
parseInt(backgroundColor.blue) * 114) /
|
||||
1000,
|
||||
)
|
||||
return colorContrast > 70 ? styleKitColors.background : styleKitColors.foreground
|
||||
}
|
||||
|
||||
function getPropertyValue(document, propertyName) {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(propertyName).trim().toUpperCase();
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(propertyName).trim().toUpperCase()
|
||||
}
|
||||
|
||||
export const contextualColors = [
|
||||
'info',
|
||||
'success',
|
||||
'neutral',
|
||||
'warning'
|
||||
];
|
||||
export const contextualColors = ['info', 'success', 'neutral', 'warning']
|
||||
|
||||
export function getContextualColor(document, colorName) {
|
||||
if (!contextualColors.includes(colorName)) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
return getPropertyValue(
|
||||
document,
|
||||
`--sn-stylekit-${colorName}-color`
|
||||
);
|
||||
return getPropertyValue(document, `--sn-stylekit-${colorName}-color`)
|
||||
}
|
||||
|
||||
export function getEntryColor(document, entry) {
|
||||
const { color } = entry;
|
||||
const { color } = entry
|
||||
|
||||
if (!contextualColors.includes(color)) {
|
||||
return color;
|
||||
return color
|
||||
}
|
||||
|
||||
return getContextualColor(document, color);
|
||||
return getContextualColor(document, color)
|
||||
}
|
||||
|
||||
export function getAllContextualColors(document) {
|
||||
return contextualColors.map((colorName) => getContextualColor(document, colorName));
|
||||
return contextualColors.map((colorName) => getContextualColor(document, colorName))
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.body.appendChild(document.createElement('div'))
|
||||
);
|
||||
@@ -1,25 +1,24 @@
|
||||
@import '~stylekit';
|
||||
@import '~@standardnotes/styles/src/Styles/main.scss';
|
||||
|
||||
body,
|
||||
html {
|
||||
font-family: var(--sn-stylekit-sans-serif-font);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: var(--sn-stylekit-base-font-size);
|
||||
background-color: transparent;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
* {
|
||||
// To prevent gray flash when focusing input on mobile Safari
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-family: var(--sn-stylekit-sans-serif-font);
|
||||
}
|
||||
|
||||
.sn-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
min-height: -webkit-fill-available;
|
||||
}
|
||||
|
||||
.sk-panel-content {
|
||||
height: inherit !important;
|
||||
@@ -127,7 +126,6 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* entry default styles */
|
||||
.auth-entry {
|
||||
display: flex;
|
||||
@@ -291,7 +289,7 @@ html {
|
||||
padding: 5px;
|
||||
background: var(--sn-stylekit-contrast-background-color);
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.1);
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -409,9 +407,37 @@ html {
|
||||
}
|
||||
|
||||
// Show palette icon on the first 4 color rectangles.
|
||||
div.twitter-picker > div:nth-child(3) > span:nth-child(-n+4) > div {
|
||||
div.twitter-picker > div:nth-child(3) > span:nth-child(-n + 4) > div {
|
||||
background-image: url('../assets/svg/palette.svg') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: top 4px right 4px !important;
|
||||
background-size: 12px 12px !important;
|
||||
}
|
||||
|
||||
.grab-cursor {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.left-header {
|
||||
display: flex;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sk-input-group {
|
||||
> * {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.73125rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,46 +7,55 @@
|
||||
"components:compile": "webpack --config webpack.prod.js",
|
||||
"start": "webpack serve --config webpack.dev.js --progress --hot",
|
||||
"skip:components:lint": "eslint app/ --ext .js",
|
||||
"components:lint:fix": "yarn lint --fix"
|
||||
"components:lint:fix": "eslint --fix",
|
||||
"components:lint": "prettier --write 'app/**/*.{html,css,scss,js,jsx,ts,tsx,json}' README.md"
|
||||
},
|
||||
"sn": {
|
||||
"main": "dist/index.html"
|
||||
},
|
||||
"lint-staged": {
|
||||
"README.md": [
|
||||
"prettier --write"
|
||||
],
|
||||
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.13.10",
|
||||
"@babel/eslint-parser": "^7.13.10",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-transform-runtime": "^7.13.10",
|
||||
"@babel/preset-env": "^7.13.10",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@standardnotes/editor-kit": "2.2.1",
|
||||
"@standardnotes/eslint-config-extensions": "^1.0.1",
|
||||
"@svgr/webpack": "^6.1.2",
|
||||
"babel-loader": "^8.2.2",
|
||||
"css-loader": "^5.1.3",
|
||||
"eslint": "^7.21.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"immutability-helper": "^3.0.1",
|
||||
"jsqr": "^1.2.0",
|
||||
"mini-css-extract-plugin": "^1.3.9",
|
||||
"@babel/core": "^7.18.5",
|
||||
"@babel/eslint-parser": "^7.18.2",
|
||||
"@babel/plugin-proposal-class-properties": "^7.17.12",
|
||||
"@babel/plugin-transform-runtime": "^7.18.5",
|
||||
"@babel/preset-env": "^7.18.2",
|
||||
"@babel/preset-react": "^7.17.12",
|
||||
"@standardnotes/editor-kit": "2.2.5",
|
||||
"@standardnotes/eslint-config-extensions": "^1.0.4",
|
||||
"@standardnotes/styles": "workspace:*",
|
||||
"@svgr/webpack": "^6.2.1",
|
||||
"babel-loader": "^8.2.5",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "*",
|
||||
"eslint-plugin-react": "^7.30.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"node-sass": "*",
|
||||
"notp": "^2.0.3",
|
||||
"otplib": "^11.0.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.1",
|
||||
"otplib": "^12.0.1",
|
||||
"prettier": "*",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-beautiful-dnd": "^13.1.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-dom": "^17.0.1",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"sass-loader": "^11.0.1",
|
||||
"sn-stylekit": "2.1.0",
|
||||
"style-loader": "~0.13.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"sass-loader": "^13.0.0",
|
||||
"style-loader": "~3.3.1",
|
||||
"svg-url-loader": "^7.1.1",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"terser-webpack-plugin": "^5.3.3",
|
||||
"webpack": "*",
|
||||
"webpack-cli": "*",
|
||||
"webpack-dev-server": "*",
|
||||
"webpack-merge": "^5.7.3"
|
||||
"webpack-merge": "^5.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
const path = require('path');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const path = require('path')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
context: __dirname,
|
||||
entry: [
|
||||
path.resolve(__dirname, 'app/main.js'),
|
||||
path.resolve(__dirname, 'app/index.js'),
|
||||
path.resolve(__dirname, 'app/stylesheets/main.scss')
|
||||
],
|
||||
output: {
|
||||
@@ -30,11 +30,8 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.js[x]?$/,
|
||||
include: [
|
||||
path.resolve(__dirname, 'app')
|
||||
],
|
||||
exclude: /node_modules/,
|
||||
use: ['babel-loader']
|
||||
use: ['babel-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/i,
|
||||
@@ -60,7 +57,6 @@ module.exports = {
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx'],
|
||||
alias: {
|
||||
stylekit: require.resolve('sn-stylekit/dist/stylekit.css'),
|
||||
'@Components': path.resolve(__dirname, 'app/components'),
|
||||
'@Lib': path.resolve(__dirname, 'app/lib')
|
||||
}
|
||||
@@ -75,4 +71,4 @@ module.exports = {
|
||||
filename: 'index.html'
|
||||
})
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,14 +7,17 @@ module.exports = merge(config, {
|
||||
devtool: 'cheap-source-map',
|
||||
devServer: {
|
||||
port: 8001,
|
||||
contentBase: path.resolve(__dirname, 'dist'),
|
||||
disableHostCheck: true,
|
||||
static: path.resolve(__dirname, 'dist'),
|
||||
allowedHosts: "all",
|
||||
historyApiFallback: true,
|
||||
watchOptions: { aggregateTimeout: 300, poll: 1000 },
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization'
|
||||
}
|
||||
},
|
||||
watchOptions: {
|
||||
aggregateTimeout: 300,
|
||||
poll: 1000
|
||||
},
|
||||
});
|
||||
|
||||
@@ -94,6 +94,10 @@
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-4 {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.pl-1 {
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
187
yarn.lock
187
yarn.lock
@@ -87,7 +87,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/core@npm:^7.1.0, @babel/core@npm:^7.1.6, @babel/core@npm:^7.11.1, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.10, @babel/core@npm:^7.13.13, @babel/core@npm:^7.13.14, @babel/core@npm:^7.13.8, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.6, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.17.10, @babel/core@npm:^7.17.9, @babel/core@npm:^7.18.2, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.7, @babel/core@npm:^7.8.0":
|
||||
"@babel/core@npm:^7.1.0, @babel/core@npm:^7.1.6, @babel/core@npm:^7.11.1, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.13.10, @babel/core@npm:^7.13.13, @babel/core@npm:^7.13.14, @babel/core@npm:^7.13.8, @babel/core@npm:^7.14.0, @babel/core@npm:^7.14.6, @babel/core@npm:^7.15.5, @babel/core@npm:^7.16.0, @babel/core@npm:^7.17.10, @babel/core@npm:^7.17.9, @babel/core@npm:^7.18.2, @babel/core@npm:^7.18.5, @babel/core@npm:^7.7.2, @babel/core@npm:^7.7.7, @babel/core@npm:^7.8.0":
|
||||
version: 7.18.5
|
||||
resolution: "@babel/core@npm:7.18.5"
|
||||
dependencies:
|
||||
@@ -110,7 +110,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/eslint-parser@npm:^7.13.10, @babel/eslint-parser@npm:^7.13.14, @babel/eslint-parser@npm:^7.13.4, @babel/eslint-parser@npm:^7.13.8, @babel/eslint-parser@npm:^7.14.7, @babel/eslint-parser@npm:^7.16.3":
|
||||
"@babel/eslint-parser@npm:^7.13.10, @babel/eslint-parser@npm:^7.13.14, @babel/eslint-parser@npm:^7.13.4, @babel/eslint-parser@npm:^7.13.8, @babel/eslint-parser@npm:^7.14.7, @babel/eslint-parser@npm:^7.16.3, @babel/eslint-parser@npm:^7.18.2":
|
||||
version: 7.18.2
|
||||
resolution: "@babel/eslint-parser@npm:7.18.2"
|
||||
dependencies:
|
||||
@@ -1296,7 +1296,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/plugin-transform-runtime@npm:^7.0.0, @babel/plugin-transform-runtime@npm:^7.10.1, @babel/plugin-transform-runtime@npm:^7.11.0, @babel/plugin-transform-runtime@npm:^7.13.0, @babel/plugin-transform-runtime@npm:^7.13.10, @babel/plugin-transform-runtime@npm:^7.16.4, @babel/plugin-transform-runtime@npm:^7.18.2":
|
||||
"@babel/plugin-transform-runtime@npm:^7.0.0, @babel/plugin-transform-runtime@npm:^7.10.1, @babel/plugin-transform-runtime@npm:^7.11.0, @babel/plugin-transform-runtime@npm:^7.13.0, @babel/plugin-transform-runtime@npm:^7.16.4, @babel/plugin-transform-runtime@npm:^7.18.2, @babel/plugin-transform-runtime@npm:^7.18.5":
|
||||
version: 7.18.5
|
||||
resolution: "@babel/plugin-transform-runtime@npm:7.18.5"
|
||||
dependencies:
|
||||
@@ -1517,7 +1517,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/preset-react@npm:^7.10.1, @babel/preset-react@npm:^7.10.4, @babel/preset-react@npm:^7.12.12, @babel/preset-react@npm:^7.12.13, @babel/preset-react@npm:^7.12.5, @babel/preset-react@npm:^7.13.13, @babel/preset-react@npm:^7.14.5, @babel/preset-react@npm:^7.16.0":
|
||||
"@babel/preset-react@npm:^7.10.1, @babel/preset-react@npm:^7.10.4, @babel/preset-react@npm:^7.12.12, @babel/preset-react@npm:^7.12.13, @babel/preset-react@npm:^7.12.5, @babel/preset-react@npm:^7.13.13, @babel/preset-react@npm:^7.14.5, @babel/preset-react@npm:^7.16.0, @babel/preset-react@npm:^7.17.12":
|
||||
version: 7.17.12
|
||||
resolution: "@babel/preset-react@npm:7.17.12"
|
||||
dependencies:
|
||||
@@ -3905,6 +3905,54 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@otplib/core@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "@otplib/core@npm:12.0.1"
|
||||
checksum: b3c34bc20b31bc3f49cc0dc3c0eb070491c0101e8c1efa83cec48ca94158bd736aaca8187df667fc0c4a239d4ac52076bc44084bee04a50c80c3630caf77affa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@otplib/plugin-crypto@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "@otplib/plugin-crypto@npm:12.0.1"
|
||||
dependencies:
|
||||
"@otplib/core": ^12.0.1
|
||||
checksum: 6867c74ee8aca6c2db9670362cf51e44f3648602c39318bf537421242e33f0012a172acd43bbed9a21d706e535dc4c66aff965380673391e9fd74cf685b5b13a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@otplib/plugin-thirty-two@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "@otplib/plugin-thirty-two@npm:12.0.1"
|
||||
dependencies:
|
||||
"@otplib/core": ^12.0.1
|
||||
thirty-two: ^1.0.2
|
||||
checksum: 920099e40d3e8c2941291c84c70064c2d86d0d1ed17230d650445d5463340e406bc413ddf2e40c374ddc4ee988ef1e3facacab9b5248b1ff361fd13df52bf88f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@otplib/preset-default@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "@otplib/preset-default@npm:12.0.1"
|
||||
dependencies:
|
||||
"@otplib/core": ^12.0.1
|
||||
"@otplib/plugin-crypto": ^12.0.1
|
||||
"@otplib/plugin-thirty-two": ^12.0.1
|
||||
checksum: 8133231384f6277f77eb8e42ef83bc32a8b01059bef147d1c358d9e9bfd292e1c239f581fe008367a48489dd68952b7ac0948e6c41412fc06079da2c91b71d16
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@otplib/preset-v11@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "@otplib/preset-v11@npm:12.0.1"
|
||||
dependencies:
|
||||
"@otplib/core": ^12.0.1
|
||||
"@otplib/plugin-crypto": ^12.0.1
|
||||
"@otplib/plugin-thirty-two": ^12.0.1
|
||||
checksum: 367cb09397e617c21ec748d54e920ab43f1c5dfba70cbfd88edf73aecca399cf0c09fefe32518f79c7ee8a06e7058d14b200da378cc7d46af3cac4e22a153e2f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.3":
|
||||
version: 0.5.7
|
||||
resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7"
|
||||
@@ -4949,41 +4997,41 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@standardnotes/authenticator@workspace:packages/components/src/packages/org.standardnotes.token-vault"
|
||||
dependencies:
|
||||
"@babel/core": ^7.13.10
|
||||
"@babel/eslint-parser": ^7.13.10
|
||||
"@babel/plugin-proposal-class-properties": ^7.13.0
|
||||
"@babel/plugin-transform-runtime": ^7.13.10
|
||||
"@babel/preset-env": ^7.13.10
|
||||
"@babel/preset-react": ^7.12.13
|
||||
"@standardnotes/editor-kit": 2.2.1
|
||||
"@standardnotes/eslint-config-extensions": ^1.0.1
|
||||
"@svgr/webpack": ^6.1.2
|
||||
babel-loader: ^8.2.2
|
||||
css-loader: ^5.1.3
|
||||
eslint: ^7.21.0
|
||||
eslint-plugin-react: ^7.22.0
|
||||
html-webpack-plugin: ^5.3.1
|
||||
immutability-helper: ^3.0.1
|
||||
jsqr: ^1.2.0
|
||||
mini-css-extract-plugin: ^1.3.9
|
||||
"@babel/core": ^7.18.5
|
||||
"@babel/eslint-parser": ^7.18.2
|
||||
"@babel/plugin-proposal-class-properties": ^7.17.12
|
||||
"@babel/plugin-transform-runtime": ^7.18.5
|
||||
"@babel/preset-env": ^7.18.2
|
||||
"@babel/preset-react": ^7.17.12
|
||||
"@standardnotes/editor-kit": 2.2.5
|
||||
"@standardnotes/eslint-config-extensions": ^1.0.4
|
||||
"@standardnotes/styles": "workspace:*"
|
||||
"@svgr/webpack": ^6.2.1
|
||||
babel-loader: ^8.2.5
|
||||
css-loader: ^6.7.1
|
||||
eslint: "*"
|
||||
eslint-plugin-react: ^7.30.0
|
||||
html-webpack-plugin: ^5.5.0
|
||||
immutability-helper: ^3.1.1
|
||||
jsqr: ^1.4.0
|
||||
mini-css-extract-plugin: ^2.6.1
|
||||
node-sass: "*"
|
||||
notp: ^2.0.3
|
||||
otplib: ^11.0.1
|
||||
prop-types: ^15.7.2
|
||||
react: ^17.0.1
|
||||
otplib: ^12.0.1
|
||||
prettier: "*"
|
||||
prop-types: ^15.8.1
|
||||
react: ^18.2.0
|
||||
react-beautiful-dnd: ^13.1.0
|
||||
react-color: ^2.19.3
|
||||
react-dom: ^17.0.1
|
||||
regenerator-runtime: ^0.13.2
|
||||
sass-loader: ^11.0.1
|
||||
sn-stylekit: 2.1.0
|
||||
style-loader: ~0.13.1
|
||||
react-dom: ^18.2.0
|
||||
sass-loader: ^13.0.0
|
||||
style-loader: ~3.3.1
|
||||
svg-url-loader: ^7.1.1
|
||||
terser-webpack-plugin: ^5.1.1
|
||||
terser-webpack-plugin: ^5.3.3
|
||||
webpack: "*"
|
||||
webpack-cli: "*"
|
||||
webpack-dev-server: "*"
|
||||
webpack-merge: ^5.7.3
|
||||
webpack-merge: ^5.8.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -5178,13 +5226,6 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@standardnotes/editor-kit@npm:2.2.1":
|
||||
version: 2.2.1
|
||||
resolution: "@standardnotes/editor-kit@npm:2.2.1"
|
||||
checksum: d856714b8d7c1b7704d1644fc30edab3536b3e1f0f277a009f2b2381e7df07f827e5a68c764b58192d26ea5385e71379c410e5509d71570b317cda04d56c5010
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standardnotes/editor-kit@npm:2.2.3":
|
||||
version: 2.2.3
|
||||
resolution: "@standardnotes/editor-kit@npm:2.2.3"
|
||||
@@ -5256,7 +5297,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standardnotes/eslint-config-extensions@npm:^1.0.0, @standardnotes/eslint-config-extensions@npm:^1.0.1, @standardnotes/eslint-config-extensions@npm:^1.0.2, @standardnotes/eslint-config-extensions@npm:^1.0.4":
|
||||
"@standardnotes/eslint-config-extensions@npm:^1.0.0, @standardnotes/eslint-config-extensions@npm:^1.0.2, @standardnotes/eslint-config-extensions@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "@standardnotes/eslint-config-extensions@npm:1.0.4"
|
||||
peerDependencies:
|
||||
@@ -6373,7 +6414,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@svgr/webpack@npm:^6.1.2, @svgr/webpack@npm:^6.2.1":
|
||||
"@svgr/webpack@npm:^6.2.1":
|
||||
version: 6.2.1
|
||||
resolution: "@svgr/webpack@npm:6.2.1"
|
||||
dependencies:
|
||||
@@ -14789,7 +14830,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react@npm:*, eslint-plugin-react@npm:^7.22.0, eslint-plugin-react@npm:^7.23.1, eslint-plugin-react@npm:^7.24.0, eslint-plugin-react@npm:^7.26.1, eslint-plugin-react@npm:^7.27.1, eslint-plugin-react@npm:^7.29.4":
|
||||
"eslint-plugin-react@npm:*, eslint-plugin-react@npm:^7.22.0, eslint-plugin-react@npm:^7.23.1, eslint-plugin-react@npm:^7.24.0, eslint-plugin-react@npm:^7.26.1, eslint-plugin-react@npm:^7.27.1, eslint-plugin-react@npm:^7.29.4, eslint-plugin-react@npm:^7.30.0":
|
||||
version: 7.30.0
|
||||
resolution: "eslint-plugin-react@npm:7.30.0"
|
||||
dependencies:
|
||||
@@ -14953,7 +14994,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:^7.20.0, eslint@npm:^7.21.0":
|
||||
"eslint@npm:^7.20.0":
|
||||
version: 7.32.0
|
||||
resolution: "eslint@npm:7.32.0"
|
||||
dependencies:
|
||||
@@ -17851,7 +17892,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"immutability-helper@npm:^3.0.1":
|
||||
"immutability-helper@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "immutability-helper@npm:3.1.1"
|
||||
checksum: 6fdbf6d2123efa567263e904bbaff07aca0e24560d270d34967b03aab8ec20bd3e4057f394d59e50eb6c4718c9415591a6281692bb0aafd522ad72cf4887133f
|
||||
@@ -20063,7 +20104,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsqr@npm:^1.2.0":
|
||||
"jsqr@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "jsqr@npm:1.4.0"
|
||||
checksum: 7c572971f90c42772e30d152bde63b84edf1164bde80c53942e6b2068ea31caf00ad704aa46cacc9e71645f52dbeddebc6e84ba15e883c678ee93cde690de339
|
||||
@@ -20530,7 +20571,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"loader-utils@npm:^1.0.2, loader-utils@npm:^1.1.0":
|
||||
"loader-utils@npm:^1.1.0":
|
||||
version: 1.4.0
|
||||
resolution: "loader-utils@npm:1.4.0"
|
||||
dependencies:
|
||||
@@ -22349,7 +22390,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mini-css-extract-plugin@npm:^2.0.0, mini-css-extract-plugin@npm:^2.4.5, mini-css-extract-plugin@npm:^2.5.3, mini-css-extract-plugin@npm:^2.6.0":
|
||||
"mini-css-extract-plugin@npm:^2.0.0, mini-css-extract-plugin@npm:^2.4.5, mini-css-extract-plugin@npm:^2.5.3, mini-css-extract-plugin@npm:^2.6.0, mini-css-extract-plugin@npm:^2.6.1":
|
||||
version: 2.6.1
|
||||
resolution: "mini-css-extract-plugin@npm:2.6.1"
|
||||
dependencies:
|
||||
@@ -23699,12 +23740,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"otplib@npm:^11.0.1":
|
||||
version: 11.0.1
|
||||
resolution: "otplib@npm:11.0.1"
|
||||
"otplib@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "otplib@npm:12.0.1"
|
||||
dependencies:
|
||||
thirty-two: 1.0.2
|
||||
checksum: 42225f1ccc4562fc062dfd0cbe4b0c527f56648775601175b638e54850c44a1dbe1770e5858a2e50216e5111bd4dd2776df3372a92f74a2fb41e7f3975dc0bbd
|
||||
"@otplib/core": ^12.0.1
|
||||
"@otplib/preset-default": ^12.0.1
|
||||
"@otplib/preset-v11": ^12.0.1
|
||||
checksum: 4a1b91cf1b8e920b50ad4bac2ef2a89126630c62daf68e9b32ff15106b2551db905d3b979955cf5f8f114da0a8883cec3d636901d65e793c1745bb4174e2a572
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -26285,7 +26328,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-dom@npm:^18.1.0":
|
||||
"react-dom@npm:^18.1.0, react-dom@npm:^18.2.0":
|
||||
version: 18.2.0
|
||||
resolution: "react-dom@npm:18.2.0"
|
||||
dependencies:
|
||||
@@ -27075,7 +27118,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:^18.1.0":
|
||||
"react@npm:^18.1.0, react@npm:^18.2.0":
|
||||
version: 18.2.0
|
||||
resolution: "react@npm:18.2.0"
|
||||
dependencies:
|
||||
@@ -28232,6 +28275,31 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sass-loader@npm:^13.0.0":
|
||||
version: 13.0.0
|
||||
resolution: "sass-loader@npm:13.0.0"
|
||||
dependencies:
|
||||
klona: ^2.0.4
|
||||
neo-async: ^2.6.2
|
||||
peerDependencies:
|
||||
fibers: ">= 3.1.0"
|
||||
node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
sass: ^1.3.0
|
||||
sass-embedded: "*"
|
||||
webpack: ^5.0.0
|
||||
peerDependenciesMeta:
|
||||
fibers:
|
||||
optional: true
|
||||
node-sass:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
checksum: f7af03813dccf0405eb02917cd10c97571ab81f42e9ea1f3da6d9e96991e076521809a452ad319d57c1a63273ce07c23ddfdbda5cd071a56d261dc28913afdaa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sass-loader@npm:^9.0.3":
|
||||
version: 9.0.3
|
||||
resolution: "sass-loader@npm:9.0.3"
|
||||
@@ -29798,15 +29866,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-loader@npm:~0.13.1":
|
||||
version: 0.13.2
|
||||
resolution: "style-loader@npm:0.13.2"
|
||||
dependencies:
|
||||
loader-utils: ^1.0.2
|
||||
checksum: 68bdfbf4e759abf6e5195880966ac9407b758ca9f1fd96dc2584554707d25d8dbbe1a9d6524a617513e7d28d9f51bdd754689c1e6ae12ac1c4ff62781f5e7ccc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-loader@npm:~1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "style-loader@npm:1.2.1"
|
||||
@@ -30276,7 +30335,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"terser-webpack-plugin@npm:^5.1.1, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.1.4, terser-webpack-plugin@npm:^5.2.5, terser-webpack-plugin@npm:^5.3.1":
|
||||
"terser-webpack-plugin@npm:^5.1.1, terser-webpack-plugin@npm:^5.1.3, terser-webpack-plugin@npm:^5.1.4, terser-webpack-plugin@npm:^5.2.5, terser-webpack-plugin@npm:^5.3.1, terser-webpack-plugin@npm:^5.3.3":
|
||||
version: 5.3.3
|
||||
resolution: "terser-webpack-plugin@npm:5.3.3"
|
||||
dependencies:
|
||||
@@ -30337,7 +30396,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"thirty-two@npm:1.0.2":
|
||||
"thirty-two@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "thirty-two@npm:1.0.2"
|
||||
checksum: f6700b31d16ef942fdc0d14daed8a2f69ea8b60b0e85db8b83adf58d84bbeafe95a17d343ab55efaae571bb5148b62fc0ee12b04781323bf7af7d7e9693eec76
|
||||
|
||||
Reference in New Issue
Block a user