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