diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 595b4cc1..00000000 --- a/.prettierignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules -src -deps -.clang-format - -# Minified files -**/*.min.css -**/*.min.js -**/leaflet.* -**/commonmark* -**/w3.css -apps/ssb/tribute.esm.js -apps/api/app.js -**/emojis.json diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 1f7ef42f..00000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,10 +0,0 @@ -trailingComma: 'es5' -useTabs: true -semi: true -singleQuote: true -bracketSpacing: false -# overrides: -# - files: '**/*.json' -# options: -# useTabs: false -# tabWidth: 2 diff --git a/README.md b/README.md index c6073dc5..b7c474a2 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,43 @@ # Tilde Friends - Tilde Friends is a tool for making and sharing. A public instance lives at https://www.tildefriends.net/. -It is both a peer-to-peer social network client, participating in Secure Scuttlebutt, as well as a platform for writing and running web applications. +It is both a peer-to-peer social network client, participating in Secure +Scuttlebutt, as well as a platform for writing and running web applications. ## Goals - 1. Make it easy and fun to run all sorts of web applications. 2. Provide security that is easy to understand and protects your data. -3. Make creating and sharing web applications accessible to anyone with a browser. +3. Make creating and sharing web applications accessible to anyone with a + browser. ## Building +Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for +all of those host platforms plus mingw64, iOS, and android. -Builds on Linux (x86_64 and aarch64), MacOS, OpenBSD, and Haiku. Builds for all of those host platforms plus mingw64, iOS, and android. - -1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies are kept up to date in the tree. -2. To build, run `make debug` or `make release`. An executable will be generated in a subdirectory of `out/`. -3. It's possible to build for Android, iOS, and Windows on Linux, if you have the right dependencies in the right places. `make windebug winrelease iosdebug-ipa iosrelease-ipa release-apk`. +1. Requires openssl (`libssl-dev`, in debian-speak). All other dependencies + are kept up to date in the tree. +2. To build, run `make debug` or `make release`. An executable will be + generated in a subdirectory of `out/`. +3. It's possible to build for Android, iOS, and Windows on Linux, if you have + the right dependencies in the right places. `make windebug winrelease + iosdebug-ipa iosrelease-ipa release-apk`. 4. To build in docker, `docker build .`. 5. `make format` will normalize formatting to the coding standard. ## Running +By default, running the built `tildefriends` executable will start a web server +at . `tildefriends -h` lists further options. -By default, running the built `tildefriends` executable will start a web server at . `tildefriends -h` lists further options. - -The first user to create an account and log in will be granted administrative privileges. Further administration can be done at . +The first user to create an account and log in will be granted administrative +privileges. Further administration can be done at +. ## Documentation - -Docs are a work in progress: . +Docs are a work in progress: +. ## License - -All code unless otherwise noted in is provided under the [MIT](https://opensource.org/licenses/MIT) license. +All code unless otherwise noted in is provided under the +[MIT](https://opensource.org/licenses/MIT) license. diff --git a/apps/admin.json b/apps/admin.json index a9a2e723..711779a2 100644 --- a/apps/admin.json +++ b/apps/admin.json @@ -1,4 +1,4 @@ { - "type": "tildefriends-app", - "emoji": "🎛" -} + "type": "tildefriends-app", + "emoji": "🎛" +} \ No newline at end of file diff --git a/apps/admin/app.js b/apps/admin/app.js index 5916f11f..0a1c35e8 100644 --- a/apps/admin/app.js +++ b/apps/admin/app.js @@ -18,13 +18,9 @@ async function main() { for (let user of await core.users()) { data.users[user] = await core.permissionsForUser(user); } - await app.setDocument( - utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data)) - ); + await app.setDocument(utf8Decode(getFile('index.html')).replace('$data', JSON.stringify(data))); } catch { - await app.setDocument( - 'Only an administrator can modify these settings.' - ); + await app.setDocument('Only an administrator can modify these settings.'); } } -main(); +main(); \ No newline at end of file diff --git a/apps/admin/index.html b/apps/admin/index.html index 114ab1b9..eabbf708 100644 --- a/apps/admin/index.html +++ b/apps/admin/index.html @@ -1,12 +1,10 @@ - + - +

Tilde Friends Administration

