index.html (10737B)
1 <html> 2 <head> 3 <title>Forro contact form</title> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <script defer src="node_modules/alpinejs/dist/cdn.min.js"></script> 7 <script src="node_modules/openpgp/dist/openpgp.min.js"></script> 8 <script src="node_modules/jssha/dist/sha256.js"></script> 9 <script src="node_modules/MimeJS/src/base64.js"></script> 10 <script src="node_modules/MimeJS/dist/mime-js.min.js"></script> 11 <script src="top.js"></script> 12 <script src="key.js"></script> 13 <script src="name.js"></script> 14 <script src="help.js"></script> 15 <script src="app.js"></script> 16 <link rel="stylesheet" type="text/css" href="style.css"></link> 17 </head> 18 19 <body 20 21 x-data="{ 22 unlock_set: false, 23 message_status: '', 24 message_count: g_counter, 25 last_state: 0, 26 27 get haveSettings() { 28 return this.last_state & STATE['SETTINGS'] > 0; 29 }, 30 31 get isDev() { 32 return checkState(this.last_state, STATE.DEV); 33 }, 34 35 get isHelp() { 36 return checkState(this.last_state, STATE.HELP); 37 }, 38 39 get lastState() { 40 return debugState(this.last_state); 41 }, 42 43 get haveKey() { 44 return checkState(this.last_state, STATE['LOCAL_KEY']); 45 }, 46 47 get unlockedKey() { 48 return checkState(this.last_state, STATE['LOCAL_KEY_DECRYPTED']); 49 }, 50 51 get haveRemoteKey() { 52 return checkState(this.last_state, STATE['REMOTE_KEY']); 53 }, 54 55 get getTitle() { 56 return 'forro v' + g_version; 57 }, 58 59 }" 60 61 x-init="unlockLocalKey();" 62 63 @messagestatechange.window=' 64 message_status = $event.detail.s; 65 message_count = $event.detail.c; 66 last_state = $event.detail.state; 67 if (!unlock_set && checkState(STATE["LOCAL_KEY_DECRYPTED"])) { 68 unlock_set = true; 69 $dispatch("unlocked"); 70 } 71 if (checkState(STATE["PASSPHRASE_FAIL"])) { 72 $dispatch("passfail"); 73 } 74 if (checkState(STATE["ACK_MESSAGE"])) { 75 $dispatch("rcpt", {v: $event.detail.s[1]}); 76 } 77 ' 78 79 @unlocked='applyLocalKey();' 80 81 @rst.window='unlock_set = false; message_count = 0' 82 83 > 84 85 86 87 <div id="app"> 88 89 90 <h1><a href="https://defalsify.org/git/forro" 91 x-text="getTitle"></a> 92 </h1> 93 94 95 <div id="localkey" x-data="{ 96 passphrase_cache: '', 97 passphrase_status: '', 98 passphrase_default_status: 'create key with passphrase', 99 get passphraseStatus() { 100 return this.passphrase_status ? this.passphrase_status : this.passphrase_default_status; 101 }, 102 }" 103 104 105 106 x-show='!unlockedKey' 107 class="localkey-container" 108 109 > 110 <input name="pwd" type="password" 111 x-model='passphrase_cache' 112 x-bind:placeholder='passphraseStatus' 113 @passfail.window='passphrase_status = "wrong_passphrase"; passphrase_cache = "";'; 114 @messagestatechange.window='if (checkState(STATE["LOCAL_KEY"])) { passphrase_default_status = "please unlock key"; };' 115 @rst.window='passphrase_status = "please create new key";' 116 class="input-passphrase" 117 118 > 119 <button x-data="{ 120 go_label: 'go', 121 }" 122 123 x-text='go_label' 124 125 @passfail.window='go_label = "go again";' 126 @rst.window='go_label = "go";' 127 128 @click='setPwd(passphrase_cache);' 129 class="btn-primary" 130 131 ></button> 132 133 134 <button x-show='!haveKey' @click='tryHelpFor("nopass"); setPwd();' class="btn-secondary" 135 >without passphrase</button> 136 </div> 137 138 139 <div class="container"> 140 141 <div class="form" id="message_panel" 142 x-show='unlockedKey' 143 x-init='setUp();' 144 x-data="{ 145 rcpt: '', 146 content: '', 147 key: '', 148 rkey: '', 149 key_name: '', 150 rkey_name: '', 151 defaultname: true, 152 key_content: '', 153 rkey_content: '', 154 filez: {}, 155 156 get localKeyArmor() { 157 return 'data:text/plain;charset=utf8,' + this.key_content; 158 }, 159 get localKeyFilename() { 160 return 'privatekey_' + this.key + '.asc'; 161 }, 162 get remoteKeyArmor() { 163 return 'data:text/plain;charset=utf8,' + this.rkey_content; 164 }, 165 get remoteKeyFilename() { 166 return 'publickey_' + this.rkey + '.asc'; 167 }, 168 get keyDisplay() { 169 if (!this.key) { 170 return ''; 171 } 172 return this.key + ' (' + this.key_name + ')'; 173 }, 174 get rkeyDisplay() { 175 if (!this.rkey) { 176 return ''; 177 } 178 return this.rkey + ' (' + this.rkey_name + ')'; 179 }, 180 181 addFileToList(k, v) { 182 this.filez[k] = v; 183 document.getElementById('fileAdder').value=''; 184 }, 185 186 purgeFiles() { 187 this.filez = {}; 188 }, 189 190 get fileList() { 191 let files = []; 192 for (const k in this.filez) { 193 files.push(this.filez[k] + ' (' + k.substring(0, 8) + ')'); 194 } 195 return files; 196 }, 197 }" 198 @rcpt.window='rcpt = $event.detail.v;' 199 @messagestatechange.window=' 200 key = $event.detail.kl; 201 rkey = $event.detail.kr; 202 key_name = $event.detail.nl; 203 rkey_name = $event.detail.nr; 204 205 if (checkState(STATE["LOCAL_KEY"])) { 206 if (key_content == "" | key_content == null) { 207 key_content = g_local_key.armor(); 208 } 209 } 210 if (checkState(STATE["REMOTE_KEY"])) { 211 if (rkey_content == "") { 212 rkey_content = g_remote_key.armor(); 213 } 214 } 215 if (checkState(STATE.ACK_MESSAGE)) { 216 purgeFiles(); 217 document.getElementById("fileAdder").value=""; 218 } 219 '> 220 221 222 <div id="helpdiv" 223 x-show="isHelp" 224 > 225 <h3 class="title">Let's get in touch</h3> 226 227 <template x-data="{ 228 help_cap: 128, // if set to 0 then no buffer 229 help_display_count: 5, // if help_cap is 0, ignore 230 help_contents: [], 231 help_lastcount: 0, 232 help_count: 0, 233 help_crsr: -1, 234 235 addHelpContents(v) { 236 if (this.help_cap == 0) { 237 this.help_display_count = v.length; 238 this.help_count = v.length; 239 this.help_contents = v; 240 this.help_crsr = this.help_count - 1; 241 } else { 242 for (let i = 0; i < v.length; i++) { 243 this.help_crsr += 1; 244 this.help_crsr %= this.help_cap; 245 this.help_contents[this.help_crsr] = v[i]; 246 if (this.help_count < this.help_cap) { 247 this.help_count += 1; 248 } 249 } 250 } 251 if (v.length > 0) { 252 this.help_lastcount = v.length; 253 } 254 }, 255 256 get helpContents() { 257 if (this.help_crsr < 0) { 258 return []; 259 } 260 let contents = []; 261 262 let l = this.help_display_count; 263 if (l > this.help_count) { 264 l = this.help_count; 265 } 266 267 268 let c = (this.help_crsr - l + 1); 269 if (c < 0) { 270 c = l + c; 271 } 272 273 new_threshold = l - this.help_lastcount; 274 for (let i = 0; i < l; i++) { 275 if (this.help_cap > 0) { 276 c %= this.help_cap; 277 } 278 is_old = i < new_threshold; 279 v = [this.help_contents[c], is_old ? 'old' : 'new']; 280 contents.push(v); //this.help_contents[c]); 281 c += 1; 282 } 283 return contents; 284 }, 285 }" 286 x-init="tryHelpFor('welcome');" 287 x-for="(v) in helpContents" 288 @help.window="addHelpContents($event.detail.v);"> 289 <p x-html="v[0]" :class="v[1] == 'old' ? 'help-text old' : 'help-text new'" /> 290 291 </template> 292 </div> 293 294 <div class="contact-form"> 295 296 297 298 <form autocomplete="off"> 299 <h3 class="title">Get in touch!</h3> 300 <div class="details"> 301 302 <div > 303 <label for="status">Status:</label> 304 <span x-text="message_status" x-on:messagestatechange.window="defaultname = !g_local_key_identified;"></span> 305 </div> 306 307 <div> 308 <label for="your-identity">Your identity:</label> 309 <p><a x-text="keyDisplay" title="Click to download your private key" x-bind:href="localKeyArmor" x-bind:download="localKeyFilename"></a></p> 310 </div> 311 312 313 314 <div> 315 <label for="their-identity">Their identity:</label> 316 <p><a x-text="rkeyDisplay" title="Click to download the recipient's public key" x-bind:href="remoteKeyArmor" x-bind:download="remoteKeyFilename"></a></p> 317 </div> 318 319 320 <div> 321 <label for="message-number">Message number:</label> 322 <span x-text="message_count"></span> 323 </div> 324 325 <div> 326 <label for="receipt">Your receipt:</label> 327 <p><a x-bind:href="g_data_endpoint + '/' + rcpt" x-text="rcpt"></a></p> 328 </div> 329 </div> 330 <div class="input-container textarea"> 331 <label for="">Message:</label> 332 333 <textarea 334 name="message" 335 placeholder="message" 336 class="input" 337 x-model="content" 338 @focus="tryHelpFor('writemsg');" 339 @messagestatechange.window="if (checkState(STATE.ACK_MESSAGE)) {console.log('foo'); content = '';}" 340 > 341 </textarea> 342 </div> 343 <div class="input-container"> 344 <input class="btn" type="file" id="fileAdder" 345 @change="fileChange();" 346 /> 347 <ol> 348 <template x-data="{ 349 350 };" 351 x-for="(v) in fileList" 352 @messagestatechange.window="if (checkState(STATE.FILE_ADDED)) {addFileToList($event.detail.s[0], $event.detail.s[1]);}" 353 > 354 <li x-text="v"></li> 355 </template> 356 </ol> 357 </div> 358 359 <div x-data='{ 360 realname: "", 361 realemail: "", 362 }'> 363 <div x-show='defaultname && !g_local_key_identified' 364 x-data='{ 365 identify: false, 366 }' 367 @rst.window='identify = false;' 368 > 369 <select id="identity_select" @change='identify = $event.target.value > 0; if (identify) { tryHelpFor("identify") };' 370 @rst.window='document.getElementById("identity_select").value=0;'> 371 <option value=0 defaultselected>Stay anonymous</option> 372 <option value=1>Identify yourself</option> 373 </select> 374 <div class="input-container identify" x-show="identify"> 375 376 <input name="id_name" class="input" type="text" placeholder="Name" x-model="realname" /> 377 <input name="id_email" class="input" type="email" placeholder="Email" x-model="realemail" /> 378 379 380 </div> 381 </div> 382 <div x-data="{ 383 ready: false, 384 }" 385 x-on:messagestatechange.window='ready = checkState(STATE["RTS"]);' 386 > 387 <button 388 class="btn" 389 x-bind:disabled='!ready' 390 @click="tryDispatch(content, realname, realemail, filez);"> 391 sign, encrypt and send 392 </button> 393 </div> 394 </div> 395 396 </form> 397 <div id="reset" x-data="{ rst: false }"> 398 <button x-show="haveKey && !rst" @click="rst = true;">Discard key</button> 399 <button x-show="rst" @click="if (confirm('Are you sure you want to discard the key?')) { rst = false; $dispatch('rst'); purgeLocalKey(); }">Discard key</button> 400 </div> 401 </div> 402 </div> 403 </div> 404 405 406 <div id="dev" x-show='isDev'> 407 <h2>Devmode details</h2> 408 <dl> 409 <dt>last event</dt> 410 <dd x-text='message_status' /> 411 <dt>state value</dt> 412 <dd x-text='last_state' /> 413 <dt>state description</dt> 414 <dd x-text='lastState' /> 415 </dl> 416 </div> 417 </body> 418 </html>