CSS konstrui + tab clean
This commit is contained in:
parent
a128a031f0
commit
264bf3b8e7
287
app.html
287
app.html
|
@ -2,231 +2,122 @@
|
||||||
<title en>Cryptopter</title>
|
<title en>Cryptopter</title>
|
||||||
<title fr>Cryptoptère</title>
|
<title fr>Cryptoptère</title>
|
||||||
<style lang="eo,en,fr">
|
<style lang="eo,en,fr">
|
||||||
[_lang_] { display: none; }
|
[_lang_] { display: none; }
|
||||||
:root[lang=_lang_] [_lang_] { display: inherit; }
|
:root[lang=_lang_] [_lang_] { display: inherit; }
|
||||||
|
|
||||||
:root[lang=_lang_] [_lang_\:title]:hover::after { display: inherit }
|
:root[lang=_lang_] [_lang_\:title]:hover::after { display: inherit }
|
||||||
|
|
||||||
/* [_lang_\:title]:after {
|
/* [_lang_\:title]:after {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: grey;
|
background: grey;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
color: white;
|
color: white;
|
||||||
padding: .1em .3em;
|
padding: .1em .3em;
|
||||||
margin-left: -5%;
|
margin-left: -5%;
|
||||||
}
|
}
|
||||||
:root[lang=_lang_] [_lang_\:title]:after { content: attr(_lang_\:title) } */
|
:root[lang=_lang_] [_lang_\:title]:after { content: attr(_lang_\:title) } */
|
||||||
</style>
|
</style>
|
||||||
<style>
|
|
||||||
body{
|
|
||||||
display: grid;
|
|
||||||
grid-template: "menu menu menu" 2em
|
|
||||||
"main main main" auto
|
|
||||||
"left middle right" 3em
|
|
||||||
/ 7em auto 7em;
|
|
||||||
height: 100%;
|
|
||||||
grid-gap: 1em;
|
|
||||||
margin: 0;
|
|
||||||
background: lightgrey;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu { grid-area: menu }
|
<link rel="stylesheet" href="./layout.css">
|
||||||
input[type=file] { display: none; }
|
|
||||||
|
|
||||||
ui-data {
|
|
||||||
grid-area: main;
|
|
||||||
margin-top: -1.05em;
|
|
||||||
height: calc(100% + 1.05em);
|
|
||||||
}
|
|
||||||
|
|
||||||
button[encrypt] { grid-area: left }
|
|
||||||
pass { grid-area: middle }
|
|
||||||
button[decrypt] { grid-area: right }
|
|
||||||
|
|
||||||
button[encrypt],
|
|
||||||
button[decrypt] {
|
|
||||||
font-weight: bolder;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
menu {
|
|
||||||
display: flex;
|
|
||||||
height: 2em;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
padding-left: 1px;
|
|
||||||
}
|
|
||||||
menu > * {
|
|
||||||
margin-left: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu > button {
|
|
||||||
font-size: 1em;
|
|
||||||
height: 2em;
|
|
||||||
width: 2em;
|
|
||||||
}
|
|
||||||
menu > button > i {
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
menu > button > .fa-caret-up {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.75em;
|
|
||||||
}
|
|
||||||
menu > button > .fa-caret-down {
|
|
||||||
position: absolute;
|
|
||||||
top: 2.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
[select-get], [select-set], [point], [page] { display: none; }
|
|
||||||
.parented [select-get], .parented [select-set], .parented [point], .parented [page] { display: inherit; }
|
|
||||||
|
|
||||||
input[filename] { flex: 1; padding: 0 .5em; }
|
|
||||||
|
|
||||||
pass {
|
|
||||||
position: relative;
|
|
||||||
height: 2em;
|
|
||||||
border: 1px solid darkgrey;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
pass input { filter: blur(5px); width: 100%; border: none;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 .5em; }
|
|
||||||
pass i { position: absolute; top: .5em; right: .5em;}
|
|
||||||
pass input.clear { filter: blur(0) }
|
|
||||||
|
|
||||||
i.fa-file {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
i.fa-file.crypted {
|
|
||||||
font-size: 1.5em;
|
|
||||||
color: rebeccapurple;
|
|
||||||
}
|
|
||||||
i.fa-file i.fa-key {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 0.6em;
|
|
||||||
top: .5em;
|
|
||||||
left: -.5em;
|
|
||||||
text-shadow: 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black, 0 0 1px black;
|
|
||||||
transform: rotate(-50deg) scaleX(-1);
|
|
||||||
font-size: 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
i.fa-key {
|
|
||||||
color: lightseagreen
|
|
||||||
}
|
|
||||||
i.fa-key.pub {
|
|
||||||
color: lawngreen
|
|
||||||
}
|
|
||||||
i.fa-key.priv {
|
|
||||||
color: orangered
|
|
||||||
}
|
|
||||||
|
|
||||||
ui-toast {
|
|
||||||
position: fixed;
|
|
||||||
text-align: center;
|
|
||||||
background: white;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 60em;
|
|
||||||
margin: -1em -30em;
|
|
||||||
height: 2em;
|
|
||||||
box-shadow: 0 0.1em 1em darkgrey;
|
|
||||||
border: 1px solid darkgrey;
|
|
||||||
line-height: 2em;
|
|
||||||
}
|
|
||||||
ui-toast[error] {
|
|
||||||
color: red;
|
|
||||||
border-color: red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/solid.css" integrity="sha384-TbilV5Lbhlwdyc4RuIV/JhD8NR+BfMrvz4BL5QFa2we1hQu6wvREr3v6XSRfCTRp" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/solid.css" integrity="sha384-TbilV5Lbhlwdyc4RuIV/JhD8NR+BfMrvz4BL5QFa2we1hQu6wvREr3v6XSRfCTRp" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/regular.css" integrity="sha384-avJt9MoJH2rB4PKRsJRHZv7yiFZn8LrnXuzvmZoD3fh1aL6aM6s0BBcnCvBe6XSD" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/regular.css" integrity="sha384-avJt9MoJH2rB4PKRsJRHZv7yiFZn8LrnXuzvmZoD3fh1aL6aM6s0BBcnCvBe6XSD" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/fontawesome.css" integrity="sha384-ozJwkrqb90Oa3ZNb+yKFW2lToAWYdTiF1vt8JiH5ptTGHTGcN7qdoR1F95e0kYyG" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/fontawesome.css" integrity="sha384-ozJwkrqb90Oa3ZNb+yKFW2lToAWYdTiF1vt8JiH5ptTGHTGcN7qdoR1F95e0kYyG" crossorigin="anonymous">
|
||||||
|
|
||||||
|
|
||||||
<menu>
|
<menu>
|
||||||
<button select-get title="Get selected text on the page"
|
<button select-get title="Get selected text on the page"
|
||||||
onclick="$text.value = getParentSelection()">
|
onclick="$text.value = getParentSelection()">
|
||||||
<i class="fas fa-align-left"></i>
|
<i class="fas fa-align-left"></i>
|
||||||
<i class="fas fa-caret-down"></i>
|
<i class="fas fa-caret-down"></i>
|
||||||
</button>
|
</button>
|
||||||
<!-- <button point title="Point the element in the page (not available yet)"
|
<!-- <button point title="Point the element in the page (not available yet)"
|
||||||
disabled onclick="">
|
disabled onclick="">
|
||||||
<i class="fas fa-mouse-pointer"></i>
|
<i class="fas fa-mouse-pointer"></i>
|
||||||
</button> -->
|
</button> -->
|
||||||
<button page title="Load the whole page content"
|
<button page title="Load the whole page content"
|
||||||
onclick="$text.value = opener.document.body.textContent">
|
onclick="$text.value = opener.document.body.textContent">
|
||||||
<i class="far fa-file"></i>
|
<i class="far fa-file"></i>
|
||||||
<i class="fas fa-caret-down"></i>
|
<i class="fas fa-caret-down"></i>
|
||||||
</button>
|
</button>
|
||||||
<button open title="Load a file from disk"
|
<button open title="Load a file from disk"
|
||||||
onclick="$`[type=file]`.click()">
|
onclick="$`[type=file]`.click()">
|
||||||
<i class="far fa-folder-open"></i>
|
<i class="far fa-folder-open"></i>
|
||||||
<input type="file" onchange="read(this.files[0]).then( s=> $text.value = s ); $filename.value = this.files[0].name"/>
|
<input type="file" onchange="read(this.files[0]).then( s=> $text.value = s ); $filename.value = this.files[0].name"/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<input filename type="text" eo:placeHolder="Filo nami" en:placeHolder="File name" fr:placeHolder="Nom du fichier"/>
|
<input filename type="text" eo:placeHolder="Filo nami" en:placeHolder="File name" fr:placeHolder="Nom du fichier"/>
|
||||||
|
|
||||||
<button save en:title="Save to disk" fr:title="Enregistrer sur le disque" eo:title="Sovargi al disko"
|
<button save en:title="Save to disk" fr:title="Enregistrer sur le disque" eo:title="Sovargi al disko"
|
||||||
onclick="save($text.value,$filename.value||'krypta.krr').catch(bubbleOn(this))">
|
onclick="save($text.value,$filename.value||'krypta.krr').catch(bubbleOn(this))">
|
||||||
<i class="far fa-save"></i>
|
<i class="far fa-save"></i>
|
||||||
</button>
|
</button>
|
||||||
<button select-set en:title="Replace selected text on the page" fr:title="Remplacer la séléction" eo:title="Sovargi al disko"
|
<button select-set en:title="Replace selected text on the page" fr:title="Remplacer la séléction" eo:title="Sovargi al disko"
|
||||||
onclick="setParentSelection( $text.value )">
|
onclick="setParentSelection( $text.value )">
|
||||||
<i class="fas fa-align-left"></i>
|
<i class="fas fa-align-left"></i>
|
||||||
<i class="fas fa-caret-up"></i>
|
<i class="fas fa-caret-up"></i>
|
||||||
</button>
|
</button>
|
||||||
</menu>
|
</menu>
|
||||||
<ui-data bin></ui-data>
|
<ui-data bin></ui-data>
|
||||||
|
|
||||||
|
|
||||||
<button encrypt onclick="encrypt3($text.value).then(res=>$text.value=res,bubbleOn(this))">
|
<button encrypt onclick="encrypt3($text.value).then(res=>$text.value=res,bubbleOn(this))">
|
||||||
<i class="far fa-file crypted" style="font-size: 1.5em">
|
<i class="far fa-file crypted" style="font-size: 1.5em">
|
||||||
<i class="fas fa-key" style=""></i>
|
<i class="fas fa-key" style=""></i>
|
||||||
</i>
|
</i>
|
||||||
<lang eo>cifri</lang>
|
<lang eo>cifri</lang>
|
||||||
<lang en>encrypt</lang>
|
<lang en>encrypt</lang>
|
||||||
<lang fr>chiffrer</lang>
|
<lang fr>chiffrer</lang>
|
||||||
</button>
|
</button>
|
||||||
<!-- <button sign onclick="sign($text.value).then(res=>$text.value=res,bubbleOn(this))"> -->
|
<!-- <button sign onclick="sign($text.value).then(res=>$text.value=res,bubbleOn(this))"> -->
|
||||||
<!-- <i class="fas fa-file-contract"></i></button> -->
|
<!-- <i class="fas fa-file-contract"></i></button> -->
|
||||||
<pass>
|
<pass>
|
||||||
<input type="text" placeholder="Enter a password"/>
|
<input type="text" placeholder="Enter a password"/>
|
||||||
<i class="far fa-eye-slash" onmouseover="$pass.classList.add('clear');this.classList.remove('fa-eye-slash');this.classList.add('fa-eye')"
|
<i class="far fa-eye-slash" onmouseover="$pass.classList.add('clear');this.classList.remove('fa-eye-slash');this.classList.add('fa-eye')"
|
||||||
onmouseout="$pass.classList.remove('clear');this.classList.add('fa-eye-slash');this.classList.remove('fa-eye')"></i>
|
onmouseout="$pass.classList.remove('clear');this.classList.add('fa-eye-slash');this.classList.remove('fa-eye')"></i>
|
||||||
</pass>
|
</pass>
|
||||||
<button decrypt onclick="decrypt3($text.value).then(res=>$text.value=res,bubbleOn(this))">
|
<button decrypt onclick="decrypt3($text.value).then(res=>$text.value=res,bubbleOn(this))">
|
||||||
<i class="fas fa-file crypted" style="font-size: 1.5em">
|
<i class="fas fa-file crypted" style="font-size: 1.5em">
|
||||||
<i class="fas fa-key" style=""></i>
|
<i class="fas fa-key" style=""></i>
|
||||||
</i>
|
</i>
|
||||||
<lang eo>decifri</lang>
|
<lang eo>decifri</lang>
|
||||||
<lang en>decrypt</lang>
|
<lang en>decrypt</lang>
|
||||||
<lang fr>déchiffrer</lang>
|
<lang fr>déchiffrer</lang>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<template ui-data>
|
<template ui-data>
|
||||||
<style>
|
<style>
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
resize: none;
|
resize: none;
|
||||||
padding-bottom: 2em;
|
padding-bottom: 2em;
|
||||||
}
|
}
|
||||||
button {
|
iframe {
|
||||||
position: relative;
|
width: 100%;
|
||||||
top: -1.6em;
|
height: 100%;
|
||||||
margin-right: -1px;
|
border: 1px solid darkgrey;
|
||||||
}
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
:host-context([frame]) iframe { display: block; }
|
||||||
|
button {
|
||||||
|
position: relative;
|
||||||
|
top: -1.6em;
|
||||||
|
margin-right: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
:host-context([bin]) [bin] { color: rebeccapurple }
|
:host-context([bin]) [bin] { color: rebeccapurple }
|
||||||
:host-context([uint]) [uint] { color: rebeccapurple }
|
:host-context([uint]) [uint] { color: rebeccapurple }
|
||||||
:host-context([utf8]) [utf8] { color: rebeccapurple }
|
:host-context([utf8]) [utf8] { color: rebeccapurple }
|
||||||
:host-context([base64]) [base64] { color: rebeccapurple }
|
:host-context([base64]) [base64] { color: rebeccapurple }
|
||||||
:host-context([frame]) [frame] { color: rebeccapurple }
|
:host-context([frame]) [frame] { color: rebeccapurple }
|
||||||
</style>
|
</style>
|
||||||
<textarea></textarea>
|
<textarea></textarea>
|
||||||
|
<iframe></iframe>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script ui src="./ui.js"></script>
|
<script ui src="./ui.js"></script>
|
||||||
|
|
220
app.js
220
app.js
|
@ -1,11 +1,11 @@
|
||||||
|
|
||||||
const algo = { name: "AES-GCM", iv: Uint8Array.from([120,1,248,135,62,71,87,156,92,67,155,37]) }
|
const algo = { name: "AES-GCM", iv: Uint8Array.from([120,1,248,135,62,71,87,156,92,67,155,37]) }
|
||||||
, parseAlgo = str=> ({ name: str.split(' ')[0], iv: Uint8Array.from(str.split(' ').slice(1)) })
|
, parseAlgo = str=> ({ name: str.split(' ')[0], iv: Uint8Array.from(str.split(' ').slice(1)) })
|
||||||
, algos = [ "AES-GCM 120 1 248 135 62 71 87 156 92 67 155 37", 'AES-CBC' ]
|
, algos = [ "AES-GCM 120 1 248 135 62 71 87 156 92 67 155 37", 'AES-CBC' ]
|
||||||
//, algo = parseAlgo(algos[0])
|
//, algo = parseAlgo(algos[0])
|
||||||
, $pass = $`pass input`
|
, $pass = $`pass input`
|
||||||
, $text = $`ui-data`
|
, $text = $`ui-data`
|
||||||
, $filename = $`[filename]`
|
, $filename = $`[filename]`
|
||||||
|
|
||||||
opener && document.documentElement.classtList.add('parented')
|
opener && document.documentElement.classtList.add('parented')
|
||||||
// sel=opener.document.getSelection().type=='None'||'Range'
|
// sel=opener.document.getSelection().type=='None'||'Range'
|
||||||
|
@ -17,127 +17,128 @@ let key
|
||||||
const ivLen = 16 // the IV is always 16 bytes
|
const ivLen = 16 // the IV is always 16 bytes
|
||||||
|
|
||||||
$pass.onchange = async e=>
|
$pass.onchange = async e=>
|
||||||
key = await crypto.subtle.importKey(
|
key = await crypto.subtle.importKey(
|
||||||
'raw'
|
'raw'
|
||||||
, await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
, await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
||||||
, algo, false, ['encrypt','decrypt']
|
, algo, false, ['encrypt','decrypt']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const joinIvAndData = (iv, data)=> {
|
const joinIvAndData = (iv, data)=> {
|
||||||
var buf = new Uint8Array(iv.length + data.length)
|
var buf = new Uint8Array(iv.length + data.length)
|
||||||
Array.prototype.forEach.call(iv, (byte, i)=> buf[i] = byte )
|
Array.prototype.forEach.call(iv, (byte, i)=> buf[i] = byte )
|
||||||
Array.prototype.forEach.call(data, (byte, i)=> buf[ivLen + i] = byte )
|
Array.prototype.forEach.call(data, (byte, i)=> buf[ivLen + i] = byte )
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
const separateIvFromData = buf=> Array.prototype.reduce.call( new Uint8Array(buf),
|
const separateIvFromData = buf=> Array.prototype.reduce.call( new Uint8Array(buf),
|
||||||
( res, byte, i )=>
|
( res, byte, i )=>
|
||||||
i < ivLen
|
i < ivLen
|
||||||
? ( res.iv[i] = byte, res )
|
? ( res.iv[i] = byte, res )
|
||||||
: ( res.data[i - ivLen] = byte, res )
|
: ( res.data[i - ivLen] = byte, res )
|
||||||
, { iv: new Uint8Array(ivLen), data: new Uint8Array(buf.byteLength - ivLen) } )
|
, { iv: new Uint8Array(ivLen), data: new Uint8Array(buf.byteLength - ivLen) } )
|
||||||
|
|
||||||
|
|
||||||
const encrypt = ()=> {
|
const encrypt = ()=> {
|
||||||
return crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
return crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
||||||
.then( pwHash=> crypto.subtle.importKey('raw', pwHash, algo, false, ['encrypt']) )
|
.then( pwHash=> crypto.subtle.importKey('raw', pwHash, algo, false, ['encrypt']) )
|
||||||
.then( key=> crypto.subtle.encrypt(algo, key, new TextEncoder().encode($text.value)) )
|
.then( key=> crypto.subtle.encrypt(algo, key, new TextEncoder().encode($text.value)) )
|
||||||
//.then( ctBuffer=> $text.value = new Uint8Array(ctBuffer).toString().replace(/,/g,'O') )
|
//.then( ctBuffer=> $text.value = new Uint8Array(ctBuffer).toString().replace(/,/g,'O') )
|
||||||
.then( ctBuffer=> $text.value = uint2String( new Uint8Array(ctBuffer) ) )
|
.then( ctBuffer=> $text.value = uint2String( new Uint8Array(ctBuffer) ) )
|
||||||
//.then( ctBuffer=> $text.value = new TextDecoder().decode(new Uint8Array(ctBuffer)) )
|
//.then( ctBuffer=> $text.value = new TextDecoder().decode(new Uint8Array(ctBuffer)) )
|
||||||
|
|
||||||
//.catch( e=> pulse('red') )
|
//.catch( e=> pulse('red') )
|
||||||
}
|
}
|
||||||
const encrypt2 = str=>
|
const encrypt2 = str=>
|
||||||
crypto.subtle.encrypt( algo, key, new TextEncoder().encode(str) )
|
crypto.subtle.encrypt( algo, key, new TextEncoder().encode(str) )
|
||||||
//.then( ctBuffer=> $text.value = new Uint8Array(ctBuffer).toString().replace(/,/g,'O') )
|
//.then( ctBuffer=> $text.value = new Uint8Array(ctBuffer).toString().replace(/,/g,'O') )
|
||||||
// .then( ctBuffer=> uint2String(new Uint8Array(ctBuffer)) )
|
// .then( ctBuffer=> uint2String(new Uint8Array(ctBuffer)) )
|
||||||
.then( ctBuffer=> $text.value = new TextDecoder().decode(new Uint8Array(ctBuffer)) )
|
.then( ctBuffer=> $text.value = new TextDecoder().decode(new Uint8Array(ctBuffer)) )
|
||||||
|
|
||||||
//.catch( e=> pulse('red') )
|
//.catch( e=> pulse('red') )
|
||||||
|
|
||||||
const encrypt3 = ( data, iv = crypto.getRandomValues(new Uint8Array(ivLen)) )=>
|
const encrypt3 = ( data, iv = crypto.getRandomValues(new Uint8Array(ivLen)) )=>
|
||||||
crypto.subtle.encrypt( {name: 'AES-GCM', iv}, key, data )
|
crypto.subtle.encrypt( {name: 'AES-GCM', iv}, key, data )
|
||||||
// .then( encrypted=> bufferToBinaryString( joinIvAndData(iv, new Uint8Array(encrypted)).buffer ) )
|
// .then( encrypted=> bufferToBinaryString( joinIvAndData(iv, new Uint8Array(encrypted)).buffer ) )
|
||||||
.then( encrypted=> joinIvAndData(iv, new Uint8Array(encrypted)).buffer )
|
.then( encrypted=> joinIvAndData(iv, new Uint8Array(encrypted)).buffer )
|
||||||
// var base64 = Unibabel.bufferToBase64(ciphered)
|
// var base64 = Unibabel.bufferToBase64(ciphered)
|
||||||
// .replace(/\-/g, '+')
|
// .replace(/\-/g, '+')
|
||||||
// .replace(/_/g, '\/')
|
// .replace(/_/g, '\/')
|
||||||
// ;
|
// ;
|
||||||
|
|
||||||
// while (base64.length % 4) {
|
// while (base64.length % 4) {
|
||||||
// base64 += '=';
|
// base64 += '=';
|
||||||
// }
|
// }
|
||||||
// return base64;
|
// return base64;
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
|
||||||
const decrypt = ()=> {
|
const decrypt = ()=> {
|
||||||
return crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
return crypto.subtle.digest( 'SHA-256', new TextEncoder().encode($pass.value) )
|
||||||
.then( pwHash=> crypto.subtle.importKey('raw', pwHash, algo, false, ['decrypt']) )
|
.then( pwHash=> crypto.subtle.importKey('raw', pwHash, algo, false, ['decrypt']) )
|
||||||
//.then( key=> crypto.subtle.decrypt(algo, key, Uint8Array.from($text.value.split('O').map(Number)).buffer ) )
|
//.then( key=> crypto.subtle.decrypt(algo, key, Uint8Array.from($text.value.split('O').map(Number)).buffer ) )
|
||||||
.then( key=> crypto.subtle.decrypt(algo, key, string2Uint($text.value).buffer ) )
|
.then( key=> crypto.subtle.decrypt(algo, key, string2Uint($text.value).buffer ) )
|
||||||
//.then( key=> crypto.subtle.decrypt(algo, key, new TextEncoder().encode($text.value).buffer ) )
|
//.then( key=> crypto.subtle.decrypt(algo, key, new TextEncoder().encode($text.value).buffer ) )
|
||||||
.then( ptBuffer=> $text.value = new TextDecoder().decode(ptBuffer) )
|
.then( ptBuffer=> $text.value = new TextDecoder().decode(ptBuffer) )
|
||||||
|
|
||||||
//.catch( e=> pulse('red') )
|
//.catch( e=> pulse('red') )
|
||||||
}
|
}
|
||||||
|
|
||||||
const decrypt3 = buf=> {
|
const decrypt3 = buf=> {
|
||||||
let { iv, data } = separateIvFromData( buf )
|
let { iv, data } = separateIvFromData( buf )
|
||||||
|
|
||||||
return crypto.subtle.decrypt(
|
return crypto.subtle.decrypt(
|
||||||
{ name: 'AES-GCM', iv }
|
{ name: 'AES-GCM', iv }
|
||||||
, key
|
, key
|
||||||
, data
|
, data
|
||||||
)/*.then(function (decrypted) {
|
)
|
||||||
var base64 = bufferToUtf8(new Uint8Array(decrypted))
|
/*.then(function (decrypted) {
|
||||||
// .replace(/\-/g, '+')
|
var base64 = bufferToUtf8(new Uint8Array(decrypted))
|
||||||
// .replace(/_/g, '\/')
|
// .replace(/\-/g, '+')
|
||||||
// ;
|
// .replace(/_/g, '\/')
|
||||||
|
// ;
|
||||||
|
|
||||||
// while (base64.length % 4) {
|
// while (base64.length % 4) {
|
||||||
// base64 += '=';
|
// base64 += '=';
|
||||||
// }
|
// }
|
||||||
return base64
|
return base64
|
||||||
})*/
|
})*/
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastSaved
|
var lastSaved
|
||||||
const save = ( content, filename )=> {
|
const save = ( content, filename )=> {
|
||||||
let link = document.createElement('a')
|
let link = document.createElement('a')
|
||||||
link.setAttribute( 'download', filename )
|
link.setAttribute( 'download', filename )
|
||||||
// If we are replacing a previously generated file we need to
|
// If we are replacing a previously generated file we need to
|
||||||
// manually revoke the object URL to avoid memory leaks.
|
// manually revoke the object URL to avoid memory leaks.
|
||||||
lastSaved && URL.revokeObjectURL( lastSaved )
|
lastSaved && URL.revokeObjectURL( lastSaved )
|
||||||
link.href = lastSaved = URL.createObjectURL( new Blob([content], {type: 'text/plain'}) )
|
link.href = lastSaved = URL.createObjectURL( new Blob([content], {type: 'text/plain'}) )
|
||||||
//link.href = makeTextFile( content )
|
//link.href = makeTextFile( content )
|
||||||
document.body.appendChild( link )
|
document.body.appendChild( link )
|
||||||
|
|
||||||
// wait for the link to be added to the document
|
// wait for the link to be added to the document
|
||||||
window.requestAnimationFrame( e=> {
|
window.requestAnimationFrame( e=> {
|
||||||
link.dispatchEvent( new MouseEvent('click') )
|
link.dispatchEvent( new MouseEvent('click') )
|
||||||
document.body.removeChild( link )
|
document.body.removeChild( link )
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const read = file=> new Promise( (ok,ko)=> {
|
const read = file=> new Promise( (ok,ko)=> {
|
||||||
let r = new FileReader()
|
let r = new FileReader()
|
||||||
r.onload = e=> ok( r.result )
|
r.onload = e=> ok( r.result )
|
||||||
r.onerror = e=> ko()
|
r.onerror = e=> ko()
|
||||||
r.readAsText( file )
|
r.readAsText( file )
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
var textFile
|
var textFile
|
||||||
const makeTextFile = text=> {
|
const makeTextFile = text=> {
|
||||||
|
|
||||||
textFile && URL.revokeObjectURL(textFile)
|
textFile && URL.revokeObjectURL(textFile)
|
||||||
textFile = URL.createObjectURL( new Blob([text], {type: 'text/plain'}) )
|
textFile = URL.createObjectURL( new Blob([text], {type: 'text/plain'}) )
|
||||||
|
|
||||||
// returns a URL you can use as a href
|
// returns a URL you can use as a href
|
||||||
return textFile
|
return textFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,8 +149,8 @@ const save2 = (file, content, type)=> window.open( "data:application/octet-strea
|
||||||
const uint2string = uintArray=> String.fromCharCode.apply( null, uintArray )
|
const uint2string = uintArray=> String.fromCharCode.apply( null, uintArray )
|
||||||
|
|
||||||
const string2uint = string=> new Uint8Array(
|
const string2uint = string=> new Uint8Array(
|
||||||
btoa( string ).split('')
|
btoa( string ).split('')
|
||||||
.map( l=>l.charCodeAt(0) )
|
.map( l=>l.charCodeAt(0) )
|
||||||
)
|
)
|
||||||
|
|
||||||
const buffer2bin = buf=> Array.prototype.map.call( new Uint8Array(buf), ch=> String.fromCharCode(ch) ).join('')
|
const buffer2bin = buf=> Array.prototype.map.call( new Uint8Array(buf), ch=> String.fromCharCode(ch) ).join('')
|
||||||
|
@ -157,7 +158,7 @@ const buffer2bin = buf=> Array.prototype.map.call( new Uint8Array(buf), ch=> Str
|
||||||
const buffer2base64 = arr=> btoa( buffer2bin(arr) )
|
const buffer2base64 = arr=> btoa( buffer2bin(arr) )
|
||||||
|
|
||||||
const bin2buffer = binstr=> new Uint8Array(
|
const bin2buffer = binstr=> new Uint8Array(
|
||||||
Array.prototype.map.call( new Uint8Array( binstr.length ), (ch, i)=> binstr.charCodeAt(i) )
|
Array.prototype.map.call( new Uint8Array( binstr.length ), (ch, i)=> binstr.charCodeAt(i) )
|
||||||
)
|
)
|
||||||
|
|
||||||
const base642buffer = base64=> bin2buffer( atob(base64) )
|
const base642buffer = base64=> bin2buffer( atob(base64) )
|
||||||
|
@ -179,31 +180,32 @@ const utf82buffer = str=> bin2buffer(utf82bin(str))
|
||||||
|
|
||||||
|
|
||||||
const CC = {
|
const CC = {
|
||||||
uint2string
|
uint2string
|
||||||
, string2uint
|
, string2uint
|
||||||
, buffer2bin
|
, buffer2bin
|
||||||
, buffer2base64
|
, buffer2uint: buf=> new Uint8Array(buf)
|
||||||
, buffer2utf8
|
, buffer2utf8
|
||||||
, bin2buffer
|
, buffer2base64
|
||||||
, base642buffer
|
, bin2buffer
|
||||||
, bin2utf8
|
, base642buffer
|
||||||
, utf82bin
|
, bin2utf8
|
||||||
, utf82buffer
|
, utf82bin
|
||||||
|
, utf82buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
/*const uintToString = (uintArray)=> {
|
/*const uintToString = (uintArray)=> {
|
||||||
var encodedString = String.fromCharCode.apply(null, uintArray),
|
var encodedString = String.fromCharCode.apply(null, uintArray),
|
||||||
decodedString = decodeURIComponent(escape(encodedString));
|
decodedString = decodeURIComponent(escape(encodedString));
|
||||||
return decodedString;
|
return decodedString;
|
||||||
}
|
}
|
||||||
function stringToUint(string) {
|
function stringToUint(string) {
|
||||||
var string = btoa(unescape(encodeURIComponent(string))),
|
var string = btoa(unescape(encodeURIComponent(string))),
|
||||||
charList = string.split(''),
|
charList = string.split(''),
|
||||||
uintArray = [];
|
uintArray = [];
|
||||||
for (var i = 0; i < charList.length; i++) {
|
for (var i = 0; i < charList.length; i++) {
|
||||||
uintArray.push(charList[i].charCodeAt(0));
|
uintArray.push(charList[i].charCodeAt(0));
|
||||||
}
|
}
|
||||||
return new Uint8Array(uintArray);
|
return new Uint8Array(uintArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,5 +16,10 @@ writeFileSync(
|
||||||
? `<script ${$1.replace(src,'')}>/*${$1.match(src)[1]}*/${readFileSync('./'+$1.match(src)[1], 'utf8')}</script>`
|
? `<script ${$1.replace(src,'')}>/*${$1.match(src)[1]}*/${readFileSync('./'+$1.match(src)[1], 'utf8')}</script>`
|
||||||
: s
|
: s
|
||||||
)
|
)
|
||||||
|
.replace(/<link rel="stylesheet" (.*?)>/g
|
||||||
|
, (s,$1)=> $1.match(href) && !/^https?:\/\//.test( $1.match(href)[1] )
|
||||||
|
? `<style ${$1.replace(href,'')}>/*${$1.match(href)[1]}*/${readFileSync('./'+$1.match(href)[1], 'utf8')}</style>`
|
||||||
|
: s
|
||||||
|
)
|
||||||
, 'utf8'
|
, 'utf8'
|
||||||
)
|
)
|
||||||
|
|
54
lang.js
54
lang.js
|
@ -1,50 +1,50 @@
|
||||||
/*
|
/*
|
||||||
<style lang="eo,en,fr">
|
<style lang="eo,en,fr">
|
||||||
[_lang_] { display: none; }
|
[_lang_] { display: none; }
|
||||||
:root[lang=_lang_] [_lang_] { display: inherit; }
|
:root[lang=_lang_] [_lang_] { display: inherit; }
|
||||||
|
|
||||||
:root[lang=_lang_] [_lang_\:title]:hover::after { display: inherit }
|
:root[lang=_lang_] [_lang_\:title]:hover::after { display: inherit }
|
||||||
|
|
||||||
/* [_lang_\:title]:after {
|
/* [_lang_\:title]:after {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: grey;
|
background: grey;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
color: white;
|
color: white;
|
||||||
padding: .1em .3em;
|
padding: .1em .3em;
|
||||||
margin-left: -5%;
|
margin-left: -5%;
|
||||||
}
|
}
|
||||||
:root[lang=_lang_] [_lang_\:title]:after { content: attr(_lang_\:title) } * /
|
:root[lang=_lang_] [_lang_\:title]:after { content: attr(_lang_\:title) } * /
|
||||||
</style>
|
</style>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// lang support
|
// lang support
|
||||||
Object.defineProperty( window, 'lang', {
|
Object.defineProperty( window, 'lang', {
|
||||||
get: ()=> $`:root`.getAttribute('lang')
|
get: ()=> $`:root`.getAttribute('lang')
|
||||||
, set: v=> $`:root`.setAttribute( 'lang', v )
|
, set: v=> $`:root`.setAttribute( 'lang', v )
|
||||||
})
|
})
|
||||||
|
|
||||||
new MutationObserver( changes=>
|
new MutationObserver( changes=>
|
||||||
changes.map( ch=> ch.attributeName == 'lang' && $`:root`.dispatchEvent(new Event('lang')) )
|
changes.map( ch=> ch.attributeName == 'lang' && $`:root`.dispatchEvent(new Event('lang')) )
|
||||||
)
|
)
|
||||||
.observe( $`:root`, { attributes: true } )
|
.observe( $`:root`, { attributes: true } )
|
||||||
|
|
||||||
const lgAttReg = new RegExp( '^('+$`style[lang]`.attributes.lang.value.split(',').join('|')+'):')
|
const lgAttReg = new RegExp( '^('+$`style[lang]`.attributes.lang.value.split(',').join('|')+'):')
|
||||||
$`:root`.on`lang`( e=>
|
$`:root`.on`lang`( e=>
|
||||||
$`*`.map( node=> [...node.attributes]
|
$`*`.map( node=> [...node.attributes]
|
||||||
.filter( att=> lgAttReg.test(att.name) )
|
.filter( att=> lgAttReg.test(att.name) )
|
||||||
)
|
)
|
||||||
.filter( arr=> arr.length )
|
.filter( arr=> arr.length )
|
||||||
.map( atts=> atts.filter(att=>new RegExp('^'+lang+':').test(att.name))
|
.map( atts=> atts.filter(att=>new RegExp('^'+lang+':').test(att.name))
|
||||||
.map(att=> att.ownerElement.setAttribute(att.name.split(':').pop(),att.value) ))
|
.map(att=> att.ownerElement.setAttribute(att.name.split(':').pop(),att.value) ))
|
||||||
)
|
)
|
||||||
// compile lang styles
|
// compile lang styles
|
||||||
Q`style[lang]`.map( style=>
|
Q`style[lang]`.map( style=>
|
||||||
style.innerHTML = [...style.sheet.rules]
|
style.innerHTML = [...style.sheet.rules]
|
||||||
.map( r=> style.attributes.lang.value.split(',')
|
.map( r=> style.attributes.lang.value.split(',')
|
||||||
.map( lg=> r.cssText.replace(/_lang_/g, lg) )
|
.map( lg=> r.cssText.replace(/_lang_/g, lg) )
|
||||||
.join('\n')
|
.join('\n')
|
||||||
)
|
)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
)
|
)
|
||||||
lang = navigator.language
|
lang = navigator.language
|
||||||
|
|
129
ui.js
129
ui.js
|
@ -1,96 +1,97 @@
|
||||||
const _s = (ss, ...pp)=> ss.map((s,i)=> s + (pp[i]||'')).join('')
|
const _s = (ss, ...pp)=> ss.map((s,i)=> s + (pp[i]||'')).join('')
|
||||||
, _a = iterable=> iterable.length > 1 ? iterable : iterable[0]
|
, _a = iterable=> iterable.length > 1 ? iterable : iterable[0]
|
||||||
, Q = (ss, ...pp)=> [...document.querySelectorAll( _s(ss, ...pp))]
|
, Q = (ss, ...pp)=> [...document.querySelectorAll( _s(ss, ...pp))]
|
||||||
, T = document.createElement('template')
|
, T = document.createElement('template')
|
||||||
, DOM = (ss, ...pp)=> ( T.innerHTML = _s(ss, ...pp), document.adoptNode(T.content).childNodes )
|
, DOM = (ss, ...pp)=> ( T.innerHTML = _s(ss, ...pp), document.adoptNode(T.content).childNodes )
|
||||||
, $ = (ss, ...pp)=> _a( ~_s(ss, ...pp).indexOf('<') ? DOM(ss, ...pp) : Q(ss, ...pp) )
|
, $ = (ss, ...pp)=> _a( ~_s(ss, ...pp).indexOf('<') ? DOM(ss, ...pp) : Q(ss, ...pp) )
|
||||||
|
|
||||||
, notify = (html,attrs)=> $`body`.add( $`<ui-toast ${attrs ? attrs : ''}>${html}</ui-toast>` )
|
, notify = (html,attrs)=> $`body`.add( $`<ui-toast ${attrs ? attrs : ''}>${html}</ui-toast>` )
|
||||||
, bubbleOn = el=> e=> el.dispatchEvent( new ErrorEvent('Error',{bubbles:true, error:e}) )
|
, bubbleOn = el=> e=> el.dispatchEvent( new ErrorEvent('Error',{bubbles:true, error:e}) )
|
||||||
, pulse = color=> (document.body.style.background = color) && setTimeout(o=> document.body.style.background = 'lightgrey', 1000)
|
, pulse = color=> (document.body.style.background = color) && setTimeout(o=> document.body.style.background = 'lightgrey', 1000)
|
||||||
|
|
||||||
Node.prototype.add = function( ...els )
|
Node.prototype.add = function( ...els )
|
||||||
{
|
{
|
||||||
this.append( ...els )
|
this.append( ...els )
|
||||||
return _a( els )
|
return _a( els )
|
||||||
}
|
}
|
||||||
Node.prototype.$ = function(ss, ...pp)
|
Node.prototype.$ = function(ss, ...pp)
|
||||||
{
|
{
|
||||||
return _a(
|
return _a(
|
||||||
~_s(ss, ...pp).indexOf('<')
|
~_s(ss, ...pp).indexOf('<')
|
||||||
? this.add(...DOM(ss, ...pp))
|
? this.add(...DOM(ss, ...pp))
|
||||||
: [...this.querySelectorAll( _s(ss, ...pp))]
|
: [...this.querySelectorAll( _s(ss, ...pp))]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Node.prototype.on = function(ss, ...pp)
|
Node.prototype.on = function(ss, ...pp)
|
||||||
{
|
{
|
||||||
let types = _s( [].concat(ss), ...pp ).split(/[,\s]/)
|
let types = _s( [].concat(ss), ...pp ).split(/[,\s]/)
|
||||||
return (...args)=> ( types.map( type=> this.addEventListener(type,...args)), this )
|
return (...args)=> ( types.map( type=> this.addEventListener(type,...args)), this )
|
||||||
}
|
}
|
||||||
|
|
||||||
class UI extends HTMLElement {
|
class UI extends HTMLElement {
|
||||||
static get is(){ return 'ui' + this.name.replace(/[A-Z]/g, L=> '-'+L.toLocaleLowerCase()) }
|
static get is(){ return 'ui' + this.name.replace(/[A-Z]/g, L=> '-'+L.toLocaleLowerCase()) }
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super()
|
super()
|
||||||
let tpl = $`template[${this.constructor.is}]`
|
let tpl = $`template[${this.constructor.is}]`
|
||||||
tpl && this.attachShadow({mode: 'open'})
|
tpl && this.attachShadow({mode: 'open'})
|
||||||
.appendChild( tpl.content.cloneNode(true) )
|
.appendChild( tpl.content.cloneNode(true) )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Toast extends UI {
|
class Toast extends UI {
|
||||||
static get duration(){ return this._duration || 10000 }
|
static get duration(){ return this._duration || 10000 }
|
||||||
static set duration( v ){ this._duration = v }
|
static set duration( v ){ this._duration = v }
|
||||||
connectedCallback()
|
connectedCallback()
|
||||||
{
|
{
|
||||||
setTimeout( toast=> toast.remove(), this.getAttribute('duration') || Toast.duration, this )
|
setTimeout( toast=> toast.remove(), this.getAttribute('duration') || Toast.duration, this )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Data extends UI {
|
class Data extends UI {
|
||||||
static get observedAttributes(){ return ['bin','uint','utf8','base64','frame'] }
|
static get observedAttributes(){ return ['bin','uint','utf8','base64','frame'] }
|
||||||
connectedCallback()
|
connectedCallback()
|
||||||
{
|
{
|
||||||
// this.observer = new MutationObserver(
|
// this.observer = new MutationObserver(
|
||||||
// changes=> console.log(changes.filter(m=>~['bin','255','utf8','base64','object'].indexOf(m.attributeName)) )
|
// changes=> console.log(changes.filter(m=>~['bin','255','utf8','base64','object'].indexOf(m.attributeName)) )
|
||||||
// )
|
// )
|
||||||
// .observe( this, { attributes: true } )
|
// .observe( this, { attributes: true } )
|
||||||
this.shadowRoot.append(
|
this.shadowRoot.append(
|
||||||
...Data.observedAttributes
|
...Data.observedAttributes
|
||||||
.map( type=> $`<button ${type}>${type}</button>`
|
.map( type=> $`<button ${type}>${type}</button>`
|
||||||
.on`click`( e=> this.type = e.target.innerText )
|
.on`click`( e=> this.type = e.target.innerText )
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// this.shadowRoot.$`button`.map( button=> button.onclick = e=> this.type = e.target.innerText )
|
// this.shadowRoot.$`button`.map( button=> button.onclick = e=> this.type = e.target.innerText )
|
||||||
this.shadowRoot.$`textarea`.on`change`( e=> this._value = CC[this.type+'2buffer'](e.target.value) )
|
this.shadowRoot.$`textarea`.on`change`( e=> this._value = CC[this.type+'2buffer'](e.target.value) )
|
||||||
}
|
}
|
||||||
|
|
||||||
get type(){ return this.attributes[0].name }
|
get type(){ return this.attributes[0].name }
|
||||||
set type( v ){
|
set type( v ){
|
||||||
if( this.type == v ) return;
|
if( this.type == v ) return;
|
||||||
[...this.attributes].map( o=> o.ownerElement.removeAttributeNode(o) )
|
[...this.attributes].map( o=> o.ownerElement.removeAttributeNode(o) )
|
||||||
this.setAttribute( v, '' )
|
this.setAttribute( v, '' )
|
||||||
}
|
}
|
||||||
|
|
||||||
get value(){ return this._value }
|
get value(){ return this._value }
|
||||||
set value( v ){ this._value = v; this.shadowRoot.$`textarea`.value = CC['buffer2'+this.type](v) }
|
set value( v ){ this._value = v; this.shadowRoot.$`textarea`.value = CC['buffer2'+this.type](v) }
|
||||||
|
|
||||||
attributeChangedCallback(name, oldValue, newValue)
|
attributeChangedCallback(name, oldValue, newValue)
|
||||||
{
|
{
|
||||||
if( this.shadowRoot.$`textarea`.value )
|
console.log(arguments)
|
||||||
this.shadowRoot.$`textarea`.value = CC['buffer2'+this.type]( this._value )
|
if( newValue != null && this._value && CC['buffer2'+this.type] )
|
||||||
}
|
this.shadowRoot.$`textarea`.value = CC['buffer2'+this.type]( this._value )
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
;[ Toast, Data ]
|
;[ Toast, Data ]
|
||||||
.map( klass=> customElements.define(klass.is, klass) )
|
.map( klass=> customElements.define(klass.is, klass) )
|
||||||
|
|
||||||
// document.body.addEventListener('Error', e=> pulse('red') && (notify(e.error.message).style.color = 'red') )
|
// document.body.addEventListener('Error', e=> pulse('red') && (notify(e.error.message).style.color = 'red') )
|
||||||
$`body`.on`Error`( e=>
|
$`body`.on`Error`( e=>
|
||||||
pulse('red') && notify(
|
pulse('red') && notify(
|
||||||
`<b>${e.error.code||''} ${e.error.name}</b>: ${e.error.message}`
|
`<b>${e.error.code||''} ${e.error.name}</b>: ${e.error.message}`
|
||||||
, 'error')
|
, 'error')
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue