From 41024ddb7961b04a5688bbc997cb74de6fab4763 Mon Sep 17 00:00:00 2001 From: Tasia Date: Thu, 22 Feb 2024 15:36:45 +0100 Subject: [PATCH 1/6] build: Add prettier to the project --- .git-blame-ignore-revs | 1 + .prettierignore | 14 + .prettierrc.yaml | 10 + README.md | 38 +- apps/admin.json | 6 +- apps/admin/app.js | 10 +- apps/admin/index.html | 8 +- apps/admin/script.js | 55 ++- apps/api.json | 8 +- apps/api/docs.js | 4 +- apps/apps.json | 8 +- apps/apps/app.js | 12 +- apps/blog.json | 8 +- apps/blog/app.js | 2 +- apps/blog/blog.js | 48 +- apps/blog/handler.js | 40 +- apps/db/app.js | 4 +- apps/follow.json | 6 +- apps/follow/app.js | 136 ++++-- apps/gg.json | 8 +- apps/gg/app.js | 17 +- apps/gg/gpx.js | 9 +- apps/gg/handler.js | 2 +- apps/gg/index.html | 20 +- apps/gg/polyline.js | 174 +++---- apps/gg/script.js | 262 +++++++--- apps/gg/strava.js | 2 +- apps/identity.json | 8 +- apps/identity/app.js | 18 +- apps/issues.json | 8 +- apps/issues/app.js | 10 +- apps/issues/index.html | 12 +- apps/issues/script.js | 83 +++- apps/issues/tf-utils.js | 50 +- apps/journal.json | 8 +- apps/journal/app.js | 44 +- apps/journal/index.html | 10 +- apps/journal/tf-id-picker.js | 21 +- apps/journal/tf-journal-app.js | 28 +- apps/journal/tf-journal-entry.js | 41 +- apps/sneaker.json | 6 +- apps/sneaker/app.js | 2 +- apps/sneaker/index.html | 12 +- apps/sneaker/script.js | 75 ++- apps/ssb.json | 8 +- apps/ssb/app.js | 10 +- apps/ssb/emojis.js | 14 +- apps/ssb/index.html | 14 +- apps/ssb/script.js | 2 +- apps/ssb/tf-app.js | 141 ++++-- apps/ssb/tf-compose.js | 223 ++++++--- apps/ssb/tf-id-picker.js | 27 +- apps/ssb/tf-message.js | 561 ++++++++++++++------- apps/ssb/tf-news.js | 44 +- apps/ssb/tf-profile.js | 161 ++++-- apps/ssb/tf-styles.js | 101 ++-- apps/ssb/tf-tab-connections.js | 93 +++- apps/ssb/tf-tab-mentions.js | 21 +- apps/ssb/tf-tab-news-feed.js | 91 ++-- apps/ssb/tf-tab-news.js | 62 ++- apps/ssb/tf-tab-query.js | 40 +- apps/ssb/tf-tab-search.js | 12 +- apps/ssb/tf-tag.js | 8 +- apps/ssb/tf-user.js | 31 +- apps/ssb/tf-utils.js | 50 +- apps/ssb/tribute.css | 38 +- apps/todo.json | 6 +- apps/todo/app.js | 8 +- apps/todo/index.html | 4 +- apps/todo/script.js | 104 ++-- apps/welcome.json | 8 +- apps/welcome/app.js | 2 +- apps/welcome/index.html | 152 ++++-- apps/wiki.json | 8 +- apps/wiki/app.js | 2 +- apps/wiki/handler.js | 23 +- apps/wiki/index.html | 10 +- apps/wiki/tf-collection.js | 90 ++-- apps/wiki/tf-id-picker.js | 21 +- apps/wiki/tf-wiki-app.js | 149 ++++-- apps/wiki/tf-wiki-doc.js | 137 ++++-- apps/wiki/utils.js | 66 ++- core/app.js | 98 ++-- core/auth.html | 12 +- core/auth.js | 167 +++++-- core/client.js | 794 ++++++++++++++++++++---------- core/core.js | 812 ++++++++++++++++++++----------- core/form.js | 10 +- core/http.js | 101 ++-- core/index.html | 139 +++++- core/style.css | 64 ++- core/tfrpc.js | 48 +- docs/guide.md | 180 +++---- package-lock.json | 28 ++ package.json | 11 + 95 files changed, 4237 insertions(+), 2117 deletions(-) create mode 100644 .git-blame-ignore-revs create mode 100644 .prettierignore create mode 100644 .prettierrc.yaml create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..47b9ddba --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +# Add prettier to the project diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..595b4cc1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000..1f7ef42f --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,10 @@ +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 b7c474a2..c6073dc5 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,37 @@ # 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. -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`. +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`. 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. -The first user to create an account and log in will be granted administrative -privileges. Further administration can be done at -. +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 . ## 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 711779a2..a9a2e723 100644 --- a/apps/admin.json +++ b/apps/admin.json @@ -1,4 +1,4 @@ { - "type": "tildefriends-app", - "emoji": "๐ŸŽ›" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "๐ŸŽ›" +} diff --git a/apps/admin/app.js b/apps/admin/app.js index 0a1c35e8..5916f11f 100644 --- a/apps/admin/app.js +++ b/apps/admin/app.js @@ -18,9 +18,13 @@ 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(); \ No newline at end of file +main(); diff --git a/apps/admin/index.html b/apps/admin/index.html index eabbf708..114ab1b9 100644 --- a/apps/admin/index.html +++ b/apps/admin/index.html @@ -1,10 +1,12 @@ - + - +

