Redid lots of things about viewing an invidual user's feed, their profile, and following users.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3740 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2022-01-08 20:54:02 +00:00
parent 50d860183d
commit 700d09c730
6 changed files with 115 additions and 92 deletions

View File

@ -1 +1 @@
{"type":"tildefriends-app","files":{"app.js":"&KdIfXHcm7xE6iBlJWoW8DixHbIPs+YPevJL+rF8eW4s=.sha256","index.html":"&ayK1muTik0h4UYNvT0QNm0WrygVrL8kxBoEyrQkHyHs=.sha256","vue-material.js":"&K5cdLqXYCENPak/TCINHQhyJhpS4G9DlZHGwoh/LF2g=.sha256","tf-user.js":"&fXhPpneuYuseoaYk25ODN3QecFAeOfuMOZd98OgvjsQ=.sha256","tf-message.js":"&oQggZN26PtRi4Ce9HY8TKVJ9jRrYWLRE5tN+3yHwEJE=.sha256","tf.js":"&EgrrFfINhqFL/Kj8qkJwH/DHTLrZ8CxhHDBxZjcyjUY=.sha256","commonmark.min.js":"&5x6ek3tFrKTZX6hXNNyFsjmhvrjmWpUkwuuaiyVV1Us=.sha256","vue.js":"&g1wvA+yHl1sVC+eufTsg9If7ZeVyMTBU+h0tks7ZNzE=.sha256","vue-material-theme-default-dark.css":"&RP2nr+2CR18BpHHw5ST9a5GJUCOG9n0G2kuGkcQioWE=.sha256","vue-material.min.css":"&kGbUM2QgFSyHZRzqQb0b+0S3EVIlZ0AXpdiAVjIhou8=.sha256","roboto.css":"&jJv43Om673mQO5JK0jj7714s5E+5Yrf82H6LcDx7wUs=.sha256","material-icons.css":"&a28PdcVvgq/DxyIvJAx/e+ZOEtOuHnr3kjLWKyzH11M=.sha256"}}
{"type":"tildefriends-app","files":{"app.js":"&SJrK87ZgHkxZQDB1nm/Xej/bzajzWpiY9a384zqZVzE=.sha256","index.html":"&O8+Vr4YHP8NIORZzM3Ee9tyLXr2xCETT2Eva0rO5ebA=.sha256","vue-material.js":"&K5cdLqXYCENPak/TCINHQhyJhpS4G9DlZHGwoh/LF2g=.sha256","tf-user.js":"&DdJwZYEo7AqFyutYMvEjykoVXxdHVog0UXye6Sbo0TU=.sha256","tf-message.js":"&ox9wj2+P3BhqokfHPI3ZMpSYNpNhl91K5R6Z4GbWxCA=.sha256","tf.js":"&rUCI1QKOoyWcFegV72DfG/P7HykmkX4DcvWV87hCeBM=.sha256","commonmark.min.js":"&5x6ek3tFrKTZX6hXNNyFsjmhvrjmWpUkwuuaiyVV1Us=.sha256","vue.js":"&g1wvA+yHl1sVC+eufTsg9If7ZeVyMTBU+h0tks7ZNzE=.sha256","vue-material-theme-default-dark.css":"&RP2nr+2CR18BpHHw5ST9a5GJUCOG9n0G2kuGkcQioWE=.sha256","vue-material.min.css":"&kGbUM2QgFSyHZRzqQb0b+0S3EVIlZ0AXpdiAVjIhou8=.sha256","roboto.css":"&jJv43Om673mQO5JK0jj7714s5E+5Yrf82H6LcDx7wUs=.sha256","material-icons.css":"&a28PdcVvgq/DxyIvJAx/e+ZOEtOuHnr3kjLWKyzH11M=.sha256"}}

View File

