feature: watches on the backend, need frontend implementation for
notifications and unread indicators
This commit is contained in:
parent
7046bb0389
commit
6293374bb7
28 changed files with 1405 additions and 608 deletions
275
public/js/app.js
Normal file
275
public/js/app.js
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
const HASH_EXTRACTOR = /^\#\/topic\/(?<topic_id>[A-Za-z\-]+)\/?(?<view>\w+)?/gm;
|
||||
const UPDATE_TOPICS_FREQUENCY = 60_000;
|
||||
|
||||
const APP = {
|
||||
user_servers: [],
|
||||
user_watches: [],
|
||||
|
||||
_event_callbacks: {},
|
||||
|
||||
on: function( event_name, callback ) {
|
||||
this._event_callbacks[ event_name ] = this._event_callbacks[ event_name ] ?? new Set();
|
||||
this._event_callbacks[event_name ].add( callback );
|
||||
return true;
|
||||
},
|
||||
|
||||
off: function( event_name, callback ) {
|
||||
return this._event_callbacks[ event_name ]?.delete( callback );
|
||||
},
|
||||
|
||||
_emit: function( event_name, event_data ) {
|
||||
const event_callbacks = this._event_callbacks[ event_name ];
|
||||
event_callbacks?.forEach( ( callback ) => {
|
||||
callback( event_data );
|
||||
});
|
||||
},
|
||||
|
||||
check_if_logged_in: async function () {
|
||||
try {
|
||||
const session_response = await api.fetch("/api/users/me");
|
||||
|
||||
if (!session_response.ok) {
|
||||
const error_body = await session_response.json();
|
||||
const error = error_body?.error;
|
||||
|
||||
console.dir({
|
||||
error_body,
|
||||
error,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await session_response.json();
|
||||
this.login( user );
|
||||
} catch (error) {
|
||||
console.dir({
|
||||
error,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
extract_url_hash_info: async function () {
|
||||
HASH_EXTRACTOR.lastIndex = 0; // ugh, need this to have this work on multiple exec calls
|
||||
const {
|
||||
groups: { topic_id, view },
|
||||
} = HASH_EXTRACTOR.exec(window.location.hash ?? "") ?? {
|
||||
groups: {},
|
||||
};
|
||||
|
||||
console.dir({
|
||||
url: window.location.href,
|
||||
hash: window.location.hash,
|
||||
topic_id,
|
||||
view,
|
||||
});
|
||||
|
||||
if (!document.body.dataset.topic || document.body.dataset.topic !== topic_id) {
|
||||
const previous = document.body.dataset.topic;
|
||||
|
||||
console.dir({
|
||||
topic_changed: {
|
||||
detail: {
|
||||
previous,
|
||||
topic_id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
document.body.dataset.topic = topic_id;
|
||||
|
||||
this._emit( 'topic_changed', {
|
||||
previous,
|
||||
topic_id
|
||||
});
|
||||
|
||||
if (!topic_id) {
|
||||
const first_topic_id = this.TOPICS.TOPIC_LIST[0]?.id;
|
||||
if (first_topic_id) {
|
||||
window.location.hash = `/topic/${first_topic_id}/chat`; // TODO: allow a different default than chat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!document.body.dataset.view || document.body.dataset.view !== view) {
|
||||
const previous = document.body.dataset.view;
|
||||
document.body.dataset.view = view;
|
||||
|
||||
console.dir({
|
||||
view_changed: {
|
||||
detail: {
|
||||
previous,
|
||||
view,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this._emit( 'view_changed', {
|
||||
previous,
|
||||
view
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
load: async function() {
|
||||
this.server = {};
|
||||
this.suggested_servers = [];
|
||||
try {
|
||||
const server_info_response = await api.fetch( '/files/settings/settings.json' );
|
||||
if ( !server_info_response.ok ) {
|
||||
throw new Error( 'Could not get server info.' );
|
||||
}
|
||||
|
||||
const this_server_info = await server_info_response.json();
|
||||
|
||||
this.server = {
|
||||
name: this_server_info?.name ?? document.title,
|
||||
url: this_server_info?.url ?? window.location.origin ?? window.location.href,
|
||||
icon: this_server_info?.icon ?? '/icons/favicon-128x128.png',
|
||||
icon_background: this_server_info?.icon_background ?? undefined
|
||||
};
|
||||
|
||||
const suggested_servers = await (await api.fetch( '/files/settings/suggested_servers.json' )).json();
|
||||
|
||||
}
|
||||
catch( error ) {
|
||||
console.error( error );
|
||||
}
|
||||
|
||||
window.addEventListener("locationchange", this.extract_url_hash_info.bind( this ));
|
||||
window.addEventListener("locationchange", this.TOPICS.update );
|
||||
|
||||
this.check_if_logged_in();
|
||||
this.extract_url_hash_info();
|
||||
this._emit( 'load', this );
|
||||
},
|
||||
|
||||
update_user: async function( updated_user ) {
|
||||
const user = this.user = updated_user;
|
||||
document.body.dataset.user = JSON.stringify(user);
|
||||
document.body.dataset.perms = user.permissions.join(":");
|
||||
|
||||
this.TOPICS.update();
|
||||
|
||||
this.user_servers = [];
|
||||
try {
|
||||
const user_server_response = await api.fetch( `/files/users/${ user.id }/settings/servers.json` );
|
||||
this.user_servers = user_server_response.ok ? await user_server_response.json() : [];
|
||||
}
|
||||
catch( error ) {
|
||||
console.error( error );
|
||||
}
|
||||
|
||||
this.user_watches = [];
|
||||
try {
|
||||
const user_watches_response = await api.fetch( `/api/users/${ user.id }/watches` );
|
||||
this.user_watches = user_watches_response.ok ? await user_watches_response.json() : [];
|
||||
}
|
||||
catch( error ) {
|
||||
console.error( error );
|
||||
}
|
||||
|
||||
// TODO: show unread indicators based on watches
|
||||
},
|
||||
|
||||
login: async function( user ) {
|
||||
await this.update_user( user );
|
||||
this._emit( 'user_logged_in', { user } );
|
||||
},
|
||||
|
||||
logout: function() {
|
||||
delete document.body.dataset.user;
|
||||
delete document.body.dataset.perms;
|
||||
window.location = "/";
|
||||
|
||||
this._emit( "user_logged_out", {});
|
||||
},
|
||||
|
||||
USERS: {
|
||||
_evict_timeouts: {},
|
||||
_update_timeouts: {},
|
||||
get: async (id, force) => {
|
||||
if (force || !APP.USERS[id]) {
|
||||
APP.USERS[id] = await (await api.fetch(`/api/users/${id}`)).json();
|
||||
}
|
||||
|
||||
if (!APP.USERS._update_timeouts[id]) {
|
||||
APP.USERS._update_timeouts[id] = setInterval(() => {
|
||||
APP.USERS.get(id, true);
|
||||
}, 1 * 60_000);
|
||||
}
|
||||
|
||||
if (!force) {
|
||||
if (APP.USERS._evict_timeouts[id]) {
|
||||
clearTimeout(APP.USERS._evict_timeouts[id]);
|
||||
}
|
||||
|
||||
APP.USERS._evict_timeouts[id] = setTimeout(() => {
|
||||
if (APP.USERS._update_timeouts[id]) {
|
||||
clearTimeout(APP.USERS._update_timeouts[id]);
|
||||
delete APP.USERS._update_timeouts[id];
|
||||
}
|
||||
|
||||
delete APP.USERS[id];
|
||||
}, 10 * 60_000);
|
||||
}
|
||||
|
||||
return APP.USERS[id];
|
||||
},
|
||||
},
|
||||
|
||||
TOPICS: {
|
||||
_last_topic_update: undefined,
|
||||
_update_topics_timeout: undefined,
|
||||
TOPIC_LIST: [],
|
||||
|
||||
update: async () => {
|
||||
const now = new Date();
|
||||
const time_since_last_update = now - (APP.TOPICS._last_topic_update ?? 0);
|
||||
if (time_since_last_update < UPDATE_TOPICS_FREQUENCY / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (APP.TOPICS._update_topics_timeout) {
|
||||
clearTimeout(APP.TOPICS._update_topics_timeout);
|
||||
APP.TOPICS._update_topics_timeout = undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const topics_response = await api.fetch("/api/topics");
|
||||
if (topics_response.ok) {
|
||||
const new_topics = await topics_response.json();
|
||||
const has_differences =
|
||||
APP.TOPICS.TOPIC_LIST.length !== new_topics.length ||
|
||||
new_topics.some((topic, index) => {
|
||||
return (
|
||||
APP.TOPICS.TOPIC_LIST[index]?.id !== topic.id ||
|
||||
APP.TOPICS.TOPIC_LIST[index]?.name !== topic.name
|
||||
);
|
||||
});
|
||||
|
||||
if (has_differences) {
|
||||
APP.TOPICS.TOPIC_LIST = [...new_topics];
|
||||
|
||||
APP._emit( 'topics_updated', {
|
||||
topics: APP.TOPICS.TOPIC_LIST
|
||||
});
|
||||
}
|
||||
|
||||
APP.TOPICS._last_topic_update = now;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
APP.TOPICS._update_topics_timeout = setTimeout(
|
||||
APP.TOPICS.update,
|
||||
UPDATE_TOPICS_FREQUENCY,
|
||||
);
|
||||
|
||||
// now that we have topics, make sure our url is all good
|
||||
APP.extract_url_hash_info();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", APP.load.bind( APP ));
|
||||
Loading…
Add table
Add a link
Reference in a new issue