PageSourceSearch

https://thoppe.github.io/Landmark/app.js

js thoppe.github.io collected 2026-10-03 10:04:54 UTC 18,756 bytes, 771 lines download raw bytes

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">&times;</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.