2021-01-02 13:10:00 -05:00
< html >
< head >
< meta content = "width=device-width,initial-scale=1,minimal-ui" name = "viewport" >
< link rel = "stylesheet" href = "//fonts.googleapis.com/css?family=Roboto:400,500,700,400italic|Material+Icons" >
< link href = "https://fonts.googleapis.com/icon?family=Material+Icons" rel = "stylesheet" >
< link rel = "stylesheet" href = "https://unpkg.com/vue-material/dist/vue-material.min.css" >
< link rel = "stylesheet" href = "https://unpkg.com/vue-material/dist/theme/default-dark.css" >
< script src = "https://cdn.jsdelivr.net/npm/vue/dist/vue.js" > < / script >
< script src = "vue-material.js" > < / script >
< script src = "https://cdnjs.cloudflare.com/ajax/libs/commonmark/0.29.1/commonmark.min.js" > < / script >
< script >
var g_data = {
whoami: null,
connections: [],
messages: [],
users: {},
broadcasts: [],
showUsers: false,
show_connect_dialog: false,
show_user_dialog: null,
connect: null,
pubs: [],
votes: [],
2021-01-09 17:19:52 -05:00
apps: {},
share_app: null,
2021-01-02 13:10:00 -05:00
};
2021-01-02 13:35:12 -05:00
var g_data_initial = JSON.parse(JSON.stringify(g_data));
2021-01-02 13:10:00 -05:00
window.addEventListener('message', function(event) {
var key = Object.keys(event.data)[0];
if (key + 's' in g_data & & Array.isArray(g_data[key + 's'])) {
g_data[key + 's'].push(event.data[key]);
} else if (key == 'user') {
Vue.set(g_data.users, event.data.user.user, Object.assign({}, g_data.users[event.data.user.user] || {}, event.data.user.about));
} else if (key == 'followers') {
if (!g_data.users[event.data.followers.id]) {
Vue.set(g_data.users, event.data.followers.id, {});
}
Vue.set(g_data.users[event.data.followers.id], 'followers', event.data.followers.users);
} else if (key == 'following') {
if (!g_data.users[event.data.following.id]) {
Vue.set(g_data.users, event.data.following.id, {});
}
Vue.set(g_data.users[event.data.following.id], 'following', event.data.following.users);
} else if (key == 'broadcasts') {
g_data.broadcasts = event.data.broadcasts;
} else if (key == 'pubs') {
g_data.pubs = event.data.pubs;
2021-01-09 17:19:52 -05:00
} else if (key == 'apps') {
g_data.apps = event.data.apps;
2021-01-02 13:35:12 -05:00
} else if (key == 'clear') {
Object.keys(g_data_initial).forEach(function(key) {
Vue.set(g_data, key, JSON.parse(JSON.stringify(g_data_initial[key])));
});
2021-01-02 13:10:00 -05:00
} else {
g_data[key] = event.data[key];
}
});
window.addEventListener('load', function() {
Vue.use(VueMaterial.default);
Vue.component('tf-user', {
data: function() { return {users: g_data.users, show_user_dialog: false, show_follow_dialog: false} },
props: ['id'],
mounted: function() {
window.parent.postMessage({user: this.id}, '*');
},
computed: {
following: {
get: function() {
return g_data.users[g_data.whoami] & &
g_data.users[g_data.whoami].following & &
g_data.users[g_data.whoami].following.indexOf(this.id) != -1;
},
set: function(newValue) {
if (g_data.users[g_data.whoami] & &
g_data.users[g_data.whoami].following) {
if (newValue & & g_data.users[g_data.whoami].following.indexOf(this.id) == -1) {
window.parent.postMessage({appendMessage: {type: "contact", following: true, contact: this.id}}, '*');
} else if (!newValue) {
window.parent.postMessage({appendMessage: {type: "contact", following: false, contact: this.id}}, '*');
}
}
},
},
},
template: `< span @ click = "show_user_dialog = true" >
{{users[id] & & users[id].name ? users[id].name : id}}
2021-01-11 21:23:57 -05:00
< md-tooltip v-if = "users[id] && users[id].name" > {{id}}< / md-tooltip >
< md-dialog :md-active . sync = "show_user_dialog" >
< md-dialog-title > {{users[id] & & users[id].name ? users[id].name : id}}< / md-dialog-title >
< md-dialog-content v-if = "users[id]" >
< div v-if = "users[id].image" > < img :src = "'/' + users[id].image + '/view'" > < / div >
< div v-if = "users[id].name" > {{id}}< / div >
< div > {{users[id].description}}< / div >
< div > < md-switch v-model = "following" > Following< / md-switch > < / div >
< md-list >
< md-subheader > Followers< / md-subheader >
< md-list-item v-for = "follower in (users[id] || []).followers" v-bind:key = "'follower-' + follower" >
< tf-user :id = "follower" > < / tf-user >
< / md-list-item >
< md-subheader > Following< / md-subheader >
< md-list-item v-for = "user in (users[id] || []).following" v-bind:key = "'following-' + user" >
< tf-user :id = "user" > < / tf-user >
< / md-list-item >
2021-01-02 13:10:00 -05:00
< / md-list >
2021-01-11 21:23:57 -05:00
< / md-dialog-content >
< md-dialog-actions >
< md-button @ click = "show_user_dialog = false" > Close< / md-button >
< / md-dialog-actions >
< / md-dialog >
2021-01-02 13:10:00 -05:00
< / span > `,
});
Vue.component('tf-message', {
props: ['message', 'messages'],
data: function() { return { showRaw: false } },
computed: {
content_json: function() {
try {
return JSON.parse(this.message.content);
} catch {
return undefined;
}
},
sub_messages: function() {
var id = this.message.id;
return this.messages.filter(function (x) {
try {
return JSON.parse(x.content).root == id;
} catch {}
});
},
votes: function() {
return [];
var id = this.message.id;
return this.votes.filter(function (x) {
try {
var j = JSON.parse(x.content);
return j.type == 'vote' & & j.vote.link == id;
} catch {}
}).reduce(function (accum, value) {
var expression = JSON.parse(value.content).vote.expression;
if (!accum[expression]) {
accum[expression] = [];
}
accum[expression].push(value);
return accum;
}, {});
}
},
methods: {
markdown: function(md) {
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
return writer.render(reader.parse(md));
},
json: function(message) {
try {
return JSON.parse(message.content);
} catch {
return undefined;
}
},
},
template: `< md-app class = "md-elevation-8" style = "margin: 1em" v-if = "!content_json || ['pub', 'vote'].indexOf(content_json.type) == -1" >
< md-app-toolbar >
2021-01-11 21:23:57 -05:00
< h3 >
< tf-user :id = "message.author" > < / tf-user >
< / h3 >
< div style = "font-size: x-small" > {{new Date(message.timestamp)}}< / div >
< div class = "md-toolbar-section-end" >
< md-menu >
< md-button md-menu-trigger class = "md-icon-button" > < md-icon > more_vert< / md-icon > < / md-button >
< md-menu-content >
< md-menu-item v-if = "!showRaw" v-on:click = "showRaw = true" > View Raw< / md-menu-item >
< md-menu-item v-else v-on:click = "showRaw = false" > View Message< / md-menu-item >
2021-01-02 13:10:00 -05:00
< / md-menu-content >
2021-01-11 21:23:57 -05:00
< / md-menu >
< / div >
< / md-app-toolbar >
2021-01-02 13:10:00 -05:00
< md-app-content >
2021-01-11 21:23:57 -05:00
< div v-if = "showRaw" > {{message.content}}< / div >
< div v-else >
< div v-if = "content_json && content_json.type == 'post'" >
< div v-html = "this.markdown(content_json.text)" > < / div >
< img v-for = "mention in content_json.mentions" v-if = "mention.link && typeof(mention.link) == 'string' && mention.link.startsWith('&')" :src = "'/' + mention.link + '/view'" > < / img >
< / div >
< div v-else-if = "content_json && content_json.type == 'tildefriends-app'" >
< div v-html = "this.markdown(content_json.text)" > < / div >
< md-button target = "_top" :href = "'/' + message.id + '/'" > {{content_json.name || 'tildefriends-app'}}< / md-button >
< / div >
< div v-else-if = "content_json && content_json.type == 'contact'" > < tf-user :id = "message.author" > < / tf-user > {{content_json.following ? '==> ' : '=/=> '}} < tf-user :id = "content_json.contact" > < / tf-user > < / div >
< div v-else > {{message.content}}< / div >
< / div >
< tf-message v-for = "sub_message in sub_messages" v-bind:message = "sub_message" v-bind:messages = "messages" v-bind:key = "sub_message.id" > < / tf-message >
< md-chip v-for = "vote in Object.keys(votes)" v-bind:key = "vote" >
{{vote + (votes[vote].length > 1 ? ' (' + votes[vote].length + ')' : '')}}
< / md-chip >
< / md-app-content >
< / md-app > `,
2021-01-02 13:10:00 -05:00
});
function markdown(d) { return d; }
Vue.config.performance = true;
var vue = new Vue({
el: '#app',
data: g_data,
methods: {
post_message: function() {
2021-01-09 17:19:52 -05:00
if (g_data.share_app) {
window.parent.postMessage({share_app: {
app: g_data.apps[g_data.share_app],
2021-01-11 21:23:57 -05:00
name: g_data.share_app,
2021-01-09 17:19:52 -05:00
text: document.getElementById('post_text').value,
}}, '*');
} else {
window.parent.postMessage({post: document.getElementById('post_text').value}, '*');
}
document.getElementById('post_text').value = '';
2021-01-02 13:10:00 -05:00
},
ssb_connect: function(connection) {
window.parent.postMessage({connect: connection}, '*');
},
content_json: function(message) {
try {
return JSON.parse(message.content);
} catch {
return undefined;
}
},
refresh: function() {
window.parent.postMessage({refresh: true}, '*');
},
}
});
});
window.parent.postMessage('ready', '*');
< / script >
< / head >
< body style = "color: #fff" >
< div id = "app" >
< md-dialog :md-active . sync = "show_connect_dialog" >
< md-dialog-title > Connect< / md-dialog-title >
< md-dialog-content >
< md-field >
< label > net:127.0.0.1:8008~shs:id< / label >
< md-input v-model = "connect" > < / md-input >
< / md-field >
< / md-dialog-content >
< md-dialog-actions >
< md-button class = "md-primary" @ click = "ssb_connect(connect); connect = null; show_connect_dialog = false" > Connect< / md-button >
< md-button @ click = "connect = null; show_connect_dialog = false" > Cancel< / md-button >
< / md-dialog-actions >
< / md-dialog >
< md-app style = "position: absolute; height: 100%; width: 100%" >
< md-app-toolbar class = "md-primary" >
< md-button class = "md-icon-button" @ click = "showUsers = !showUsers" >
< md-icon > menu< / md-icon >
< / md-button >
< span class = "md-title" > Tilde Friends Secure Scuttlebutt Test< / span >
< / md-app-toolbar >
< md-app-drawer :md-active . sync = "showUsers" md-persistent = "full" >
< md-list >
< md-subheader > Network< / md-subheader >
< md-list-item v-for = "broadcast in broadcasts" v-bind:key = "JSON.stringify(broadcast)" @ click = "ssb_connect(broadcast)" > {{broadcast.address}}:{{broadcast.port}} < tf-user :id = "broadcast.pubkey" > < / tf-user > < / md-list-item >
< md-subheader > Connections< / md-subheader >
< md-list-item v-for = "connection in connections" v-bind:key = "'connection-' + JSON.stringify(connection)" > < tf-user :id = "connection" > < / tf-user > < / md-list-item >
< md-list-item @ click = "show_connect_dialog = true" > Connect< / md-list-item >
< / md-list >
< / md-app-drawer >
< md-app-content >
< md-button @ click = "refresh()" class = "md-icon-button md-dense md-raised md-primary" >
< md-icon > cached< / md-icon >
< / md-button >
Welcome, < tf-user :id = "whoami" > < / tf-user > .
< md-card class = "md-elevation-8" >
< md-card-header >
< div class = "md-title" > What's up?< / div >
< / md-card-header >
< md-card-content >
2021-01-09 17:19:52 -05:00
< md-chip v-if = "share_app" md-deletable @ md-delete = "share_app = null" >
{{share_app}}: {{apps[share_app]}}
< / md-chip >
2021-01-02 13:10:00 -05:00
< md-field >
< label > Post a message< / label >
< md-textarea id = "post_text" > < / md-textarea >
< / md-field >
< / md-card-content >
< md-card-actions >
2021-01-09 17:19:52 -05:00
< md-menu >
< md-button md-menu-trigger > Share App< / md-button >
< md-menu-content >
2021-01-13 21:45:52 -05:00
< md-menu-item v-for = "app in Object.keys(apps)" v-bind:key = "app" @ click = "share_app = app" >
2021-01-09 17:19:52 -05:00
{{app}}
< / md-menu-item >
< / md-menu-content >
< / md-menu >
2021-01-02 13:10:00 -05:00
< md-button class = "md-raised md-primary" v-on:click = "post_message()" > Submit Post< / md-button >
< / md-card-actions >
< / md-card >
2021-01-11 21:23:57 -05:00
< tf-message v-for = "message in messages" v-if = "!content_json(message).root || !messages.some(m => m.id == content_json(message).root)" v-bind:message = "message" v-bind:messages = "messages" v-bind:key = "message.id" > < / tf-message >
2021-01-02 13:10:00 -05:00
< / md-app-content >
< / md-app >
< / div >
< / body >
< / html >