@ -3,6 +3,7 @@
const k_posts_max = 20;
const k_votes_max = 20;
var g_ready = false;
var g_selected = null;
var g_following_cache = {};
@ -240,6 +241,7 @@ async function getPosts(db, ids) {
}
async function ready() {
g_ready = true;
return refresh(g_selected);
}
@ -267,6 +269,7 @@ async function refresh(selected) {
}
await Promise.all([
app.postMessage({whoami: whoami}),
app.postMessage({hash: selected && selected.length == 1 ? selected[0] : null}),
ssb.getBroadcasts().then(broadcasts => app.postMessage({broadcasts: broadcasts})),
ssb.connections().then(connections => app.postMessage({connections: connections})),
core.apps().then(apps => app.postMessage({apps: apps})),
@ -293,6 +296,7 @@ async function refresh(selected) {
];
}))),
]);
await app.postMessage({ready: true});
}
ssb.addEventListener('message', async function(id) {
@ -308,7 +312,6 @@ ssb.addEventListener('message', async function(id) {
});
core.register('message', async function(m) {
print(JSON.stringify(m));
if (m.message == 'ready') {
await ready();
} else if (m.message) {
@ -321,9 +324,12 @@ core.register('message', async function(m) {
}
} else if (m.event == 'hashChange') {
if (m.hash.length > 1) {
refresh([m.hash.substring(1)]);
g_selected = [m.hash.substring(1)];
} else {
refresh();
g_selected = null;
}
if (g_ready) {
await refresh(g_selected);
}
} else if (m.event == 'focus' || m.event == 'blur') {
/* Shh. */

View File

@ -7,10 +7,10 @@
<link rel="stylesheet" href="vue-material-theme-default-dark.css">
<script src="vue.js"></script>
<script src="vue-material.js"></script>
<script src="commonmark.min.js"></script>
<script src="tf-user.js"></script>
<script src="tf-message.js"></script>
<script src="tf.js"></script>
<script src="commonmark.min.js"></script>
</head>
<body style="color: #fff">
<div id="app">
@ -38,8 +38,6 @@
<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-subheader>Users</md-subheader>
<!-- <md-list-item v-for="user in Object.keys(users).sort((x, y) => (users[x].name || x).localeCompare(users[y].name || y))" v-bind:key="'user-' + user"><tf-user v-bind:id="user"/></md-list-item> -->
</md-list>
</md-app-drawer>
<md-app-toolbar class="md-secondary" v-show="unread > 0">
@ -74,6 +72,58 @@
<md-button class="md-raised md-primary" v-on:click="post_message()">Submit Post</md-button>
</md-card-actions>
</md-card>
<md-button v-if="selected" class="md-raised md-primary" style="margin: 1em" @click="set_hash(null)">
<md-icon>home</md-icon> Home
</md-button>
<md-card v-if="selected && selected.charAt(0) == '@'" class="md-raised md-elevation-8" style="margin: 1em">
<md-card-header>
<md-card-header-text>
<div class="md-title">{{users[selected] &amp;&amp; users[selected].name ? users[selected].name : selected}}</div>
<div class="md-subhead" v-if="users[selected] && users[selected].name">{{selected}}</div>
</md-card-header-text>
<md-card-media v-if="users[selected] && users[selected].image" class="md-medium">
<div><img :src="'/' + (typeof(users[selected].image) == 'string' ? users[selected].image : users[selected].image.link) + '/view'"></div>
</md-card-media>
</md-card-header>
<md-card-content>
<div v-if="selected == whoami">
<md-field>
<label>Name</label>
<md-input v-model="edit_profile_name"></md-input>
</md-field>
<md-field>
<label>Description</label>
<md-textarea v-model="edit_profile_description"></md-textarea>
</md-field>
</div>
<template v-if="users[selected]">
<div v-if="users[selected].name">{{selected}}</div>
<div v-html="markdown(users[selected].description)"></div>
</template>
<md-card-actions>
<md-menu md-size="small" v-if="users[selected] && users[selected].followers">
<md-button md-menu-trigger>{{Object.keys(users[selected].followers).length}} followers</md-button>
<md-menu-content>
<md-menu-item v-for="id of Object.keys(users[selected].followers)"><tf-user :id="id"></tf-user></md-menu-item>
</md-menu-content>
</md-menu>
<md-menu md-size="small" v-if="users[selected] && users[selected].following">
<md-button md-menu-trigger>{{Object.keys(users[selected].following).length}} following</md-button>
<md-menu-content>
<md-menu-item v-for="id of Object.keys(users[selected].following)"><tf-user :id="id"></tf-user></md-menu-item>
</md-menu-content>
</md-menu>
<template v-if="selected != whoami && users[whoami]">
<md-button @click="follow(selected)" v-if="!users[whoami].following[selected]" class="md-raised md-secondary">Follow</md-button>
<md-button @click="unfollow(selected)" v-else class="md-raised md-secondary">Unfollow</md-button>
</template>
<md-button @click="save_profile" v-if="selected == whoami" class="md-primary md-raised">Save Profile</md-button>
</md-card-actions>
</md-card-content>
</md-card>
<template v-if="messages.length">
<tf-message
v-for="message in messages"

View File

@ -27,7 +27,10 @@ Vue.component('tf-message', {
<h3>
<tf-user :id="message.author"></tf-user>
</h3>
<div style="font-size: x-small">{{new Date(message.timestamp)}}</div>
<div style="font-size: x-small">
{{new Date(message.timestamp)}}
<md-tooltip>{{message.id}}</md-tooltip>
</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>

View File

@ -1,11 +1,5 @@
"use strict";
Vue.component('tf-user', {
data: function() { return {
show_user_dialog: false,
show_follow_dialog: false,
edit_profile_name: null,
edit_profile_description: null,
} },
props: ['id'],
computed: {
following: {
@ -15,91 +9,17 @@ Vue.component('tf-user', {
g_data.users[g_data.whoami].following &&
g_data.users[g_data.whoami].following[this.id];
},
set: function(newValue) {
var already_following =
g_data.users &&
g_data.users[g_data.whoami] &&
g_data.users[g_data.whoami].following &&
g_data.users[g_data.whoami].following[this.id];
if (newValue && !already_following) {
if (confirm("Are you sure you want to follow " + this.id + "?")) {
window.parent.postMessage({appendMessage: {type: "contact", following: true, contact: this.id}}, '*');
}
} else if (!newValue && already_following) {
if (confirm("Are you sure you want to unfollow " + this.id + "?")) {
window.parent.postMessage({appendMessage: {type: "contact", following: false, contact: this.id}}, '*');
}
}
},
},
whoami: { get: function() { return g_data.whoami; } },
users: { get: function() { return g_data.users; } },
},
methods: {
save_profile: function() {
var message = {appendMessage: {
type: 'about',
about: this.id,
name: this.edit_profile_name,
description: this.edit_profile_description,
}};
window.parent.postMessage(message, '*');
},
show_user: function() {
window.parent.postMessage({action: 'setHash', hash: this.id}, '*');
if (this.id == this.whoami) {
this.edit_profile_name = this.users[this.id].name;
this.edit_profile_description = this.users[this.id].description;
}
},
},
template: `<span>
<md-chip md-clickable :class="following ? 'md-accent' : ''" @click="show_user()">
template: `<md-chip md-clickable :class="following ? 'md-accent' : ''" @click="show_user()">
{{users[id] && users[id].name ? users[id].name : id}}
</md-chip>
<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>
<div v-if="id == whoami">
<md-field>
<label>Name</label>
<md-input v-model="edit_profile_name"></md-input>
</md-field>
<md-field>
<label>Description</label>
<md-textarea v-model="edit_profile_description"></md-textarea>
</md-field>
</div>
<template 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>
<div class="md-layout">
<div class="md-layout-item">
<md-list>
<md-subheader>Followers</md-subheader>
<md-list-item v-for="follower in Object.keys((users[id] && users[id].followers) ? users[id].followers : {})" v-bind:key="'follower-' + follower">
<tf-user :id="follower"></tf-user>
</md-list-item>
</md-list>
</div>
<div class="md-layout-item">
<md-list>
<md-subheader>Following</md-subheader>
<md-list-item v-for="user in Object.keys((users[id] && users[id].following) ? users[id].following : {})" v-bind:key="'following-' + user">
<tf-user :id="user"></tf-user>
</md-list-item>
</md-list>
</div>
</div>
</template>
</md-dialog-content>
<md-dialog-actions>
<md-button @click="save_profile" v-if="id == whoami">Save Profile</md-button>
<md-button @click="show_user_dialog = false">Close</md-button>
</md-dialog-actions>
</md-dialog>
</span>`,
<md-tooltip v-if="users[id] && users[id].name">{{id}}</md-tooltip>
</md-chip>`,
});

View File

@ -16,12 +16,20 @@ var g_data = {
mentions: {},
unread: 0,
loading: true,
selected: null,
edit_profile_name: null,
edit_profile_description: null,
};
var g_data_initial = JSON.parse(JSON.stringify(g_data));
var g_message_queue = [];
var g_process_pending = false;
function updateEditUser() {
g_data.edit_profile_name = g_data.users[g_data.whoami] ? g_data.users[g_data.whoami].name : null;
g_data.edit_profile_description = g_data.users[g_data.whoami] ? g_data.users[g_data.whoami].description : null;
}
function processMessages() {
for (let event of g_message_queue) {
var key = Object.keys(event.data)[0];
@ -56,6 +64,9 @@ function processMessages() {
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));
if (event.data.user.user == g_data.whoami) {
updateEditUser();
}
} else if (key == 'followers') {
if (!g_data.users[event.data.followers.id]) {
Vue.set(g_data.users, event.data.followers.id, {});
@ -104,7 +115,10 @@ function processMessages() {
} else if (key == 'unread') {
g_data.unread += event.data.unread;
} else if (key == 'hash') {
console.log(event.data);
g_data.selected = event.data.hash;
if (g_data.selected == g_data.whoami) {
updateEditUser();
}
} else {
g_data[key] = event.data[key];
}
@ -155,6 +169,14 @@ window.addEventListener('load', function() {
return undefined;
}
},
markdown: function(md) {
if (!md) {
return;
}
var reader = new commonmark.Parser({safe: true});
var writer = new commonmark.HtmlRenderer();
return writer.render(reader.parse(md));
},
refresh: function() {
window.parent.postMessage({refresh: true}, '*');
},
@ -168,6 +190,28 @@ window.addEventListener('load', function() {
remove_from_mentions: function(link) {
Vue.delete(g_data.mentions, link);
},
save_profile: function() {
var message = {appendMessage: {
type: 'about',
about: g_data.selected,
name: g_data.edit_profile_name,
description: g_data.edit_profile_description,
}};
window.parent.postMessage(message, '*');
},
follow: function(id) {
if (confirm('Are you sure you want to follow ' + id + '?')) {
window.parent.postMessage({appendMessage: {type: "contact", following: true, contact: id}}, '*');
}
},
unfollow: function(id) {
if (confirm('Are you sure you want to unfollow ' + id + '?')) {
window.parent.postMessage({appendMessage: {type: "contact", following: false, contact: id}}, '*');
}
},
set_hash(hash) {
window.parent.postMessage({action: 'setHash', hash: hash ? hash : '#'}, '*');
},
}
});
window.parent.postMessage('ready', '*');