1var f_deployed_contract = './build/contracts/Landmark.json'; 2 3var APP_FLAGS = { 4 "hideNavbar" : false, 5 "showDates" : true, 6 "showPostID" : true, 7 "showPostNumber" : true, 8 9 "metamask_enabled" : true, 10 "renderEmoji": true, 11}; 12 13const default_contract_address = { 14 15 // Use 0 as the local testnet fallback 16 0: "0x90a9b125b6e4b22ecb139819778dc01d1339ef5c", 17 18 // Mainnet 19 1: "0xD38e005a28fae8D8c4238444BC08E7Da83902310", 20 21 // Test contract for LV memorial 22 //1: "0x1B11aC23fbB37B1F943c2b36a566fc77f64BB8a9", 23 24 // Ropsten 25 3: "0xA334472B88830Dac9BD4d800e4366e9Ce584631a", 26} 27 28var default_network_id = 1; 29 30var used_default_address = false; 31var contract_address = null; 32var contract_deploy = null; 33var contract_network = null; 34const updateInterval = 1500; 35 36// Please don't steal this key! Get your own at https://infura.io/ 37var provider_URLS = { 38 39 // Updated v3 API keys, keyed to thoppe.github.io* 40 // See account on infura.io 41 1: 'https://mainnet.infura.io/v3/520f1fa015604187bcef2f7ba922a49e', 42 3: 'https://ropsten.infura.io/v3/520f1fa015604187bcef2f7ba922a49e', 43 44 // Older API keys 45 //1: 'https://mainnet.infura.io/rk62RV4O5kpptwlDiV69', 46 //3: 'https://ropsten.infura.io/rk62RV4O5kpptwlDiV69', 47}; 48 49 50function changeURIAddress(address, reload=false) { 51 var loc = new URI($(location).attr('href')); 52 loc.removeSearch("address").addSearch("address", address); 53 window.history.pushState(null, null, loc); 54 55 if(reload) 56 location.reload(); 57} 58 59function getURI(key) { 60 return URI($(location).attr('href')).search(true)[key]; 61} 62 63 64const closeButtonHTML = `<button type="button" class="close" data-dismiss="alert" aria-label="Close"> 65<span aria-hidden="true">×</span>`; 66const infoButtonHTML = `<div> 67Post successful: "<span class="infoPostMessage"></span>" <br> 68Transaction Hash: <a class="alert-link infoTransactionHash">HASH</a> <br> 69Block Number: <a class="alert-link infoBlockNumber">BLOCK</a> <br> 70Gas Used: <span class="infoGasUsed">GAS</span> <br> 71` 72 73function statusError(x, statusType="danger", clickToRemove=true) { 74 // Alert types: 75 // primary secondary success danger warning info light dark 76 77 let div = $('<div>').addClass("alert alert-"+statusType).text(x); 78 if (clickToRemove) 79 div.addClass("alert-dismissible").attr("data-dismiss","alert"); 80 81 div.append($(closeButtonHTML)); 82 83 $('#errorbox').prepend(div); 84 return div 85} 86 87function getESUrl() { 88 89 if(contract_network == 3) 90 subdomain = "ropsten." 91 else 92 subdomain = "" 93 94 return "https://" + subdomain + "etherscan.io/"; 95} 96 97function setVersionNumber(n) { 98 $('#versiontag').text(" (v"+n+") "); 99} 100 101function setPostCount(n) { 102 $('#postCount').text(n); 103} 104 105function setAccountHash(accounts) { 106 107 $('#accountHash').text(accounts[0]) 108 .attr('href', getESUrl() + "address/"+accounts[0]); 109 110} 111 112function setContractHash(address) { 113 $('#contractHash').text(address); 114 115 $('#adminContractHash').text(address) 116 .attr('href', getESUrl() + "address/"+address); 117} 118 119function setAccountBalance(result) { 120 let eth = web3.fromWei(result.toNumber()); 121 $('#accountBalance').text(eth + " ether"); 122} 123 124function getMessageTD(n) { 125 return $('[data-nonce='+n+']') 126} 127 128const messageTemplateHTML = ` 129<tr class="LandmarkPostRow"> 130 <td> 131<div class="row row-no-padding"> 132<div class="col-xs-3"> 133 <span class="messageAvatar"></span> 134</div> 135 <div class="col-xs-9"> 136 <div class="messageText"></div> 137 <div class="messageMeta text-muted small"> 138 <span class="messagePermalink"></span> 139 <span class="messageNumber"></span> 140 <span class="messageDate font-italic"></span> 141 <div class="messageAddress"></div> 142 </div> 143<div> 144</div> 145 146</td> 147</tr>` 148 149function createPostBuffer(n) { 150 let post = $(messageTemplateHTML); 151 let label = 'LandmarkPost'+n; 152 153 post.attr("data-nonce", n); 154 155 if(APP_FLAGS["showPostNumber"]) 156 post.find('.messageNumber').text("#"+(n+1)+""); 157 158 // Hide the post when we create it 159 post.addClass("hidden"); 160 161 $("#marks").find('tbody').prepend(post); 162} 163 164function setMessageContents(result, n) { 165 let post = getMessageTD(n); 166 167 post.find('.messageText').text(result); 168 169 if(APP_FLAGS["renderEmoji"]) { 170 $(function(){ 171 post.find('.messageText').Emoji({ 172 path:'bower_components/jqueryemoji/img/apple40/'}); 173 }); 174 }; 175 176 // Reveal the post when we have some data 177 post.removeClass("hidden"); 178} 179 180function setMessageAddress(result, n) { 181 let post = getMessageTD(n); 182 if(post.length == 0) 183 return false;
184 185 if(!APP_FLAGS["showPostID"]) { 186 post.find(".messageMeta").remove(); 187 return false; 188 } 189 190 let url = getESUrl() + 'address/' + result; 191 192 var div = $('<span class=""><a> \ 193 <i class="fa fa-user" aria-hidden="true"></i></a></span>'); 194 195 div.find('a') 196 .attr("title",result) 197 .attr("href",url) 198 .append(" "+result) 199 .addClass("no-underline") 200 .addClass("ellipsis"); 201 202 img = $("<img></img>"); 203 img.attr("src", "https://robohash.org/"+result+"?size=80x80"); 204 img.addClass("img-responsive").addClass("avatar").addClass("center-block"); 205 post.find('.messageAvatar') 206 .append(img); 207 208 209 post.find(".messageAddress").append(div); 210 211} 212 213function setMessageDate(result, n) { 214 if(!APP_FLAGS["showDates"]) 215 return false; 216 217 let timestamp = result.toNumber(); 218 let dateVal = new Date(timestamp*1000).toDateString(); 219 let timeVal = new Date(timestamp*1000).toLocaleTimeString(); 220 let datetime = dateVal + ', ' + timeVal; 221 222 let post = getMessageTD(n); 223 if(post.length == 0) 224 return false; 225 226 post.find(".messageDate") 227 .attr("title",datetime) 228 .text(datetime); 229 230 // Set the permalink icon and link 231 let icon = $('<a><i class="fa fa-link" aria-hidden="true"></i></a></span>'); 232 let loc = new URI($(location).attr('href')); 233 loc.removeSearch("postNumber").addSearch("postNumber", n+1); 234 235 icon.attr("href", loc); 236 post.find(".messagePermalink").append(icon); 237 238 // Check if the current post is the permalink if so, highlight 239 let URIpostNumber = getURI("postNumber"); 240 if(URIpostNumber != null && URIpostNumber == n+1) { 241 post.addClass("highlightedPost") 242 } 243 244} 245 246var isAddress = function (address) { 247 // function isAddress(address) { 248 if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { 249 // check if it has the basic requirements of an address 250 return false; 251 } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || 252 /^(0x)?[0-9A-F]{40}$/.test(address)) { 253 // If it's all small caps or all all caps, return "true 254 return true; 255 } else { 256 // Otherwise check each case 257 //return isChecksumAddress(address); 258 return true; 259 } 260} 261 262 263 264App = { 265 web3Provider: null, 266 contracts: {}, 267 updater: null, 268 269 // Save the loaded messages into this dictionary 270 messages: {}, 271 272 init: function() { 273 App.loadURIflags(); 274 return App.initWeb3(); 275 }, 276 277 initWeb3: function() { 278 279 // Initialize web3 and set the provider to the testRPC. 280 if (typeof web3 !== 'undefined') { 281 console.log("Using injected url"); 282 App.web3Provider = web3.currentProvider; 283 } else { 284 console.log("Using provider url"); 285 window.web3 = new Web3(); 286 287 if(default_network_id in provider_URLS) { 288 var url = provider_URLS[default_network_id]; 289 } 290 else { 291 console.log("Network ID not found", default_network_id); 292 } 293 294 App.web3Provider = new web3.providers.HttpProvider(url); 295 296 // Disable post buttons 297 $("#navbar-postLink").attr("data-target", "#cantPostModal"); 298 299 $("#curatorPostMessageBtn").addClass("disabled") 300 APP_FLAGS["metamask_enabled"] = false; 301 } 302 303 web3 = new Web3(App.web3Provider); 304 return App.initContract(); 305 }, 306 307 initContract: function() { 308 309 web3.eth.getAccounts(function(error, accounts) { 310 if (error) { statusError(error); } 311 }); 312 313 // Load the contract data from file 314 $.getJSON(f_deployed_contract, function(data) { 315 App.contracts.Landmark = TruffleContract(data); 316 App.contracts.Landmark.setProvider(App.web3Provider); 317 return App.bindEvents(); 318 }); 319 320 }, 321 322 323 LandmarkCall: async function(funcname, ...args) { 324 await App.getContractDeploy(); 325 326 try { 327 return (await contract_deploy[funcname].call(...args)); 328 } 329 catch (err) { 330 statusError(err.message); 331 } 332 }, 333 334 getContractDeploy: async function() { 335 336 if(!used_default_address) { 337 338 contract_network = await web3.version.network; 339 340 // Check if URI requests an address but Metamask reports something diff 341 if(default_network_id != null & default_network_id!=contract_network) { 342 343 statusError("Network mismatch, Metamask reports "+contract_network+ 344 "but URI requests "+default_network_id+ 345 ". Check Metamask settings."); 346 } 347 if(default_network_id != null) { 348 contract_network = default_network_id; 349 } 350 351 352 // Try to load the url param address 353 let url_address = getURI("address"); 354 if(isAddress(url_address)) { 355 contract_address = url_address 356 } 357 358 // If not, set from the default networks 359 else { 360 361 // Load the default address if we know it 362 if(contract_network in default_contract_address) { 363 contract_address = default_contract_address[contract_network]; 364 } 365 // Otherwise, assume we are on testnet 366 else { 367 contract_address = default_contract_address[0]; 368 } 369 } 370 371 used_default_address = true; 372 changeURIAddress(contract_address); 373 setContractHash(contract_address); 374 } 375 376 377 contract_deploy = App.contracts.Landmark.at(contract_address); 378 }, 379 380 bindEvents: function() { 381 382 // By default, disable curator buttons until we pass a check 383 $('#curatorPostMessageBtn').prop('disabled', true); 384 $('#curatorCloseMessageBtn').prop('disabled', true); 385 386 $(document).on('click', '.btn-process-post', 387 App.processButtonPost);
388 $(document).on('click', '.btn-process-closesite', 389 App.processButtonCloseSite); 390 $(document).on('click', '.btn-process-curatormsg', 391 App.processButtonCuratorMsg); 392 $(document).on('click', '.btn-process-change-address', 393 App.processButtonChangeAddress); 394 395 396 return App.setInfo(); 397 }, 398 399 400 checkIfContractDeployed: async function () { 401 402 await App.getContractDeploy(); 403 404 var x = await contract_deploy["getVersionNumber"].call(); 405 if(parseInt(x) == 0) { 406 $('#statusNotFound').show(); 407 $('#statusLooking').hide(); 408 $("#statusEmpty").hide(); 409 410 return false; 411 } 412 413 return true; 414 415 }, 416 417 loadURIflags: function () { 418 // Load the URI flags and act on them 419 420 let renderEmoji = getURI("renderEmoji"); 421 if(renderEmoji != null) { 422 console.log(renderEmoji); 423 if(Boolean(renderEmoji) & renderEmoji != "false" & renderEmoji != "0") { 424 APP_FLAGS["renderEmoji"] = true; 425 } 426 else { 427 APP_FLAGS["renderEmoji"] = false; 428 } 429 } 430 431 let networkID = getURI("networkID"); 432 if(networkID != null) { 433 default_network_id = parseInt(networkID); 434 } 435 436 let noMeta = getURI("noMeta"); 437 if(noMeta != null) { 438 if(Boolean(noMeta) & noMeta != "false" & noMeta != "0") { 439 APP_FLAGS["hideNavbar"] = true; 440 APP_FLAGS["showDates"] = false; 441 APP_FLAGS["showPostID"] = false; 442 APP_FLAGS["showPostNumber"] = false; 443 } 444 } 445 446 if(APP_FLAGS["hideNavbar"]) 447 $('#navbar-isSiteUsable').hide(); 448 }, 449 450 setInfo: async function () { 451 452 await App.getContractDeploy(); 453 454 $("#modalAddressText").val(contract_address); 455 456 $('#AddressChangeModal').on('shown.bs.modal', function () { 457 $('#modalAddressText').focus().select(); 458 }); 459 460 $("#accountNetwork").text(web3.version.network); 461 462 $('#PostModal').on('shown.bs.modal', function () { 463 $('#marktext').focus(); 464 }); 465 466 $('#AdminModal').on('shown.bs.modal', function (e) { 467 App.processAdminInfo(); 468 }); 469 470 // Break if address is not found 471 if(! await App.checkIfContractDeployed() ) { 472 return false; 473 } 474 475 const vn = parseInt(await App.LandmarkCall("getVersionNumber")); 476 setVersionNumber(vn); 477 478 $('#marktext').keydown(function (event) { 479 if ((event.keyCode == 10 || event.keyCode == 13) && event.ctrlKey){ 480 App.processButtonPost(); 481 } 482 }); 483 484 App.loadCuratorMessage(); 485 App.loadAllPosts(); 486 487 //updater = setInterval(App.loadAllPosts, updateInterval); 488 489 }, 490 491 loadCuratorMessage: async function() { 492 493 // If a permalink is requested, modify the message 494 let URIpostNumber = getURI("postNumber"); 495 496 if(URIpostNumber != null) { 497 let loc = new URI($(location).attr('href')); 498 loc.removeSearch("postNumber"); 499 500 let msg = ("Permalink to post #"+URIpostNumber 501 +", <a>return to the active chain.</a>"); 502 $("#curatorMessageText").html(msg) 503 .find("a").attr("href", loc); 504 $("#curatorMessage").find(".blockquote-footer").hide(); 505 $("#curatorMessage").show(); 506 } 507 else { 508 const cMsg = await App.LandmarkCall("getCuratorMessage"); 509 if(cMsg) { 510 $("#curatorMessageText").text(cMsg); 511 $("#curatorMessage").show(); 512 } 513 } 514 515 }, 516 517 allocatePostArea: function(i) { 518 519 // Create an item while we wait so we don't double load 520 App.messages[i] = { 521 "msg" : "Loading", 522 "adr" : "0x", 523 "date": "", 524 } 525 526 createPostBuffer(i); 527 }, 528 529 530 loadPost: async function(i) { 531 532 // If a permalink is requested, only load that one 533 let URIpostNumber = getURI("postNumber"); 534 if(URIpostNumber != null && URIpostNumber != i+1) { 535 return false; 536 } 537 538 App.messages[i] = { 539 "msg" : await App.LandmarkCall("getMessageContents", i), 540 "adr" : await App.LandmarkCall("getMessageAddress", i), 541 "date": await App.LandmarkCall("getMessageTimestamp", i), 542 } 543 544 setMessageContents(App.messages[i].msg, i); 545 setMessageAddress(App.messages[i].adr, i); 546 setMessageDate(App.messages[i].date, i); 547 548 }, 549 550 loadAllPosts: async function () { 551 552 if(! await App.checkIfContractDeployed() ) { 553 return false; 554 } 555 556 var n = parseInt(await App.LandmarkCall("getMessageCount")); 557 setPostCount(n); 558 559 try { 560 if(n==0) { 561 $('#statusNotFound').hide(); 562 $('#statusLooking').hide(); 563 $("#statusEmpty").show(); 564 return true; 565 } 566 } 567 catch (err) { 568 // Perhaps we aren't ready yet... 569 return false;
570 } 571 572 $('#statusNotFound').hide(); 573 $('#statusLooking').hide(); 574 $("#statusEmpty").hide(); 575 576 // Allocate space for posts 577 for (i = 0; i < n; i++) { 578 if(i in App.messages) 579 continue; 580 581 await createPostBuffer(i); 582 App.loadPost(i); 583 } 584 585 586 587 }, 588 589 loadAccountInfo: function() { 590 web3.eth.getAccounts(async function(error, accounts) { 591 setAccountHash(accounts); 592 593 // Check if user is the curator and if so, allow curator options 594 const Uadr = accounts[0]; 595 const Cadr = await App.LandmarkCall("getCuratorAddress"); 596 597 if(Uadr == Cadr) { 598 $('#curatorPostMessageBtn').prop('disabled', false); 599 $('#curatorCloseMessageBtn').prop('disabled', false); 600 } 601 602 web3.eth.getBalance(Uadr, function(error, val) { 603 setAccountBalance(val); 604 }); 605 }); 606 }, 607 608 processButtonCloseSite: function() { 609 box = statusError("WARNING: Attemping to close the site forever!"); 610 box.addClass("post-attempt-box"); 611 612 // Hide the modal since we are showing status 613 $("#AdminModal").modal("hide"); 614 615 web3.eth.getAccounts(function(error, accounts) { 616 contract_deploy.then(function(cx) { 617 return cx.closeLandmarkSite(); 618 }).then(async function(result) { 619 box = statusError("Site closed forever.", "info") 620 .addClass("post-attempt-box"); 621 622 }).catch(function(err) { 623 statusError(err.message); 624 }); 625 626 }) 627 628 }, 629 630 processButtonPost: async function() { 631 const text = $('#marktext').val(); 632 if(!text) return false; 633 634 // Check to see if this is a curator post 635 const is_curator_post = $("#curatorNote").is(':visible'); 636 637 // Hide the modal at this point 638 $("#PostModal").modal("hide"); 639 640 // Show the status of the post attempt 641 msg = "Attemping to post '"+text+"'" 642 643 if (is_curator_post) 644 msg += " to the curator message"; 645 646 console.log(msg) 647 box = statusError(msg, "warning") 648 .addClass("post-attempt-box"); 649 650 if(!is_curator_post) { 651 if(!(await App.LandmarkCall("getIsSiteOpen"))) { 652 let msg = "Landmark site closed. No further posting allowed." 653 statusError(msg, "info"); 654 return false; 655 } 656 } 657 658 web3.eth.getAccounts(function(error, accounts) { 659 660 contract_deploy.then(function(cx) { 661 662 if(is_curator_post) 663 return cx.postCuratorMessage(text); 664 else 665 return cx.postMessage(text); 666 667 }).then(async function(result) { 668 console.log("Post complete!", result); 669 box = statusError("", "success", false); 670 $('.post-attempt-box').remove(); 671 672 let info=$(infoButtonHTML); 673 674 res = result.receipt; 675 676 info.find(".infoPostMessage").text(text); 677 info.find(".infoTransactionHash") 678 .text(res.transactionHash) 679 .attr('href', getESUrl()+"tx/"+res.transactionHash); 680 info.find(".infoBlockNumber") 681 .text(res.blockNumber) 682 .attr('href', getESUrl()+"block/"+res.blockNumber); 683 info.find(".infoGasUsed") 684 .text(res.gasUsed); 685 686 box.append(info); 687 688 // Clear the results 689 $('#marktext').val(""); 690 691 }).catch(function(err) { 692 statusError(err.message); 693 }); 694 695 }); 696 697 }, 698 699 processAdminInfo: async function() { 700 701 if(APP_FLAGS["metamask_enabled"]) 702 App.loadAccountInfo(); 703 704 // Break if address is not found 705 if(! await App.checkIfContractDeployed() ) { 706 return false; 707 } 708 709 const Cadr = await App.LandmarkCall("getCuratorAddress"); 710 const isOpen = await App.LandmarkCall("getIsSiteOpen"); 711 const n = parseInt(await App.LandmarkCall("getLimitPostLength")); 712 713 let Fadr = await App.LandmarkCall("getForwardingAddress"); 714 // An empty address is returned when nothing is set 715 if(Fadr == "0x0000000000000000000000000000000000000000") 716 Fadr = ""; 717 718 $("#siteinfoCuratorAddress").text(Cadr); 719 $("#siteinfoForwardingAddress").text(Fadr); 720 721 $("#siteinfoIsOpen").text(isOpen); 722 $("#siteinfoMaxPostLength").text(n); 723 724 }, 725 726 processButtonCuratorMsg: function() { 727 728 // Hide the modal since we are showing status 729 $("#AdminModal").modal("hide"); 730 731 // Modify the post modal 732 $("#curatorNote").show(); 733 $("#PostModal").modal('show'); 734 735 }, 736 737 processButtonChangeAddress: function() { 738 739 const address = $('#modalAddressText').val(); 740 if(!address) return false; 741 742 if(!isAddress(address)) { 743 msg = (address + " is not a valid ethereum address. " + 744 "A valid address must be 42 hexidecimal characters long "+ 745 "and begin with 0x"); 746 747 statusError(msg); 748 return false;
749 } 750 751 // Hide the modal at this point 752 $("#modalAddressText").modal("hide"); 753 754 contract_address = address; 755 contract_deploy = null; 756 757 // Set the address and reload 758 changeURIAddress(address, reload=true); 759 760 }, 761 762 763}; 764 765$(function() { 766 767 $(document).ready(function() { 768 App.init() 769 }); 770 771});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.