Tilde Friends Administration

- \ No newline at end of file + diff --git a/apps/admin/script.js b/apps/admin/script.js index 6d43d291..e9041412 100644 --- a/apps/admin/script.js +++ b/apps/admin/script.js @@ -3,25 +3,32 @@ 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` @@ -62,26 +69,24 @@ 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 2e226336..511730eb 100644 --- a/apps/api.json +++ b/apps/api.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "๐Ÿ“œ", - "previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "๐Ÿ“œ", + "previous": "&miGORZ8BwjHg2YO0t4bms6SI28XWPYqnqOZ8u9zsbZc=.sha256" +} diff --git a/apps/api/docs.js b/apps/api/docs.js index 9e045bbb..037bf240 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 7b0370a0..03545742 100644 --- a/apps/apps.json +++ b/apps/apps.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "๐Ÿ’ป", - "previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "๐Ÿ’ป", + "previous": "&RdVEsVscZm3aWzcMrEZS8mskO5tUmvaEUihex2MMfZQ=.sha256" +} diff --git a/apps/apps/app.js b/apps/apps/app.js index d31f1ab7..2cdf9faa 100644 --- a/apps/apps/app.js +++ b/apps/apps/app.js @@ -26,14 +26,15 @@ 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') { @@ -44,10 +45,13 @@ 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 782fa7bf..d6a298e8 100644 --- a/apps/blog.json +++ b/apps/blog.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "๐Ÿชต", - "previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "๐Ÿชต", + "previous": "&TIrBnpN3iz3O9L9MCCteAcVJZjA83EKdcfu4SCM76VE=.sha256" +} diff --git a/apps/blog/app.js b/apps/blog/app.js index 99c0165d..66a800db 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(); \ No newline at end of file +main(); diff --git a/apps/blog/blog.js b/apps/blog/blog.js index 4a3e0866..d2ba4e01 100644 --- a/apps/blog/blog.js +++ b/apps/blog/blog.js @@ -1,11 +1,19 @@ 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) { @@ -13,7 +21,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, @@ -21,7 +29,8 @@ export async function get_blog_message(id) { blog: content?.blog, title: content?.title, }; - }); + } + ); if (message) { await ssb.sqlAsync( ` @@ -34,9 +43,10 @@ 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; } @@ -51,8 +61,12 @@ 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); } } @@ -107,7 +121,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')} `; } @@ -135,14 +149,15 @@ 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 @@ -182,8 +197,11 @@ 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 ef14b62b..32b54bef 100644 --- a/apps/blog/handler.js +++ b/apps/blog/handler.js @@ -2,30 +2,50 @@ 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'}); -}); \ No newline at end of file +main().catch(function (error) { + respond({ + data: ` +
    ${error.message}\n${error.stack}
    `, + content_type: 'text/html', + }); +}); diff --git a/apps/db/app.js b/apps/db/app.js index 6e6c2c41..1f65b890 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(); \ No newline at end of file +database_list(); diff --git a/apps/follow.json b/apps/follow.json index 5e137e35..3cf770d7 100644 --- a/apps/follow.json +++ b/apps/follow.json @@ -1,4 +1,4 @@ { - "type": "tildefriends-app", - "emoji": "โžก๏ธ" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "โžก๏ธ" +} diff --git a/apps/follow/app.js b/apps/follow/app.js index 676b7423..098f8f22 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,7 +21,8 @@ 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) { @@ -42,15 +43,34 @@ 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())]; @@ -68,10 +88,22 @@ 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); @@ -90,13 +122,15 @@ 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]; @@ -129,17 +163,21 @@ 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; @@ -155,41 +193,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; @@ -198,15 +236,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( @@ -221,7 +259,8 @@ async function getSizes(ids) { [JSON.stringify(ids)], function (row) { sizes[row.author] = row.size; - }); + } + ); return sizes; } @@ -241,7 +280,10 @@ function niceSize(bytes) { } function escape(value) { - return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); + return value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>'); } async function main() { @@ -249,19 +291,27 @@ 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(); \ No newline at end of file +main(); diff --git a/apps/gg.json b/apps/gg.json index b5a6f698..16eba8e0 100644 --- a/apps/gg.json +++ b/apps/gg.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "๐Ÿ—บ", - "previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "๐Ÿ—บ", + "previous": "&0XSp+xdQwVtQ88bXzvWdH15Ex63hv5zUKTa4zx7HBGM=.sha256" +} diff --git a/apps/gg/app.js b/apps/gg/app.js index d8ae30db..2384e409 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,10 +71,15 @@ 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(); \ No newline at end of file +main(); diff --git a/apps/gg/gpx.js b/apps/gg/gpx.js index 93421d88..56875d17 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,7 +63,10 @@ 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); } @@ -78,4 +81,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 647e81cd..4f210ce5 100644 --- a/apps/gg/handler.js +++ b/apps/gg/handler.js @@ -18,4 +18,4 @@ async function main() { status_code: 307, }); } -main(); \ No newline at end of file +main(); diff --git a/apps/gg/index.html b/apps/gg/index.html index 57d98809..66a7f723 100644 --- a/apps/gg/index.html +++ b/apps/gg/index.html @@ -1,14 +1,26 @@ - + - + - + - \ No newline at end of file + diff --git a/apps/gg/polyline.js b/apps/gg/polyline.js index 9f1dff5f..5cea31ce 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,54 +41,53 @@ 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) { + // 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; - // Reset shift, result, and byte - byte = null; - shift = 1; - result = 0; + do { + byte = str.charCodeAt(index++) - 63; + result += (byte & 0x1f) * shift; + shift *= 32; + } while (byte >= 0x20); - do { - byte = str.charCodeAt(index++) - 63; - result += (byte & 0x1f) * shift; - shift *= 32; - } while (byte >= 0x20); + latitude_change = result & 1 ? (-result - 1) / 2 : result / 2; - latitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2); + shift = 1; + result = 0; - shift = 1; - result = 0; + do { + byte = str.charCodeAt(index++) - 63; + result += (byte & 0x1f) * shift; + shift *= 32; + } while (byte >= 0x20); - do { - byte = str.charCodeAt(index++) - 63; - result += (byte & 0x1f) * shift; - shift *= 32; - } while (byte >= 0x20); + longitude_change = result & 1 ? (-result - 1) / 2 : result / 2; - longitude_change = (result & 1) ? ((-result - 1) / 2) : (result / 2); + lat += latitude_change; + lng += longitude_change; - lat += latitude_change; - lng += longitude_change; + coordinates.push([lat / factor, lng / factor]); + } - coordinates.push([lat / factor, lng / factor]); - } - - return coordinates; + return coordinates; }; /** @@ -98,28 +97,33 @@ 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; } /** @@ -129,14 +133,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); }; /** @@ -146,13 +150,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 }; \ No newline at end of file +export {polyline_decode as decode}; diff --git a/apps/gg/script.js b/apps/gg/script.js index 69e70d04..700dc1f2 100644 --- a/apps/gg/script.js +++ b/apps/gg/script.js @@ -1,4 +1,11 @@ -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'; @@ -56,7 +63,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 = '๐Ÿ '; @@ -65,9 +72,12 @@ 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]; @@ -109,7 +119,8 @@ 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, @@ -117,10 +128,15 @@ 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; @@ -135,13 +151,19 @@ 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'}; @@ -166,8 +188,11 @@ 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 @@ -178,17 +203,26 @@ 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 = { @@ -201,7 +235,7 @@ class GgAppElement extends LitElement { { link: blob_id, name: 'activity_data', - } + }, ], }; await tfrpc.rpc.appendMessage(this.whoami, message); @@ -215,13 +249,20 @@ 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) { @@ -246,9 +287,14 @@ 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; @@ -261,13 +307,16 @@ 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); } } @@ -282,10 +331,12 @@ 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]; } } @@ -329,9 +380,11 @@ class GgAppElement extends LitElement { on_click(event) { let popup = L.popup() .setLatLng(event.latlng) - .setContent(` + .setContent( + ` - `) + ` + ) .openOn(this.leaflet); } @@ -368,31 +421,43 @@ 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) + ); } } @@ -403,7 +468,10 @@ 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)}); } @@ -417,14 +485,18 @@ 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; @@ -432,7 +504,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(); @@ -442,33 +514,53 @@ 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(); @@ -484,10 +576,7 @@ 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([ @@ -588,7 +677,12 @@ 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]; @@ -623,8 +717,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); @@ -637,8 +731,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); @@ -667,7 +761,7 @@ class GgAppElement extends LitElement { { link: blob_id, name: 'activity_data', - } + }, ], }; console.log('id =', this.whoami, 'message = ', message); @@ -693,8 +787,7 @@ 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; } @@ -703,9 +796,13 @@ 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} +
    • + ` + )}
    `; } @@ -714,7 +811,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}
    `; } @@ -732,7 +829,10 @@ 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` @@ -765,10 +865,10 @@ class GgAppElement extends LitElement { } `; @@ -790,13 +890,15 @@ class GgAppElement extends LitElement { return html` - -
    + +
    ${header}
    ${content}
    ${navigation} @@ -804,4 +906,4 @@ class GgAppElement extends LitElement { `; } } -customElements.define('gg-app', GgAppElement); \ No newline at end of file +customElements.define('gg-app', GgAppElement); diff --git a/apps/gg/strava.js b/apps/gg/strava.js index 8c78b412..3955e403 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 91328bcb..a4c28e41 100644 --- a/apps/identity.json +++ b/apps/identity.json @@ -1,5 +1,5 @@ { - "type": "tildefriends-app", - "emoji": "๐Ÿชช", - "previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256" -} \ No newline at end of file + "type": "tildefriends-app", + "emoji": "๐Ÿชช", + "previous": "&kgukkyDk1RxgfzgMH6H/0QeDPIuwPZypLuAFax21ljk=.sha256" +} diff --git a/apps/identity/app.js b/apps/identity/app.js index 2aa50363..a56e5572 100644 --- a/apps/identity/app.js +++ b/apps/identity/app.js @@ -18,7 +18,8 @@ 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 1c9c2d32..cdb37b91 100644 --- a/apps/issues/script.js +++ b/apps/issues/script.js @@ -31,7 +31,12 @@ class TfIdPickerElement extends LitElement { if (this.ids) { return html` `; } else { @@ -57,13 +62,15 @@ 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(); } @@ -96,7 +103,8 @@ 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'), @@ -107,7 +115,9 @@ 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) { @@ -123,7 +133,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; @@ -136,7 +146,9 @@ 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) { @@ -150,11 +162,20 @@ 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()} + `; } @@ -170,13 +191,21 @@ 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', @@ -207,7 +236,9 @@ 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, @@ -226,16 +257,18 @@ 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 { @@ -250,11 +283,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); \ No newline at end of file +customElements.define('tf-issues-app', TfIssuesAppElement); diff --git a/apps/issues/tf-utils.js b/apps/issues/tf-utils.js index a1c7666a..444e2a93 100644 --- a/apps/issues/tf-utils.js +++ b/apps/issues/tf-utils.js @@ -1,20 +1,32 @@ 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('