- + \ No newline at end of file diff --git a/apps/admin/script.js b/apps/admin/script.js index e9041412..6d43d291 100644 --- a/apps/admin/script.js +++ b/apps/admin/script.js @@ -3,32 +3,25 @@ import * as tfrpc from '/static/tfrpc.js'; function delete_user(user) { if (confirm(`Are you sure you want to delete the user "${user}"?`)) { - tfrpc.rpc - .delete_user(user) - .then(function () { - alert(`User "${user}" deleted successfully.`); - }) - .catch(function (error) { - alert( - `Failed to delete user "${user}": ${JSON.stringify(error, null, 2)}.` - ); - }); + tfrpc.rpc.delete_user(user).then(function() { + alert(`User "${user}" deleted successfully.`); + }).catch(function(error) { + alert(`Failed to delete user "${user}": ${JSON.stringify(error, null, 2)}.`); + }); } } function global_settings_set(key, value) { - tfrpc.rpc - .global_settings_set(key, value) - .then(function () { - alert(`Set "${key}" to "${value}".`); - }) - .catch(function (error) { - alert(`Failed to set "${key}": ${JSON.stringify(error, null, 2)}.`); - }); + tfrpc.rpc.global_settings_set(key, value).then(function() { + alert(`Set "${key}" to "${value}".`); + }).catch(function(error) { + alert(`Failed to set "${key}": ${JSON.stringify(error, null, 2)}.`); + }); } -window.addEventListener('load', function () { - const permission_template = (permission) => html` ${permission}`; +window.addEventListener('load', function() { + const permission_template = (permission) => + html` ${permission}`; function input_template(key, description) { if (description.type === 'boolean') { return html` @@ -69,24 +62,26 @@ window.addEventListener('load', function () { } const user_template = (user, permissions) => html`
  • - - ${user}: ${permissions.map((x) => permission_template(x))} + + ${user}: + ${permissions.map(x => permission_template(x))}
  • `; const users_template = (users) => html`

    Users

    -
      - ${Object.entries(users).map((u) => user_template(u[0], u[1]))} -
    `; +
      + ${Object.entries(users).map(u => user_template(u[0], u[1]))} +
    `; const page_template = (data) => html`

    Global Settings

    - ${Object.keys(data.settings) - .sort() - .map((x) => html`${input_template(x, data.settings[x])}`)} + ${Object.keys(data.settings).sort().map(x => html`${input_template(x, data.settings[x])}`)}
    ${users_template(data.users)} -
    `; + + `; render(page_template(g_data), document.body); -}); +}); \ No newline at end of file diff --git a/apps/api.json b/apps/api.json index 511730eb..2e226336 100644 --- a/apps/api.json +++ b/apps/api.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "📜", - "previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256" -} + "type": "tildefriends-app", + "emoji": "📜", + "previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256" +} \ No newline at end of file diff --git a/apps/api/docs.js b/apps/api/docs.js index 037bf240..9e045bbb 100644 --- a/apps/api/docs.js +++ b/apps/api/docs.js @@ -219,7 +219,7 @@ Parses an HTTP response. * *Object* An object with **bytes_parsed**, **minor_version**, **status**, **message**, and **headers** fields on successful parse. `; -docs['sha1Digest()'] = ` +docs['sha1Digest()'] =` Calculates a SHA1 digest. Completes synchronously. @@ -353,4 +353,4 @@ Call a remote function. * **...** Parameters to pass to the function. ### Returns The return value of the called function. -`; +`; \ No newline at end of file diff --git a/apps/apps.json b/apps/apps.json index 03545742..7b0370a0 100644 --- a/apps/apps.json +++ b/apps/apps.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "💻", - "previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256" -} + "type": "tildefriends-app", + "emoji": "💻", + "previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256" +} \ No newline at end of file diff --git a/apps/apps/app.js b/apps/apps/app.js index 2cdf9faa..d31f1ab7 100644 --- a/apps/apps/app.js +++ b/apps/apps/app.js @@ -26,15 +26,14 @@ async function fetch_info(apps) { async function fetch_shared_apps() { let messages = {}; - await ssb.sqlAsync( - ` + await ssb.sqlAsync(` SELECT messages.* FROM messages_fts('"application/tildefriends"') JOIN messages ON messages.rowid = messages_fts.rowid ORDER BY timestamp `, [], - function (row) { + function(row) { let content = JSON.parse(row.content); for (let mention of content.mentions) { if (mention?.type === 'application/tildefriends') { @@ -45,13 +44,10 @@ async function fetch_shared_apps() { }; } } - } - ); + }); let result = {}; - for (let app of Object.values(messages).sort( - (x, y) => y.message.timestamp - x.message.timestamp - )) { + for (let app of Object.values(messages).sort((x, y) => y.message.timestamp - x.message.timestamp)) { let app_object = JSON.parse(utf8Decode(await ssb.blobGet(app.blob))); if (app_object) { app_object.blob_id = app.blob; diff --git a/apps/blog.json b/apps/blog.json index d6a298e8..782fa7bf 100644 --- a/apps/blog.json +++ b/apps/blog.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "🪵", - "previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256" -} + "type": "tildefriends-app", + "emoji": "🪵", + "previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256" +} \ No newline at end of file diff --git a/apps/blog/app.js b/apps/blog/app.js index 66a800db..99c0165d 100644 --- a/apps/blog/app.js +++ b/apps/blog/app.js @@ -5,4 +5,4 @@ async function main() { await app.setDocument(blog.render_html(blogs)); } -main(); +main(); \ No newline at end of file diff --git a/apps/blog/blog.js b/apps/blog/blog.js index d2ba4e01..4a3e0866 100644 --- a/apps/blog/blog.js +++ b/apps/blog/blog.js @@ -1,19 +1,11 @@ import * as commonmark from './commonmark.min.js'; function escape(text) { - return (text ?? '') - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>'); + return (text ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); } function escapeAttribute(text) { - return (text ?? '') - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"') - .replaceAll("'", '''); + return (text ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); } export async function get_blog_message(id) { @@ -21,7 +13,7 @@ export async function get_blog_message(id) { await ssb.sqlAsync( 'SELECT author, timestamp, content FROM messages WHERE id = ?', [id], - function (row) { + function(row) { let content = JSON.parse(row.content); message = { author: row.author, @@ -29,8 +21,7 @@ export async function get_blog_message(id) { blog: content?.blog, title: content?.title, }; - } - ); + }); if (message) { await ssb.sqlAsync( ` @@ -43,10 +34,9 @@ export async function get_blog_message(id) { ORDER BY sequence DESC LIMIT 1 `, [message.author], - function (row) { + function(row) { message.name = row.name; - } - ); + }); } return message; } @@ -61,12 +51,8 @@ export function markdown(md) { node = event.node; if (event.entering) { if (node.destination?.startsWith('&')) { - node.destination = - '/' + node.destination + '/view?filename=' + node.firstChild?.literal; - } else if ( - node.destination?.startsWith('@') || - node.destination?.startsWith('%') - ) { + node.destination = '/' + node.destination + '/view?filename=' + node.firstChild?.literal; + } else if (node.destination?.startsWith('@') || node.destination?.startsWith('%')) { node.destination = '/~core/ssb/#' + escape(node.destination); } } @@ -121,7 +107,7 @@ export function render_html(blogs) {

    🪵Tilde Friends Blog

    - ${blogs.map((blog_post) => render_blog_post(blog_post)).join('\n')} + ${blogs.map(blog_post => render_blog_post(blog_post)).join('\n')} `; } @@ -149,15 +135,14 @@ export function render_atom(blogs) { ${core.url} ${new Date().toString()} - ${blogs.map((blog_post) => render_blog_post_atom(blog_post)).join('\n')} + ${blogs.map(blog_post => render_blog_post_atom(blog_post)).join('\n')} `; } export async function get_posts() { let blogs = []; let ids = await ssb.getIdentities(); - await ssb.sqlAsync( - ` + await ssb.sqlAsync(` WITH blogs AS ( SELECT @@ -197,11 +182,8 @@ export async function get_posts() { JOIN public ON public.author = blogs.author LEFT OUTER JOIN names ON names.author = blogs.author ORDER BY blogs.timestamp DESC LIMIT 20 - `, - [JSON.stringify(ids)], - function (row) { - blogs.push(row); - } - ); + `, [JSON.stringify(ids)], function(row) { + blogs.push(row); + }); return blogs; -} +} \ No newline at end of file diff --git a/apps/blog/handler.js b/apps/blog/handler.js index 32b54bef..ef14b62b 100644 --- a/apps/blog/handler.js +++ b/apps/blog/handler.js @@ -2,50 +2,30 @@ import * as blog from './blog.js'; async function main() { if (request.path.startsWith('%') && request.path.endsWith('.sha256')) { - let id = request.path.startsWith('%25') - ? '%' + request.path.substring(3) - : request.path; + let id = request.path.startsWith('%25') ? '%' + request.path.substring(3) : request.path; let message = await blog.get_blog_message(id); if (message) { - respond({ - data: await blog.render_blog_post_html(message), - content_type: 'text/html; charset=utf-8', - }); + respond({data: await blog.render_blog_post_html(message), content_type: 'text/html; charset=utf-8'}); } else { - respond({ - data: `Message ${id} not found.`, - content_type: 'text/html; charset=utf-8', - }); + respond({data: `Message ${id} not found.`, content_type: 'text/html; charset=utf-8'}); } } else if (request.path == 'atom') { let blogs = await blog.get_posts(); - respond({ - data: blog.render_atom(blogs), - content_type: 'application/atom+xml', - }); + respond({data: blog.render_atom(blogs), content_type: 'application/atom+xml'}); } else { let blogs = await blog.get_posts(); for (let blog_post of blogs) { let title = (blog_post.title || '').replaceAll(/\W/g, '_').toLowerCase(); if (request.path === title) { - respond({ - data: await blog.render_blog_post_html(blog_post), - content_type: 'text/html; charset=utf-8', - }); + respond({data: await blog.render_blog_post_html(blog_post), content_type: 'text/html; charset=utf-8'}); return; } } - respond({ - data: blog.render_html(blogs), - content_type: 'text/html; charset=utf-8', - }); + respond({data: blog.render_html(blogs), content_type: 'text/html; charset=utf-8'}); } } -main().catch(function (error) { - respond({ - data: ` -
    ${error.message}\n${error.stack}
    `, - content_type: 'text/html', - }); -}); +main().catch(function(error) { + respond({data: ` +
    ${error.message}\n${error.stack}
    `, content_type: 'text/html'}); +}); \ No newline at end of file diff --git a/apps/db/app.js b/apps/db/app.js index 1f65b890..6e6c2c41 100644 --- a/apps/db/app.js +++ b/apps/db/app.js @@ -51,7 +51,7 @@ async function key_list(db) { app.setDocument(doc); } -core.register('message', async function (message) { +core.register('message', async function(message) { if (message.event == 'hashChange') { let hash = message.hash.substring(1); if (hash.startsWith(':shared:')) { @@ -67,4 +67,4 @@ core.register('message', async function (message) { } }); -database_list(); +database_list(); \ No newline at end of file diff --git a/apps/follow.json b/apps/follow.json index 3cf770d7..5e137e35 100644 --- a/apps/follow.json +++ b/apps/follow.json @@ -1,4 +1,4 @@ { - "type": "tildefriends-app", - "emoji": "➡️" -} + "type": "tildefriends-app", + "emoji": "➡️" +} \ No newline at end of file diff --git a/apps/follow/app.js b/apps/follow/app.js index 098f8f22..676b7423 100644 --- a/apps/follow/app.js +++ b/apps/follow/app.js @@ -2,7 +2,7 @@ let g_about_cache = {}; async function query(sql, args) { let result = []; - await ssb.sqlAsync(sql, args, function (row) { + await ssb.sqlAsync(sql, args, function(row) { result.push(row); }); return result; @@ -21,8 +21,7 @@ async function contacts_internal(id, last_row_id, following, max_row_id) { json_extract(content, '$.type') = 'contact' ORDER BY sequence `, - [id, last_row_id, max_row_id] - ); + [id, last_row_id, max_row_id]); for (let row of contacts) { let contact = JSON.parse(row.content); if (contact.following === true) { @@ -43,34 +42,15 @@ async function contact(id, last_row_id, following, max_row_id) { return await contacts_internal(id, last_row_id, following, max_row_id); } -async function following_deep_internal( - ids, - depth, - blocking, - last_row_id, - following, - max_row_id -) { - let contacts = await Promise.all( - [...new Set(ids)].map((x) => contact(x, last_row_id, following, max_row_id)) - ); +async function following_deep_internal(ids, depth, blocking, last_row_id, following, max_row_id) { + let contacts = await Promise.all([...new Set(ids)].map(x => contact(x, last_row_id, following, max_row_id))); let result = {}; for (let i = 0; i < ids.length; i++) { let id = ids[i]; let contact = contacts[i]; let all_blocking = Object.assign({}, contact.blocking, blocking); - let found = Object.keys(contact.following).filter((y) => !all_blocking[y]); - let deeper = - depth > 1 - ? await following_deep_internal( - found, - depth - 1, - all_blocking, - last_row_id, - following, - max_row_id - ) - : []; + let found = Object.keys(contact.following).filter(y => !all_blocking[y]); + let deeper = depth > 1 ? await following_deep_internal(found, depth - 1, all_blocking, last_row_id, following, max_row_id) : []; result[id] = [id, ...found, ...deeper]; } return [...new Set(Object.values(result).flat())]; @@ -88,22 +68,10 @@ async function following_deep(ids, depth, blocking) { last_row_id: 0, }; } - let max_row_id = ( - await query( - ` + let max_row_id = (await query(` SELECT MAX(rowid) AS max_row_id FROM messages - `, - [] - ) - )[0].max_row_id; - let result = await following_deep_internal( - ids, - depth, - blocking, - cache.last_row_id, - cache.following, - max_row_id - ); + `, []))[0].max_row_id; + let result = await following_deep_internal(ids, depth, blocking, cache.last_row_id, cache.following, max_row_id); cache.last_row_id = max_row_id; let store = JSON.stringify(cache); await db.set('following', store); @@ -122,15 +90,13 @@ async function fetch_about(db, ids, users) { }; } let max_row_id = 0; - await ssb.sqlAsync( - ` + await ssb.sqlAsync(` SELECT MAX(rowid) AS max_row_id FROM messages `, [], - function (row) { + function(row) { max_row_id = row.max_row_id; - } - ); + }); for (let id of Object.keys(cache.about)) { if (ids.indexOf(id) == -1) { delete cache.about[id]; @@ -163,21 +129,17 @@ async function fetch_about(db, ids, users) { ORDER BY messages.author, messages.sequence `, [ - JSON.stringify(ids.filter((id) => cache.about[id])), - JSON.stringify(ids.filter((id) => !cache.about[id])), + JSON.stringify(ids.filter(id => cache.about[id])), + JSON.stringify(ids.filter(id => !cache.about[id])), cache.last_row_id, max_row_id, - ] - ); + ]); for (let about of abouts) { let content = JSON.parse(about.content); if (content.about === about.author) { delete content.type; delete content.about; - cache.about[about.author] = Object.assign( - cache.about[about.author] || {}, - content - ); + cache.about[about.author] = Object.assign(cache.about[about.author] || {}, content); } } cache.last_row_id = max_row_id; @@ -193,41 +155,41 @@ async function getAbout(db, id) { if (g_about_cache[id]) { return g_about_cache[id]; } - let o = await db.get(id + ':about'); + let o = await db.get(id + ":about"); const k_version = 4; let f = o ? JSON.parse(o) : o; if (!f || f.version != k_version) { f = {about: {}, sequence: 0, version: k_version}; } await ssb.sqlAsync( - 'SELECT ' + - ' sequence, ' + - ' content ' + - 'FROM messages ' + - 'WHERE ' + - ' author = ?1 AND ' + - ' sequence > ?2 AND ' + - " json_extract(content, '$.type') = 'about' AND " + - " json_extract(content, '$.about') = ?1 " + - 'UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?1 ' + - 'ORDER BY sequence', + "SELECT "+ + " sequence, "+ + " content "+ + "FROM messages "+ + "WHERE "+ + " author = ?1 AND "+ + " sequence > ?2 AND "+ + " json_extract(content, '$.type') = 'about' AND "+ + " json_extract(content, '$.about') = ?1 "+ + "UNION SELECT MAX(sequence) as sequence, NULL FROM messages WHERE author = ?1 "+ + "ORDER BY sequence", [id, f.sequence], - function (row) { + function(row) { f.sequence = row.sequence; if (row.content) { let about = {}; try { about = JSON.parse(row.content); - } catch {} + } catch { + } delete about.about; delete about.type; f.about = Object.assign(f.about, about); } - } - ); + }); let j = JSON.stringify(f); if (o != j) { - await db.set(id + ':about', j); + await db.set(id + ":about", j); } g_about_cache[id] = f.about; return f.about; @@ -236,15 +198,15 @@ async function getAbout(db, id) { async function getSize(db, id) { let size = 0; await ssb.sqlAsync( - 'SELECT (LENGTH(author) * COUNT(*) + SUM(LENGTH(content)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1', + "SELECT (LENGTH(author) * COUNT(*) + SUM(LENGTH(content)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1", [id], function (row) { size += row.size; - } - ); + }); return size; } + async function getSizes(ids) { let sizes = {}; await ssb.sqlAsync( @@ -259,8 +221,7 @@ async function getSizes(ids) { [JSON.stringify(ids)], function (row) { sizes[row.author] = row.size; - } - ); + }); return sizes; } @@ -280,10 +241,7 @@ function niceSize(bytes) { } function escape(value) { - return value - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>'); + return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); } async function main() { @@ -291,27 +249,19 @@ async function main() { let db = await database('ssb'); let whoami = await ssb.getIdentities(); let tree = ''; - await app.setDocument( - `
    Enumerating followed users...
    ` - ); + await app.setDocument(`
    Enumerating followed users...
    `); let following = await following_deep(whoami, 2, {}); - await app.setDocument( - `
    Getting names and sizes...
    ` - ); + await app.setDocument(`
    Getting names and sizes...
    `); let [about, sizes] = await Promise.all([ fetch_about(db, following, {}), getSizes(following), ]); await app.setDocument(`
    Finishing...
    `); - following.sort((a, b) => (sizes[b] ?? 0) - (sizes[a] ?? 0)); + following.sort((a, b) => ((sizes[b] ?? 0) - (sizes[a] ?? 0))); for (let id of following) { tree += `
  • ${escape(about[id]?.name ?? id)} ${niceSize(sizes[id] ?? 0)}
  • \n`; } - await app.setDocument( - '\n\n

    Following

    \n
      ' + - tree + - '
    \n\n' - ); + await app.setDocument('\n\n

    Following

    \n
      ' + tree + '
    \n\n'); } -main(); +main(); \ No newline at end of file diff --git a/apps/gg.json b/apps/gg.json index 16eba8e0..b5a6f698 100644 --- a/apps/gg.json +++ b/apps/gg.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "🗺", - "previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256" -} + "type": "tildefriends-app", + "emoji": "🗺", + "previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256" +} \ No newline at end of file diff --git a/apps/gg/app.js b/apps/gg/app.js index 2384e409..d8ae30db 100644 --- a/apps/gg/app.js +++ b/apps/gg/app.js @@ -46,7 +46,7 @@ tfrpc.register(async function query(sql, args) { return result; }); tfrpc.register(async function store_blob(blob) { - if (typeof blob == 'string') { + if (typeof(blob) == 'string') { blob = utf8Encode(blob); } if (Array.isArray(blob)) { @@ -71,15 +71,10 @@ async function main() { let shared_db = await shared_database('state'); attempt = await shared_db.get(core.user.credentials.session.name); } - app.setDocument( - utf8Decode(getFile('index.html')).replace( - '${data}', - JSON.stringify({ - attempt: attempt, - state: core.user?.credentials?.session?.name, - }) - ) - ); + app.setDocument(utf8Decode(getFile('index.html')).replace('${data}', JSON.stringify({ + attempt: attempt, + state: core.user?.credentials?.session?.name, + }))); } -main(); +main(); \ No newline at end of file diff --git a/apps/gg/gpx.js b/apps/gg/gpx.js index 56875d17..93421d88 100644 --- a/apps/gg/gpx.js +++ b/apps/gg/gpx.js @@ -17,7 +17,7 @@ function xml_parse(xml) { let tag = xml.substring(tag_begin, i).trim(); if (tag.startsWith('?') && tag.endsWith('?')) { /* Ignore directives. */ - } else if (tag.startsWith('/')) { + } else if (tag.startsWith('/')) { path.pop(); } else { let parts = tag.split(' '); @@ -63,10 +63,7 @@ export function gpx_parse(xml) { for (let trkseg of xml_each(trk, 'trkseg')) { let segment = []; for (let trkpt of xml_each(trkseg, 'trkpt')) { - segment.push({ - lat: parseFloat(trkpt.attributes.lat), - lon: parseFloat(trkpt.attributes.lon), - }); + segment.push({lat: parseFloat(trkpt.attributes.lat), lon: parseFloat(trkpt.attributes.lon)}); } result.segments.push(segment); } @@ -81,4 +78,4 @@ export function gpx_parse(xml) { } } return result; -} +} \ No newline at end of file diff --git a/apps/gg/handler.js b/apps/gg/handler.js index 4f210ce5..647e81cd 100644 --- a/apps/gg/handler.js +++ b/apps/gg/handler.js @@ -18,4 +18,4 @@ async function main() { status_code: 307, }); } -main(); +main(); \ No newline at end of file diff --git a/apps/gg/index.html b/apps/gg/index.html index 66a7f723..57d98809 100644 --- a/apps/gg/index.html +++ b/apps/gg/index.html @@ -1,26 +1,14 @@ - + - + - + - + \ No newline at end of file diff --git a/apps/gg/polyline.js b/apps/gg/polyline.js index 5cea31ce..9f1dff5f 100644 --- a/apps/gg/polyline.js +++ b/apps/gg/polyline.js @@ -10,24 +10,24 @@ var polyline = {}; function py2_round(value) { - // Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values - return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1); + // Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values + return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1); } function encode(current, previous, factor) { - current = py2_round(current * factor); - previous = py2_round(previous * factor); - var coordinate = (current - previous) * 2; - if (coordinate < 0) { - coordinate = -coordinate - 1; - } - var output = ''; - while (coordinate >= 0x20) { - output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63); - coordinate /= 32; - } - output += String.fromCharCode((coordinate | 0) + 63); - return output; + current = py2_round(current * factor); + previous = py2_round(previous * factor); + var coordinate = (current - previous) * 2; + if (coordinate < 0) { + coordinate = -coordinate - 1 + } + var output = ''; + while (coordinate >= 0x20) { + output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63); + coordinate /= 32; + } + output += String.fromCharCode((coordinate | 0) + 63); + return output; } /** @@ -41,53 +41,54 @@ function encode(current, previous, factor) { * * @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js */ -polyline.decode = function (str, precision) { - var index = 0, - lat = 0, - lng = 0, - coordinates = [], - shift = 0, - result = 0, - byte = null, - latitude_change, - longitude_change, - factor = Math.pow(10, Number.isInteger(precision) ? precision : 5); +polyline.decode = function(str, precision) { + var index = 0, + lat = 0, + lng = 0, + coordinates = [], + shift = 0, + result = 0, + byte = null, + latitude_change, + longitude_change, + factor = Math.pow(10, Number.isInteger(precision) ? precision : 5); - // Coordinates have variable length when encoded, so just keep - // track of whether we've hit the end of the string. In each - // loop iteration, a single coordinate is decoded. - while (index < str.length) { - // Reset shift, result, and byte - byte = null; - shift = 1; - result = 0; + // Coordinates have variable length when encoded, so just keep + // track of whether we've hit the end of the string. In each + // loop iteration, a single coordinate is decoded. + while (index < str.length) { - do { - byte = str.charCodeAt(index++) - 63; - result += (byte & 0x1f) * shift; - shift *= 32; - } while (byte >= 0x20); + // Reset shift, result, and byte + byte = null; + shift = 1; + result = 0; - latitude_change = result & 1 ? (-result - 1) / 2 : result / 2; + do { + byte = str.charCodeAt(index++) - 63; + result += (byte & 0x1f) * shift; + shift *= 32; + } while (byte >= 0x20); - shift = 1; - result = 0; + latitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2); - do { - byte = str.charCodeAt(index++) - 63; - result += (byte & 0x1f) * shift; - shift *= 32; - } while (byte >= 0x20); + shift = 1; + result = 0; - longitude_change = result & 1 ? (-result - 1) / 2 : result / 2; + do { + byte = str.charCodeAt(index++) - 63; + result += (byte & 0x1f) * shift; + shift *= 32; + } while (byte >= 0x20); - lat += latitude_change; - lng += longitude_change; + longitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2); - coordinates.push([lat / factor, lng / factor]); - } + lat += latitude_change; + lng += longitude_change; - return coordinates; + coordinates.push([lat / factor, lng / factor]); + } + + return coordinates; }; /** @@ -97,33 +98,28 @@ polyline.decode = function (str, precision) { * @param {Number} precision * @returns {String} */ -polyline.encode = function (coordinates, precision) { - if (!coordinates.length) { - return ''; - } +polyline.encode = function(coordinates, precision) { + if (!coordinates.length) { return ''; } - var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5), - output = - encode(coordinates[0][0], 0, factor) + - encode(coordinates[0][1], 0, factor); + var factor = Math.pow(10, Number.isInteger(precision) ? precision : 5), + output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor); - for (var i = 1; i < coordinates.length; i++) { - var a = coordinates[i], - b = coordinates[i - 1]; - output += encode(a[0], b[0], factor); - output += encode(a[1], b[1], factor); - } + for (var i = 1; i < coordinates.length; i++) { + var a = coordinates[i], b = coordinates[i - 1]; + output += encode(a[0], b[0], factor); + output += encode(a[1], b[1], factor); + } - return output; + return output; }; function flipped(coords) { - var flipped = []; - for (var i = 0; i < coords.length; i++) { - var coord = coords[i].slice(); - flipped.push([coord[1], coord[0]]); - } - return flipped; + var flipped = []; + for (var i = 0; i < coords.length; i++) { + var coord = coords[i].slice(); + flipped.push([coord[1], coord[0]]); + } + return flipped; } /** @@ -133,14 +129,14 @@ function flipped(coords) { * @param {Number} precision * @returns {String} */ -polyline.fromGeoJSON = function (geojson, precision) { - if (geojson && geojson.type === 'Feature') { - geojson = geojson.geometry; - } - if (!geojson || geojson.type !== 'LineString') { - throw new Error('Input must be a GeoJSON LineString'); - } - return polyline.encode(flipped(geojson.coordinates), precision); +polyline.fromGeoJSON = function(geojson, precision) { + if (geojson && geojson.type === 'Feature') { + geojson = geojson.geometry; + } + if (!geojson || geojson.type !== 'LineString') { + throw new Error('Input must be a GeoJSON LineString'); + } + return polyline.encode(flipped(geojson.coordinates), precision); }; /** @@ -150,13 +146,13 @@ polyline.fromGeoJSON = function (geojson, precision) { * @param {Number} precision * @returns {Object} */ -polyline.toGeoJSON = function (str, precision) { - var coords = polyline.decode(str, precision); - return { - type: 'LineString', - coordinates: flipped(coords), - }; +polyline.toGeoJSON = function(str, precision) { + var coords = polyline.decode(str, precision); + return { + type: 'LineString', + coordinates: flipped(coords) + }; }; let polyline_decode = polyline.decode; -export {polyline_decode as decode}; +export { polyline_decode as decode }; \ No newline at end of file diff --git a/apps/gg/script.js b/apps/gg/script.js index 700dc1f2..69e70d04 100644 --- a/apps/gg/script.js +++ b/apps/gg/script.js @@ -1,11 +1,4 @@ -import { - LitElement, - html, - unsafeHTML, - css, - guard, - until, -} from './lit-all.min.js'; +import {LitElement, html, unsafeHTML, css, guard, until} from './lit-all.min.js'; import * as tfrpc from '/static/tfrpc.js'; import * as polyline from './polyline.js'; import {gpx_parse} from './gpx.js'; @@ -63,7 +56,7 @@ class GgAppElement extends LitElement { this.focus = undefined; this.status = undefined; this.tab = 'map'; - this.load().catch(function (e) { + this.load().catch(function(e) { console.log('load error', e); }); this.to_build = '🏠'; @@ -72,12 +65,9 @@ class GgAppElement extends LitElement { async load() { console.log('load'); let emojis = await (await fetch('emojis.json')).json(); - emojis = Object.values(emojis) - .map((x) => Object.values(x)) - .flat(); + emojis = Object.values(emojis).map(x => Object.values(x)).flat(); let today = new Date(); - let date_index = - today.getYear() * 356 + today.getMonth() * 31 + today.getDate(); + let date_index = today.getYear() * 356 + today.getMonth() * 31 + today.getDate(); this.emoji_of_the_day = emojis[(date_index * 123457) % emojis.length]; this.user = await tfrpc.rpc.getUser(); this.url = (await tfrpc.rpc.url()).split('?')[0]; @@ -119,8 +109,7 @@ class GgAppElement extends LitElement { async get_activities_from_ssb() { this.status = {text: 'loading activities'}; this.loaded_activities = []; - let rows = await tfrpc.rpc.query( - ` + let rows = await tfrpc.rpc.query(` SELECT messages.author, json_extract(mention.value, '$.link') AS blob_id FROM messages_fts('"gg-activity"') JOIN messages ON messages.rowid = messages_fts.rowid, @@ -128,15 +117,10 @@ class GgAppElement extends LitElement { WHERE json_extract(messages.content, '$.type') = 'gg-activity' AND json_extract(mention.value, '$.name') = 'activity_data' ORDER BY messages.timestamp DESC - `, - [] - ); + `, []); this.status = {text: 'loading activity data'}; - let authors = rows.map((x) => x.author); - let blobs = await this.promise_all( - rows.map((x) => tfrpc.rpc.get_blob(x.blob_id)), - 8 - ); + let authors = rows.map(x => x.author); + let blobs = await this.promise_all(rows.map(x => tfrpc.rpc.get_blob(x.blob_id)), 8); this.status = {text: 'processing activity data'}; for (let [index, blob] of blobs.entries()) { let activity; @@ -151,19 +135,13 @@ class GgAppElement extends LitElement { } } this.status = {text: 'calculating balance'}; - rows = await tfrpc.rpc.query( - ` + rows = await tfrpc.rpc.query(` SELECT count(*) AS currency FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-activity' - `, - [this.whoami] - ); + `, [this.whoami]); let currency = rows[0].currency; - rows = await tfrpc.rpc.query( - ` + rows = await tfrpc.rpc.query(` SELECT SUM(json_extract(content, '$.cost')) AS cost FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-place' - `, - [this.whoami] - ); + `, [this.whoami]); let spent = rows[0].cost; this.currency = currency - spent; this.status = {text: 'getting placed emojis'}; @@ -188,11 +166,8 @@ class GgAppElement extends LitElement { } async sync_activities() { - let ids = this.activities.map( - (x) => `https://www.strava.com/activities/${x.id}` - ); - let missing = await tfrpc.rpc.query( - ` + let ids = this.activities.map(x => `https://www.strava.com/activities/${x.id}`); + let missing = await tfrpc.rpc.query(` WITH my_activities AS ( SELECT json_extract(mention.value, '$.link') AS url FROM messages, json_each(messages.content, '$.mentions') AS mention @@ -203,26 +178,17 @@ class GgAppElement extends LitElement { SELECT from_strava.value FROM json_each(?) AS from_strava LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url WHERE my_activities.url IS NULL - `, - [this.whoami, JSON.stringify(ids)] - ); + `, [this.whoami, JSON.stringify(ids)]); console.log('missing = ', missing); for (let [index, row] of missing.entries()) { - this.status = { - text: 'syncing from strava', - value: index, - max: missing.length, - }; + this.status = {text: 'syncing from strava', value: index, max: missing.length}; let url = row.value; let id = url.match(/.*\/(\d+)/)[1]; - let response = await fetch( - `https://www.strava.com/api/v3/activities/${id}`, - { - headers: { - Authorization: `Bearer ${this.strava.access_token}`, - }, - } - ); + let response = await fetch(`https://www.strava.com/api/v3/activities/${id}`, { + headers: { + 'Authorization': `Bearer ${this.strava.access_token}`, + }, + }); let activity = await response.json(); let blob_id = await tfrpc.rpc.store_blob(JSON.stringify(activity)); let message = { @@ -235,7 +201,7 @@ class GgAppElement extends LitElement { { link: blob_id, name: 'activity_data', - }, + } ], }; await tfrpc.rpc.appendMessage(this.whoami, message); @@ -249,20 +215,13 @@ class GgAppElement extends LitElement { return; } let ids = await tfrpc.rpc.getIdentities(); - let players = ids.length - ? ( - await tfrpc.rpc.query( - ` + let players = ids.length ? (await tfrpc.rpc.query(` SELECT author FROM messages JOIN json_each(?) ON messages.author = json_each.value WHERE json_extract(messages.content, '$.type') = 'gg-player' AND json_extract(messages.content, '$.active') ORDER BY timestamp DESC limit 1 - `, - [JSON.stringify(ids)] - ) - ).map((row) => row.author) - : []; + `, [JSON.stringify(ids)])).map(row => row.author) : []; if (!players.length) { this.whoami = await tfrpc.rpc.createIdentity(); if (this.whoami) { @@ -287,14 +246,9 @@ class GgAppElement extends LitElement { await tfrpc.rpc.databaseSet('strava', shared); await tfrpc.rpc.sharedDatabaseRemove(name); } - this.strava = JSON.parse((await tfrpc.rpc.databaseGet('strava')) || '{}'); + this.strava = JSON.parse(await tfrpc.rpc.databaseGet('strava') || '{}'); if (new Date().valueOf() / 1000 > this.strava.expires_at) { - console.log( - 'this looks expired', - new Date().valueOf() / 1000, - '>', - this.strava.expires_at - ); + console.log('this looks expired', new Date().valueOf() / 1000, '>', this.strava.expires_at); let x = await tfrpc.rpc.refresh_token(this.strava); if (x) { this.strava = x; @@ -307,16 +261,13 @@ class GgAppElement extends LitElement { async update_activities() { if (this?.strava?.access_token) { - let response = await fetch( - 'https://www.strava.com/api/v3/athlete/activities', - { - headers: { - Authorization: `Bearer ${this.strava.access_token}`, - }, - } - ); + let response = await fetch('https://www.strava.com/api/v3/athlete/activities', { + headers: { + 'Authorization': `Bearer ${this.strava.access_token}`, + }, + }); this.activities = await response.json(); - this.activities.sort((a, b) => a.id - b.id); + this.activities.sort((a, b) => (a.id - b.id)); } } @@ -331,12 +282,10 @@ class GgAppElement extends LitElement { [k_color_default, '🟧'], ]; for (let m of k_map) { - if ( - m[0][0] == color[0] && + if (m[0][0] == color[0] && m[0][1] == color[1] && m[0][2] == color[2] && - m[0][3] == color[3] - ) { + m[0][3] == color[3]) { return m[1]; } } @@ -380,11 +329,9 @@ class GgAppElement extends LitElement { on_click(event) { let popup = L.popup() .setLatLng(event.latlng) - .setContent( - ` + .setContent(` - ` - ) + `) .openOn(this.leaflet); } @@ -421,43 +368,31 @@ class GgAppElement extends LitElement { on_marker_click(event) { this.popup = L.popup() .setLatLng(event.latlng) - .setContent( - ` + .setContent(` ${this.to_build} (-${k_store[this.to_build]}) - ` - ) + `) .openOn(this.leaflet); } snap_to_grid(latlng, fudge, zoom) { - let position = this.leaflet.options.crs.latLngToPoint( - latlng, - zoom ?? this.leaflet.getZoom() - ); + let position = this.leaflet.options.crs.latLngToPoint(latlng, zoom ?? this.leaflet.getZoom()); position.x = Math.round(position.x / 16) * 16 + (fudge?.x ?? 0); position.y = Math.round(position.y / 16) * 16 + (fudge?.y ?? 0); - position = this.leaflet.options.crs.pointToLatLng( - position, - zoom ?? this.leaflet.getZoom() - ); + position = this.leaflet.options.crs.pointToLatLng(position, zoom ?? this.leaflet.getZoom()); return position; } on_marker_move(event) { if (!this.no_snap && this.marker) { this.no_snap = true; - this.marker.setLatLng( - this.snap_to_grid(this.marker.getLatLng(), k_marker_snap) - ); + this.marker.setLatLng(this.snap_to_grid(this.marker.getLatLng(), k_marker_snap)); this.no_snap = false; } } on_zoom(event) { if (this.marker) { - this.marker.setLatLng( - this.snap_to_grid(this.marker.getLatLng(), k_marker_snap) - ); + this.marker.setLatLng(this.snap_to_grid(this.marker.getLatLng(), k_marker_snap)); } } @@ -468,10 +403,7 @@ class GgAppElement extends LitElement { } if (this.to_build) { - this.marker = L.marker(this.snap_to_grid(event.latlng, k_marker_snap), { - icon: L.divIcon({className: 'build-icon'}), - draggable: true, - }).addTo(this.leaflet); + this.marker = L.marker(this.snap_to_grid(event.latlng, k_marker_snap), {icon: L.divIcon({className: 'build-icon'}), draggable: true}).addTo(this.leaflet); this.marker.on({click: this.on_marker_click.bind(this)}); this.marker.on({drag: this.on_marker_move.bind(this)}); } @@ -485,18 +417,14 @@ class GgAppElement extends LitElement { return; } if (!this.leaflet) { - this.leaflet = L.map(map, { - attributionControl: false, - maxZoom: 16, - bounceAtZoomLimits: false, - }); + this.leaflet = L.map(map, {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false}); this.leaflet.on({contextmenu: this.on_click.bind(this)}); this.leaflet.on({click: this.on_mouse_down.bind(this)}); this.leaflet.on({zoom: this.on_zoom.bind(this)}); } let self = this; let grid_layer = L.GridLayer.extend({ - createTile: function (coords) { + createTile: function(coords) { var tile = L.DomUtil.create('canvas', 'leaflet-tile'); var size = this.getTileSize(); tile.width = size.x; @@ -504,7 +432,7 @@ class GgAppElement extends LitElement { var context = tile.getContext('2d'); context.font = '10pt sans'; let bounds = this._tileCoordsToBounds(coords); - let degrees = 360.0 / 2 ** coords.z; + let degrees = 360.0 / (2 ** coords.z); let ul = bounds.getNorthWest(); let lr = bounds.getSouthEast(); @@ -514,53 +442,33 @@ class GgAppElement extends LitElement { let mini_context = mini.getContext('2d'); let image_data = context.getImageData(0, 0, mini.width, mini.height); for (let activity of self.loaded_activities) { - self.draw_activity_to_tile( - image_data, - mini.width, - mini.height, - ul, - lr, - activity - ); + self.draw_activity_to_tile(image_data, mini.width, mini.height, ul, lr, activity); } context.textAlign = 'left'; context.textBaseline = 'bottom'; for (let x = 0; x < mini.width; x++) { for (let y = 0; y < mini.height; y++) { let start = (y * mini.width + x) * 4; - let pixel = self.color_to_emoji( - image_data.data.slice(start, start + 4) - ); + let pixel = self.color_to_emoji(image_data.data.slice(start, start + 4)); if (pixel) { //context.fillRect(x * size.x / mini.width, y * size.y / mini.height, size.x / mini.width, size.y / mini.height); - context.fillText( - pixel, - (x * size.x) / mini.width, - (y * size.y) / mini.height + mini.height - ); + context.fillText(pixel, x * size.x / mini.width, y * size.y / mini.height + mini.height); } } } for (let placed of self.placed_emojis) { - let position = self.leaflet.options.crs.latLngToPoint( - self.snap_to_grid(placed.position, undefined, coords.z), - coords.z - ); + let position = self.leaflet.options.crs.latLngToPoint(self.snap_to_grid(placed.position, undefined, coords.z), coords.z); let tile_x = Math.floor(position.x / size.x); let tile_y = Math.floor(position.y / size.y); position.x = position.x - tile_x * size.x; position.y = position.y - tile_y * size.y; if (tile_x == coords.x && tile_y == coords.y) { //context.fillRect(position.x, position.y, size.x / mini.width, size.y / mini.height); - context.fillText( - placed.emoji, - position.x, - position.y + mini.height - ); + context.fillText(placed.emoji, position.x, position.y + mini.height); } } return tile; - }, + } }); if (this.grid_layer) { this.grid_layer.redraw(); @@ -576,7 +484,10 @@ class GgAppElement extends LitElement { this.max_lon = Math.max(this.max_lon, bounds.max.lng); } if (this.focus) { - this.leaflet.fitBounds([this.focus.min, this.focus.max]); + this.leaflet.fitBounds([ + this.focus.min, + this.focus.max, + ]); this.focus = undefined; } else { this.leaflet.fitBounds([ @@ -677,12 +588,7 @@ class GgAppElement extends LitElement { let sy = y0 < y1 ? 1 : -1; let error = dx + dy; while (true) { - if ( - x0 >= 0 && - y0 >= 0 && - x0 < image_data.width && - y0 < image_data.height - ) { + if (x0 >= 0 && y0 >= 0 && x0 < image_data.width && y0 < image_data.height) { let base = (y0 * image_data.width + x0) * 4; image_data.data[base + 0] = value[0]; image_data.data[base + 1] = value[1]; @@ -717,8 +623,8 @@ class GgAppElement extends LitElement { let last; for (let pt of polyline.decode(activity.map.polyline)) { let px = [ - Math.floor((width * (pt[1] - ul.lng)) / (lr.lng - ul.lng)), - Math.floor((height * (pt[0] - ul.lat)) / (lr.lat - ul.lat)), + Math.floor(width * (pt[1] - ul.lng) / (lr.lng - ul.lng)), + Math.floor(height * (pt[0] - ul.lat) / (lr.lat - ul.lat)), ]; if (last) { this.line(image_data, last[0], last[1], px[0], px[1], color); @@ -731,8 +637,8 @@ class GgAppElement extends LitElement { let last; for (let pt of segment) { let px = [ - Math.floor((width * (pt.lon - ul.lng)) / (lr.lng - ul.lng)), - Math.floor((height * (pt.lat - ul.lat)) / (lr.lat - ul.lat)), + Math.floor(width * (pt.lon - ul.lng) / (lr.lng - ul.lng)), + Math.floor(height * (pt.lat - ul.lat) / (lr.lat - ul.lat)), ]; if (last) { this.line(image_data, last[0], last[1], px[0], px[1], color); @@ -761,7 +667,7 @@ class GgAppElement extends LitElement { { link: blob_id, name: 'activity_data', - }, + } ], }; console.log('id =', this.whoami, 'message = ', message); @@ -787,7 +693,8 @@ class GgAppElement extends LitElement { focus_map(activity) { let bounds = this.activity_bounds(activity); - if (bounds.min.lat < bounds.max.lat && bounds.min.lng < bounds.max.lng) { + if (bounds.min.lat < bounds.max.lat && + bounds.min.lng < bounds.max.lng) { this.tab = 'map'; this.focus = bounds; } @@ -796,13 +703,9 @@ class GgAppElement extends LitElement { render_news() { return html`
      - ${this.loaded_activities.map( - (x) => html` -
    • this.focus_map(x)}> - ${x.author} ${x.name ?? x.time} -
    • - ` - )} + ${this.loaded_activities.map(x => html` +
    • this.focus_map(x)}>${x.author} ${x.name ?? x.time}
    • + `)}
    `; } @@ -811,7 +714,7 @@ class GgAppElement extends LitElement { let [emoji, cost] = item; return html`
    - (this.to_build = emoji)}> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined} + this.to_build = emoji}> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined}
    `; } @@ -829,10 +732,7 @@ class GgAppElement extends LitElement { render() { let header; if (!this.user?.credentials?.session?.name) { - header = html`
    - Please login to - Tilde Friends, first. -
    `; + header = html`
    Please login to Tilde Friends, first.
    `; } else if (!this.strava?.access_token) { let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`; header = html` @@ -865,10 +765,10 @@ class GgAppElement extends LitElement { } `; @@ -890,15 +790,13 @@ class GgAppElement extends LitElement { return html` - -
    + +
    ${header}
    ${content}
    ${navigation} @@ -906,4 +804,4 @@ class GgAppElement extends LitElement { `; } } -customElements.define('gg-app', GgAppElement); +customElements.define('gg-app', GgAppElement); \ No newline at end of file diff --git a/apps/gg/strava.js b/apps/gg/strava.js index 3955e403..8c78b412 100644 --- a/apps/gg/strava.js +++ b/apps/gg/strava.js @@ -17,4 +17,4 @@ export async function authorization_code(code) { method: 'POST', body: `client_id=${k_client_id}&client_secret=${k_client_secret}&code=${code}&grant_type=authorization_code`, }); -} +} \ No newline at end of file diff --git a/apps/identity.json b/apps/identity.json index a4c28e41..91328bcb 100644 --- a/apps/identity.json +++ b/apps/identity.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "🪪", - "previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256" -} + "type": "tildefriends-app", + "emoji": "🪪", + "previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256" +} \ No newline at end of file diff --git a/apps/identity/app.js b/apps/identity/app.js index a56e5572..2aa50363 100644 --- a/apps/identity/app.js +++ b/apps/identity/app.js @@ -18,8 +18,7 @@ tfrpc.register(async function reload() { async function main() { let ids = await ssb.getIdentities(); - await app.setDocument( - ` + await app.setDocument(` + + - + \ No newline at end of file diff --git a/apps/issues/script.js b/apps/issues/script.js index cdb37b91..1c9c2d32 100644 --- a/apps/issues/script.js +++ b/apps/issues/script.js @@ -31,12 +31,7 @@ class TfIdPickerElement extends LitElement { if (this.ids) { return html` `; } else { @@ -62,15 +57,13 @@ class TfComposeElement extends LitElement { } submit() { - this.dispatchEvent( - new CustomEvent('tf-submit', { - bubbles: true, - composed: true, - detail: { - value: this.renderRoot.getElementById('input').value, - }, - }) - ); + this.dispatchEvent(new CustomEvent('tf-submit', { + bubbles: true, + composed: true, + detail: { + value: this.renderRoot.getElementById('input').value, + }, + })); this.renderRoot.getElementById('input').value = ''; this.input(); } @@ -103,8 +96,7 @@ class TfIssuesAppElement extends LitElement { async load() { let issues = {}; - let messages = await tfrpc.rpc.query( - ` + let messages = await tfrpc.rpc.query(` WITH issues AS (SELECT messages.* FROM messages_refs JOIN messages ON messages.id = messages_refs.message WHERE messages_refs.ref = ? AND json_extract(messages.content, '$.type') = 'issue'), @@ -115,9 +107,7 @@ class TfIssuesAppElement extends LitElement { SELECT * FROM issues UNION SELECT * FROM edits ORDER BY timestamp - `, - [k_project] - ); + `, [k_project]); for (let message of messages) { let content = JSON.parse(message.content); switch (content.type) { @@ -133,7 +123,7 @@ class TfIssuesAppElement extends LitElement { break; case 'issue-edit': case 'post': - for (let issue of content.issues || []) { + for (let issue of (content.issues || [])) { if (issues[issue.link]) { if (issue.open !== undefined) { issues[issue.link].open = issue.open; @@ -146,9 +136,7 @@ class TfIssuesAppElement extends LitElement { break; } } - this.issues = Object.values(issues).sort( - (x, y) => y.open - x.open || y.created - x.created - ); + this.issues = Object.values(issues).sort((x, y) => (y.open - x.open) || (y.created - x.created)); if (this.selected) { for (let issue of this.issues) { if (issue.id == this.selected.id) { @@ -162,20 +150,11 @@ class TfIssuesAppElement extends LitElement { return html` ${issue.open ? '☐ open' : '☑ closed'} - - ${issue.author} - - (this.selected = issue)} - > + ${issue.author} + this.selected = issue}> ${issue.text.split('\n')?.[0]} - - ${new Date(issue.updated ?? issue.created).toLocaleDateString()} - + ${new Date(issue.updated ?? issue.created).toLocaleDateString()} `; } @@ -191,21 +170,13 @@ class TfIssuesAppElement extends LitElement {
    ${new Date(update.timestamp).toLocaleString()}
    ${update.author}
    ${message}
    -
    - ${update.open !== undefined - ? update.open - ? 'issue opened' - : 'issue closed' - : undefined} -
    +
    ${update.open !== undefined ? (update.open ? 'issue opened' : 'issue closed') : undefined}
    `; } async set_open(id, open) { - if ( - confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`) - ) { + if (confirm(`Are you sure you want to ${open ? 'open' : 'close'} this issue?`)) { let whoami = this.shadowRoot.getElementById('picker').selected; await tfrpc.rpc.appendMessage(whoami, { type: 'issue-edit', @@ -236,9 +207,7 @@ class TfIssuesAppElement extends LitElement { type: 'post', text: event.detail.value, root: this.selected.id, - branch: this.selected.updates.length - ? this.selected.updates[this.selected.updates.length - 1].id - : this.selected.id, + branch: this.selected.updates.length ? this.selected.updates[this.selected.updates.length - 1].id : this.selected.id, issues: [ { link: this.selected.id, @@ -257,18 +226,16 @@ class TfIssuesAppElement extends LitElement { return html` ${header}
    - (this.selected = undefined)}> - ${ - this.selected.open - ? html` this.set_open(this.selected.id, false)}>` - : html` this.set_open(this.selected.id, true)}>` - } + this.selected = undefined}> + ${this.selected.open ? + html` this.set_open(this.selected.id, false)}>` : + html` this.set_open(this.selected.id, true)}>`}
    ${new Date(this.selected.created).toLocaleString()}
    ${this.selected.author}
    ${this.selected.id}
    ${unsafeHTML(tfutils.markdown(this.selected.text))}
    - ${this.selected.updates.map((x) => this.render_update(x))} + ${this.selected.updates.map(x => this.render_update(x))} `; } else { @@ -283,11 +250,11 @@ class TfIssuesAppElement extends LitElement { Title Date - ${this.issues.map((x) => this.render_issue_table_row(x))} + ${this.issues.map(x => this.render_issue_table_row(x))} `; } } } -customElements.define('tf-issues-app', TfIssuesAppElement); +customElements.define('tf-issues-app', TfIssuesAppElement); \ No newline at end of file diff --git a/apps/issues/tf-utils.js b/apps/issues/tf-utils.js index 444e2a93..a1c7666a 100644 --- a/apps/issues/tf-utils.js +++ b/apps/issues/tf-utils.js @@ -1,32 +1,20 @@ import * as linkify from './commonmark-linkify.js'; function image(node, entering) { - if ( - node.firstChild?.type === 'text' && - node.firstChild.literal.startsWith('video:') - ) { + if (node.firstChild?.type === 'text' && + node.firstChild.literal.startsWith('video:')) { if (entering) { - this.lit( - '