diff --git a/.circleci/config.yml b/.circleci/config.yml
index 43fba6e7..05aef636 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,5 +1,7 @@
version: 2.1
+orbs:
+ browser-tools: circleci/browser-tools@1.1.3
workflows:
version: 2
build_and_test:
@@ -15,13 +17,11 @@ executors:
node:
working_directory: ~/pinafore
docker:
- # we want Node v12, not v14
- # see https://discuss.circleci.com/t/build-failed-the-engine-node-is-incompatible-with-this-module-expected-version-12-x-got-14-15-0/37921/7
- - image: circleci/ruby@sha256:b018ec2a8f0bbf06880735d2801402bad316c465edb60663be83ac8f1086b805
+ - image: cimg/ruby:2.7.2-browsers
node_and_ruby:
working_directory: ~/pinafore
docker:
- - image: circleci/ruby@sha256:b018ec2a8f0bbf06880735d2801402bad316c465edb60663be83ac8f1086b805
+ - image: cimg/ruby:2.7.2-browsers
- image: circleci/postgres:12.2
environment:
POSTGRES_USER: pinafore
@@ -30,6 +30,45 @@ executors:
BROWSER: chrome:headless
- image: circleci/redis:5-alpine
commands:
+ install_mastodon_system_dependencies:
+ description: Install system dependencies that Mastodon requires
+ steps:
+ - run:
+ name: Install system dependencies
+ command: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ ffmpeg \
+ fonts-noto-color-emoji \
+ imagemagick \
+ libicu-dev \
+ libidn11-dev \
+ libprotobuf-dev \
+ postgresql-contrib \
+ protobuf-compiler
+ install_browsers:
+ description: Install browsers and tools
+ steps:
+ - browser-tools/install-chrome:
+ chrome-version: 91.0.4472.114
+ - browser-tools/install-chromedriver
+ - run:
+ name: "Check browser version"
+ command: |
+ google-chrome --version
+ install_node:
+ description: Install Node.js
+ steps:
+ - run:
+ name: "Install Node.js"
+ # via https://circleci.com/docs/2.0/circleci-images/#notes-on-pinning-images
+ command: |
+ curl -sSL "https://nodejs.org/dist/v12.22.2/node-v12.22.2-linux-x64.tar.xz" \
+ | sudo tar --strip-components=2 -xJ -C /usr/local/bin/ node-v12.22.2-linux-x64/bin/node
+ - run:
+ name: Check current version of node
+ command: node -v
+
save_workspace:
description: Persist workspace
steps:
@@ -47,13 +86,13 @@ commands:
steps:
- restore_cache:
name: Restore yarn cache
- key: yarn-v3-{{ checksum "yarn.lock" }}
+ key: yarn-v4-{{ checksum "yarn.lock" }}
save_yarn_cache:
description: Save yarn cache
steps:
- save_cache:
name: Save yarn cache
- key: yarn-v3-{{ checksum "yarn.lock" }}
+ key: yarn-v4-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
restore_yarn_cache_mastodon:
@@ -61,13 +100,13 @@ commands:
steps:
- restore_cache:
name: Restore yarn cache for Mastodon
- key: yarn-v3-{{ checksum "mastodon/yarn.lock" }}
+ key: yarn-v4-{{ checksum "mastodon/yarn.lock" }}
save_yarn_cache_mastodon:
description: Save yarn cache for Mastodon
steps:
- save_cache:
name: Save yarn cache for Mastodon
- key: yarn-v3-{{ checksum "mastodon/yarn.lock" }}
+ key: yarn-v4-{{ checksum "mastodon/yarn.lock" }}
paths:
- ~/.cache/yarn
restore_bundler_cache:
@@ -75,23 +114,18 @@ commands:
steps:
- restore_cache:
name: Restore bundler cache
- key: bundler-v2-{{ checksum "mastodon/Gemfile.lock" }}
+ key: bundler-v4-{{ checksum "mastodon/Gemfile.lock" }}
save_bundler_cache:
description: Save bundler cache
steps:
- save_cache:
name: Save bundler cache
- key: bundler-v2-{{ checksum "mastodon/Gemfile.lock" }}
+ key: bundler-v4-{{ checksum "mastodon/Gemfile.lock" }}
paths:
- mastodon/vendor/bundle
install_mastodon:
description: Install Mastodon and set up Postgres/Redis
steps:
- - run:
- name: Install system dependencies
- command: |
- sudo apt-get update
- sudo apt-get install -y ffmpeg fonts-noto-color-emoji libicu-dev libidn11-dev libprotobuf-dev postgresql-contrib protobuf-compiler
- run:
name: Clone mastodon
command: yarn clone-mastodon
@@ -127,6 +161,7 @@ jobs:
executor: node
steps:
- checkout
+ - install_node
- restore_yarn_cache
- run:
name: Yarn install
@@ -156,6 +191,9 @@ jobs:
integration_test_readonly:
executor: node_and_ruby
steps:
+ - install_mastodon_system_dependencies
+ - install_browsers
+ - install_node
- load_workspace
- install_mastodon
- run:
@@ -164,6 +202,9 @@ jobs:
integration_test_readwrite:
executor: node_and_ruby
steps:
+ - install_mastodon_system_dependencies
+ - install_browsers
+ - install_node
- load_workspace
- install_mastodon
- run:
diff --git a/.gitignore b/.gitignore
index 4e191c72..356c3b52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ yarn-error.log
.now
.vercel
+/webpack/*.cjs
diff --git a/bin/build-assets.js b/bin/build-assets.js
index dbd71a27..f493c724 100644
--- a/bin/build-assets.js
+++ b/bin/build-assets.js
@@ -1,9 +1,10 @@
import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
-import { LOCALE } from '../src/routes/_static/intl'
-import { getIntl, trimWhitespace } from './getIntl'
+import { LOCALE } from '../src/routes/_static/intl.js'
+import { getIntl, trimWhitespace } from './getIntl.js'
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
diff --git a/bin/build-inline-script.js b/bin/build-inline-script.js
index 74626441..e0dc2e56 100644
--- a/bin/build-inline-script.js
+++ b/bin/build-inline-script.js
@@ -5,13 +5,12 @@ import path from 'path'
import { rollup } from 'rollup'
import { terser } from 'rollup-plugin-terser'
import replace from '@rollup/plugin-replace'
-import fromPairs from 'lodash-es/fromPairs'
-import { themes } from '../src/routes/_static/themes'
-import terserOptions from './terserOptions'
+import { themes } from '../src/routes/_static/themes.js'
+import terserOptions from './terserOptions.js'
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const writeFile = promisify(fs.writeFile)
-
-const themeColors = fromPairs(themes.map(_ => ([_.name, _.color])))
+const themeColors = Object.fromEntries(themes.map(_ => ([_.name, _.color])))
export async function buildInlineScript () {
const inlineScriptPath = path.join(__dirname, '../src/inline-script/inline-script.js')
@@ -42,7 +41,7 @@ export async function buildInlineScript () {
const checksum = crypto.createHash('sha256').update(fullCode, 'utf8').digest('base64')
await writeFile(path.resolve(__dirname, '../src/inline-script/checksum.js'),
- `module.exports = ${JSON.stringify(checksum)}`, 'utf8')
+ `export default ${JSON.stringify(checksum)}`, 'utf8')
await writeFile(path.resolve(__dirname, '../static/inline-script.js.map'),
map.toString(), 'utf8')
diff --git a/bin/build-sass.js b/bin/build-sass.js
index 8beab8ef..25e3f67f 100755
--- a/bin/build-sass.js
+++ b/bin/build-sass.js
@@ -3,10 +3,12 @@ import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
import cssDedoupe from 'css-dedoupe'
-import { TextDecoder } from 'text-encoding'
+import textEncodingPackage from 'text-encoding'
+const { TextDecoder } = textEncodingPackage
const writeFile = promisify(fs.writeFile)
const readdir = promisify(fs.readdir)
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const globalScss = path.join(__dirname, '../src/scss/global.scss')
const defaultThemeScss = path.join(__dirname, '../src/scss/themes/_default.scss')
diff --git a/bin/build-svg.js b/bin/build-svg.js
index de2e445a..18c83a4a 100755
--- a/bin/build-svg.js
+++ b/bin/build-svg.js
@@ -1,10 +1,13 @@
-import svgs from './svgs'
+import svgs from './svgs.js'
import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
import { optimize } from 'svgo'
-import $ from 'cheerio'
+import cheerioPackage from 'cheerio'
+const { default: $ } = cheerioPackage
+
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
diff --git a/bin/build-template-html.js b/bin/build-template-html.js
index fc4347d6..46e089a3 100644
--- a/bin/build-template-html.js
+++ b/bin/build-template-html.js
@@ -2,15 +2,18 @@ import chokidar from 'chokidar'
import fs from 'fs'
import path from 'path'
import { promisify } from 'util'
-import { buildSass } from './build-sass'
-import { buildInlineScript } from './build-inline-script'
-import { buildSvg } from './build-svg'
+import { buildSass } from './build-sass.js'
+import { buildInlineScript } from './build-inline-script.js'
+import { buildSvg } from './build-svg.js'
import { performance } from 'perf_hooks'
-import debounce from 'lodash-es/debounce'
-import applyIntl from '../webpack/svelte-intl-loader'
-import { LOCALE } from '../src/routes/_static/intl'
-import { getLangDir } from 'rtl-detect'
+import { debounce } from '../src/routes/_thirdparty/lodash/timers.js'
+import applyIntl from '../webpack/svelte-intl-loader.js'
+import { LOCALE } from '../src/routes/_static/intl.js'
+import rtlDetectPackage from 'rtl-detect'
+const { getLangDir } = rtlDetectPackage
+
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const writeFile = promisify(fs.writeFile)
const LOCALE_DIRECTION = getLangDir(LOCALE)
const DEBOUNCE = 500
diff --git a/bin/build-vercel-json.js b/bin/build-vercel-json.js
index 6908cac1..02aeeb69 100644
--- a/bin/build-vercel-json.js
+++ b/bin/build-vercel-json.js
@@ -5,11 +5,12 @@
import path from 'path'
import fs from 'fs'
import { promisify } from 'util'
-import { routes } from '../__sapper__/service-worker'
-import cloneDeep from 'lodash-es/cloneDeep'
-import inlineScriptChecksum from '../src/inline-script/checksum'
-import { sapperInlineScriptChecksums } from '../src/server/sapperInlineScriptChecksums'
+import { routes } from '../__sapper__/service-worker.js'
+import { cloneDeep } from '../src/routes/_utils/lodash-lite.js'
+import inlineScriptChecksum from '../src/inline-script/checksum.js'
+import { sapperInlineScriptChecksums } from '../src/server/sapperInlineScriptChecksums.js'
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const writeFile = promisify(fs.writeFile)
const JSON_TEMPLATE = {
diff --git a/bin/clone-mastodon.js b/bin/clone-mastodon.js
index 3d98c880..0de52087 100644
--- a/bin/clone-mastodon.js
+++ b/bin/clone-mastodon.js
@@ -2,11 +2,13 @@ import { promisify } from 'util'
import childProcessPromise from 'child-process-promise'
import path from 'path'
import fs from 'fs'
-import { envFile, RUBY_VERSION } from './mastodon-config'
+import { envFile, RUBY_VERSION } from './mastodon-config.js'
+import esMain from 'es-main'
const exec = childProcessPromise.exec
const stat = promisify(fs.stat)
const writeFile = promisify(fs.writeFile)
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const dir = __dirname
const GIT_URL = 'https://github.com/tootsuite/mastodon.git'
@@ -25,7 +27,7 @@ export default async function cloneMastodon () {
}
}
-if (require.main === module) {
+if (esMain(import.meta)) {
cloneMastodon().catch(err => {
console.error(err)
process.exit(1)
diff --git a/bin/getIntl.js b/bin/getIntl.js
index 519c075e..c9ad2649 100644
--- a/bin/getIntl.js
+++ b/bin/getIntl.js
@@ -1,9 +1,19 @@
-import { get } from 'lodash-es'
-import { DEFAULT_LOCALE, LOCALE } from '../src/routes/_static/intl'
-import path from 'path'
+import { get } from '../src/routes/_utils/lodash-lite.js'
+import { DEFAULT_LOCALE, LOCALE } from '../src/routes/_static/intl.js'
-const intl = require(path.join(__dirname, '../src/intl', LOCALE + '.js')).default
-const defaultIntl = require(path.join(__dirname, '../src/intl', DEFAULT_LOCALE + '.js')).default
+import enUS from '../src/intl/en-US.js'
+import fr from '../src/intl/fr.js'
+import de from '../src/intl/de.js'
+
+// TODO: make it so we don't have to explicitly list these out
+const locales = {
+ 'en-US': enUS,
+ fr,
+ de
+}
+
+const intl = locales[LOCALE]
+const defaultIntl = locales[DEFAULT_LOCALE]
export function warningOrError (message) { // avoid crashing the whole server on `yarn dev`
if (process.env.NODE_ENV === 'production') {
@@ -14,6 +24,7 @@ export function warningOrError (message) { // avoid crashing the whole server on
}
export function getIntl (path) {
+ path = path.split('.')
const res = get(intl, path, get(defaultIntl, path))
if (typeof res !== 'string') {
return warningOrError('Unknown intl string: ' + JSON.stringify(path))
diff --git a/bin/install-mastodon.js b/bin/install-mastodon.js
index 856a7221..8dd0519e 100644
--- a/bin/install-mastodon.js
+++ b/bin/install-mastodon.js
@@ -2,12 +2,14 @@ import { promisify } from 'util'
import childProcessPromise from 'child-process-promise'
import path from 'path'
import fs from 'fs'
-import { DB_NAME, DB_PASS, DB_USER, mastodonDir, env } from './mastodon-config'
+import { DB_NAME, DB_PASS, DB_USER, mastodonDir, env } from './mastodon-config.js'
import mkdirp from 'mkdirp'
+import esMain from 'es-main'
const exec = childProcessPromise.exec
const stat = promisify(fs.stat)
const writeFile = promisify(fs.writeFile)
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
const dir = __dirname
async function setupMastodonDatabase () {
@@ -69,7 +71,7 @@ export default async function installMastodon () {
await installMastodonDependencies()
}
-if (require.main === module) {
+if (esMain(import.meta)) {
installMastodon().catch(err => {
console.error(err)
process.exit(1)
diff --git a/bin/mastodon-config.js b/bin/mastodon-config.js
index 88ba0b17..bbbf7718 100644
--- a/bin/mastodon-config.js
+++ b/bin/mastodon-config.js
@@ -17,10 +17,9 @@ DB_NAME=${DB_NAME}
DB_PASS=${DB_PASS}
`
-// Need a Ruby version that CircleCI bundles with Node v12, not Node v14 which doesn't
-// work for streaming
-export const RUBY_VERSION = '2.6.6'
+export const RUBY_VERSION = '2.7.2'
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
export const mastodonDir = path.join(__dirname, '../mastodon')
export const env = Object.assign({}, process.env, {
diff --git a/bin/mastodon-data.js b/bin/mastodon-data.js
index fc2337a8..38707ddf 100644
--- a/bin/mastodon-data.js
+++ b/bin/mastodon-data.js
@@ -1,4 +1,4 @@
-import times from 'lodash-es/times'
+import { times } from '../src/routes/_utils/lodash-lite.js'
function unrollThread (user, prefix, privacy, thread) {
const res = []
diff --git a/bin/restore-mastodon-data.js b/bin/restore-mastodon-data.js
index 41159d91..c519cb13 100644
--- a/bin/restore-mastodon-data.js
+++ b/bin/restore-mastodon-data.js
@@ -1,13 +1,13 @@
-import { actions } from './mastodon-data'
-import { users } from '../tests/users'
-import { postStatus } from '../src/routes/_api/statuses'
-import { followAccount } from '../src/routes/_api/follow'
-import { favoriteStatus } from '../src/routes/_api/favorite'
-import { reblogStatus } from '../src/routes/_api/reblog'
+import { actions } from './mastodon-data.js'
+import { users } from '../tests/users.js'
+import { postStatus } from '../src/routes/_api/statuses.js'
+import { followAccount } from '../src/routes/_api/follow.js'
+import { favoriteStatus } from '../src/routes/_api/favorite.js'
+import { reblogStatus } from '../src/routes/_api/reblog.js'
import fetch from 'node-fetch'
import FileApi from 'file-api'
-import { pinStatus } from '../src/routes/_api/pin'
-import { submitMedia } from '../tests/submitMedia'
+import { pinStatus } from '../src/routes/_api/pin.js'
+import { submitMedia } from '../tests/submitMedia.js'
global.File = FileApi.File
global.FormData = FileApi.FormData
diff --git a/bin/run-mastodon.js b/bin/run-mastodon.js
index 55b4b802..08f7d11f 100644
--- a/bin/run-mastodon.js
+++ b/bin/run-mastodon.js
@@ -1,10 +1,10 @@
-import { restoreMastodonData } from './restore-mastodon-data'
+import { restoreMastodonData } from './restore-mastodon-data.js'
import childProcessPromise from 'child-process-promise'
import fs from 'fs'
-import { waitForMastodonUiToStart, waitForMastodonApiToStart } from './wait-for-mastodon-to-start'
-import cloneMastodon from './clone-mastodon'
-import installMastodon from './install-mastodon'
-import { mastodonDir, env } from './mastodon-config'
+import { waitForMastodonUiToStart, waitForMastodonApiToStart } from './wait-for-mastodon-to-start.js'
+import cloneMastodon from './clone-mastodon.js'
+import installMastodon from './install-mastodon.js'
+import { mastodonDir, env } from './mastodon-config.js'
const spawn = childProcessPromise.spawn
diff --git a/bin/svgs.js b/bin/svgs.js
index 683ebeef..0409f0fb 100644
--- a/bin/svgs.js
+++ b/bin/svgs.js
@@ -1,4 +1,4 @@
-module.exports = [
+export default [
{ id: 'pinafore-logo', src: 'src/static/sailboat.svg', inline: true },
{ id: 'fa-bell', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bell.svg', inline: true },
{ id: 'fa-users', src: 'src/thirdparty/font-awesome-svg-png/white/svg/users.svg', inline: true },
diff --git a/bin/terserOptions.js b/bin/terserOptions.js
index d8495f8d..9505a55e 100644
--- a/bin/terserOptions.js
+++ b/bin/terserOptions.js
@@ -1,4 +1,4 @@
-module.exports = {
+export default {
ecma: 8,
mangle: true,
compress: {
diff --git a/bin/wait-for-mastodon-data.js b/bin/wait-for-mastodon-data.js
index a332a7cf..8ed22d70 100644
--- a/bin/wait-for-mastodon-data.js
+++ b/bin/wait-for-mastodon-data.js
@@ -1,5 +1,6 @@
import fetch from 'node-fetch'
-import { actions } from './mastodon-data'
+import { actions } from './mastodon-data.js'
+import esMain from 'es-main'
const numStatuses = actions
.map(_ => _.post || _.boost)
@@ -26,7 +27,7 @@ async function waitForMastodonData () {
console.log('Mastodon data populated')
}
-if (require.main === module) {
+if (esMain(import.meta)) {
waitForMastodonData().catch(err => {
console.error(err)
process.exit(1)
diff --git a/bin/wait-for-mastodon-to-start.js b/bin/wait-for-mastodon-to-start.js
index d0260c45..b9c460ee 100644
--- a/bin/wait-for-mastodon-to-start.js
+++ b/bin/wait-for-mastodon-to-start.js
@@ -1,4 +1,5 @@
import fetch from 'node-fetch'
+import esMain from 'es-main'
export async function waitForMastodonUiToStart () {
while (true) {
@@ -30,7 +31,7 @@ export async function waitForMastodonApiToStart () {
console.log('Mastodon API started up')
}
-if (require.main === module) {
+if (esMain(import.meta)) {
Promise.resolve()
.then(waitForMastodonApiToStart)
.then(waitForMastodonUiToStart).catch(err => {
diff --git a/package.json b/package.json
index cf06c3ab..f688ad7c 100644
--- a/package.json
+++ b/package.json
@@ -2,24 +2,29 @@
"name": "pinafore",
"description": "Alternative web client for Mastodon",
"version": "1.24.5",
+ "type": "module",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
"scripts": {
"lint": "standard && standard --plugin html 'src/routes/**/*.html'",
"lint-fix": "standard --fix && standard --fix --plugin html 'src/routes/**/*.html'",
- "dev": "run-s build-template-html build-assets serve-dev",
+ "dev": "run-s before-build serve-dev",
"serve-dev": "run-p --race build-template-html-watch sapper-dev",
- "sapper-dev": "cross-env NODE_ENV=development PORT=4002 node -r esm ./node_modules/sapper/sapper dev",
- "before-build": "run-s build-template-html build-assets",
+ "sapper-dev": "cross-env NODE_ENV=development PORT=4002 WEBPACK_CONFIG_FILE=webpack/webpack.config.cjs SERVER_FILE_EXT=cjs node ./node_modules/sapper/sapper dev",
+ "before-build": "run-p build-template-html build-assets build-webpack-config",
"build": "cross-env NODE_ENV=production run-s build-steps",
"build-steps": "run-s before-build sapper-export build-vercel-json",
- "sapper-build": "node -r esm ./node_modules/sapper/sapper build",
+ "sapper-build": "cross-env WEBPACK_CONFIG_FILE=webpack/webpack.config.cjs SERVER_FILE_EXT=cjs node ./node_modules/sapper/sapper build",
"start": "node server.js",
"build-and-start": "run-s build start",
- "build-template-html": "node -r esm ./bin/build-template-html.js",
- "build-template-html-watch": "node -r esm ./bin/build-template-html.js --watch",
- "build-assets": "node -r esm ./bin/build-assets.js",
- "clone-mastodon": "node -r esm ./bin/clone-mastodon.js",
- "install-mastodon": "node -r esm ./bin/install-mastodon.js",
- "run-mastodon": "node -r esm ./bin/run-mastodon.js",
+ "build-template-html": "node ./bin/build-template-html.js",
+ "build-template-html-watch": "node ./bin/build-template-html.js --watch",
+ "build-assets": "node ./bin/build-assets.js",
+ "build-webpack-config": "rollup -c ./webpack/rollup.config.js",
+ "clone-mastodon": "node ./bin/clone-mastodon.js",
+ "install-mastodon": "node ./bin/install-mastodon.js",
+ "run-mastodon": "node ./bin/run-mastodon.js",
"test": "cross-env BROWSER=chrome:headless run-s test-browser",
"test-browser": "run-p --race run-mastodon build-and-start test-mastodon",
"test-mastodon": "run-s wait-for-mastodon-to-start wait-for-mastodon-data testcafe",
@@ -28,25 +33,27 @@
"testcafe": "run-s testcafe-suite0 testcafe-suite1",
"testcafe-suite0": "cross-env-shell testcafe -c 2 $BROWSER tests/spec/0*",
"testcafe-suite1": "cross-env-shell testcafe $BROWSER tests/spec/1*",
- "test-unit": "NODE_ENV=test mocha -r esm -r bin/browser-shim.js tests/unit/",
+ "test-unit": "NODE_ENV=test mocha -r bin/browser-shim.js tests/unit/",
"test-in-ci-suite0": "cross-env BROWSER=chrome:headless run-p --race run-mastodon start test-mastodon-suite0",
"test-in-ci-suite1": "cross-env BROWSER=chrome:headless run-p --race run-mastodon start test-mastodon-suite1",
- "wait-for-mastodon-to-start": "node -r esm bin/wait-for-mastodon-to-start.js",
- "wait-for-mastodon-data": "node -r esm bin/wait-for-mastodon-data.js",
+ "wait-for-mastodon-to-start": "node bin/wait-for-mastodon-to-start.js",
+ "wait-for-mastodon-data": "node bin/wait-for-mastodon-data.js",
"backup-mastodon-data": "./bin/backup-mastodon-data.sh",
- "sapper-export": "cross-env PORT=22939 node -r esm ./node_modules/sapper/sapper export",
+ "sapper-export": "cross-env PORT=22939 WEBPACK_CONFIG_FILE=webpack/webpack.config.cjs SERVER_FILE_EXT=cjs node ./node_modules/sapper/sapper export",
"print-export-info": "node ./bin/print-export-info.js",
"export-steps": "run-s before-build sapper-export print-export-info",
"export": "cross-env NODE_ENV=production run-s export-steps",
"now-build": "run-s export",
- "build-vercel-json": "node -r esm bin/build-vercel-json.js"
+ "build-vercel-json": "node bin/build-vercel-json.js"
},
"dependencies": {
"@formatjs/intl-listformat": "^6.2.6",
"@formatjs/intl-locale": "^2.4.33",
"@formatjs/intl-pluralrules": "^4.0.28",
"@formatjs/intl-relativetimeformat": "^9.1.7",
+ "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-replace": "^2.4.2",
+ "@stdlib/utils-noop": "^0.0.6",
"arrow-key-navigation": "^1.2.0",
"blurhash": "^1.1.3",
"cheerio": "1.0.0-rc.10",
@@ -60,6 +67,7 @@
"emoji-picker-element-data": "^1.2.0",
"emoji-regex": "^9.2.2",
"encoding": "^0.1.13",
+ "es-main": "^1.0.2",
"escape-html": "^1.0.3",
"esm": "^3.2.25",
"events-light": "^1.0.5",
@@ -75,8 +83,6 @@
"is-emoji-supported": "^0.0.5",
"li": "^1.3.0",
"localstorage-memory": "^1.0.3",
- "lodash-es": "4.17.15",
- "lodash-webpack-plugin": "^0.11.6",
"mkdirp": "^1.0.4",
"node-fetch": "^2.6.1",
"npm-run-all": "^4.1.5",
@@ -90,7 +96,7 @@
"rollup-plugin-terser": "^7.0.2",
"rtl-detect": "^1.0.3",
"safari-14-idb-fix": "^1.0.3",
- "sapper": "nolanlawson/sapper#for-pinafore-25",
+ "sapper": "nolanlawson/sapper#for-pinafore-26",
"sass": "^1.35.1",
"stringz": "^2.1.0",
"svelte": "^2.16.1",
@@ -118,10 +124,10 @@
"standard": "^16.0.3",
"testcafe": "^1.15.0-rc.3"
},
- "engines": {
- "node": ">= 8"
- },
"standard": {
+ "ignore": [
+ "webpack/*.cjs"
+ ],
"globals": [
"AbortController",
"Blob",
diff --git a/server.js b/server.js
index 1315be22..18f4d52c 100755
--- a/server.js
+++ b/server.js
@@ -1,9 +1,16 @@
#!/usr/bin/env node
+import fs from 'fs'
+import path from 'path'
+import express from 'express'
+import compression from 'compression'
-const path = require('path')
-const express = require('express')
-const compression = require('compression')
-const { routes: rawRoutes } = require('./vercel.json')
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
+
+// JSON files not supported in ESM yet
+// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-import-json
+const vercelJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'vercel.json'), 'utf8'))
+
+const { routes: rawRoutes } = vercelJson
const { PORT = 4002 } = process.env
const app = express()
diff --git a/src/client.js b/src/client.js
index 1a9b83df..02b36601 100644
--- a/src/client.js
+++ b/src/client.js
@@ -1,12 +1,12 @@
import * as sapper from '../__sapper__/client.js'
-import './routes/_utils/serviceWorkerClient'
-import './routes/_utils/historyEvents'
-import './routes/_utils/loadingMask'
-import './routes/_utils/forceOnline'
-import { mark, stop } from './routes/_utils/marks'
-import { loadPolyfills } from './routes/_utils/polyfills/loadPolyfills'
-import { loadNonCriticalPolyfills } from './routes/_utils/polyfills/loadNonCriticalPolyfills'
-import { idbReady } from './routes/_utils/idbReady'
+import './routes/_utils/serviceWorkerClient.js'
+import './routes/_utils/historyEvents.js'
+import './routes/_utils/loadingMask.js'
+import './routes/_utils/forceOnline.js'
+import { mark, stop } from './routes/_utils/marks.js'
+import { loadPolyfills } from './routes/_utils/polyfills/loadPolyfills.js'
+import { loadNonCriticalPolyfills } from './routes/_utils/polyfills/loadNonCriticalPolyfills.js'
+import { idbReady } from './routes/_utils/idbReady.js'
Promise.all([idbReady(), loadPolyfills()]).then(() => {
mark('sapperStart')
@@ -17,6 +17,6 @@ Promise.all([idbReady(), loadPolyfills()]).then(() => {
console.log('process.env.NODE_ENV', process.env.NODE_ENV)
-if (module.hot) {
- module.hot.accept()
+if (import.meta.webpackHot) {
+ import.meta.webpackHot.accept()
}
diff --git a/src/inline-script/inline-script.js b/src/inline-script/inline-script.js
index 32b7b3e4..8d6f914c 100644
--- a/src/inline-script/inline-script.js
+++ b/src/inline-script/inline-script.js
@@ -3,12 +3,12 @@
// To allow CSP to work correctly, we also calculate a sha256 hash during
// the build process and write it to checksum.js.
-import { INLINE_THEME, DEFAULT_THEME, switchToTheme } from '../routes/_utils/themeEngine'
-import { basename } from '../routes/_api/utils'
-import { onUserIsLoggedOut } from '../routes/_actions/onUserIsLoggedOut'
-import { storeLite } from '../routes/_store/storeLite'
-import { isIOSPre12Point2 } from '../routes/_utils/userAgent/isIOSPre12Point2'
-import { isMac } from '../routes/_utils/userAgent/isMac'
+import { INLINE_THEME, DEFAULT_THEME, switchToTheme } from '../routes/_utils/themeEngine.js'
+import { basename } from '../routes/_api/utils.js'
+import { onUserIsLoggedOut } from '../routes/_actions/onUserIsLoggedOut.js'
+import { storeLite } from '../routes/_store/storeLite.js'
+import { isIOSPre12Point2 } from '../routes/_utils/userAgent/isIOSPre12Point2.js'
+import { isMac } from '../routes/_utils/userAgent/isMac.js'
window.__themeColors = process.env.THEME_COLORS
diff --git a/src/routes/_a11y/getAccessibleLabelForStatus.js b/src/routes/_a11y/getAccessibleLabelForStatus.js
index 62b8e5f8..18a4e574 100644
--- a/src/routes/_a11y/getAccessibleLabelForStatus.js
+++ b/src/routes/_a11y/getAccessibleLabelForStatus.js
@@ -1,6 +1,6 @@
-import { getAccountAccessibleName } from './getAccountAccessibleName'
-import { POST_PRIVACY_OPTIONS } from '../_static/statuses'
-import { formatIntl } from '../_utils/formatIntl'
+import { getAccountAccessibleName } from './getAccountAccessibleName.js'
+import { POST_PRIVACY_OPTIONS } from '../_static/statuses.js'
+import { formatIntl } from '../_utils/formatIntl.js'
function getNotificationText (notification, omitEmojiInDisplayNames) {
if (!notification) {
diff --git a/src/routes/_a11y/getAccountAccessibleName.js b/src/routes/_a11y/getAccountAccessibleName.js
index 81a30a1b..54e6685b 100644
--- a/src/routes/_a11y/getAccountAccessibleName.js
+++ b/src/routes/_a11y/getAccountAccessibleName.js
@@ -1,4 +1,4 @@
-import { removeEmoji } from '../_utils/removeEmoji'
+import { removeEmoji } from '../_utils/removeEmoji.js'
export function getAccountAccessibleName (account, omitEmojiInDisplayNames) {
const emojis = account.emojis
diff --git a/src/routes/_actions/accounts.js b/src/routes/_actions/accounts.js
index f9c50402..835c2ad9 100644
--- a/src/routes/_actions/accounts.js
+++ b/src/routes/_actions/accounts.js
@@ -1,7 +1,7 @@
-import { getAccount } from '../_api/user'
-import { getRelationship } from '../_api/relationships'
-import { database } from '../_database/database'
-import { store } from '../_store/store'
+import { getAccount } from '../_api/user.js'
+import { getRelationship } from '../_api/relationships.js'
+import { database } from '../_database/database.js'
+import { store } from '../_store/store.js'
async function _updateAccount (accountId, instanceName, accessToken) {
const localPromise = database.getAccount(instanceName, accountId)
diff --git a/src/routes/_actions/addInstance.js b/src/routes/_actions/addInstance.js
index ce5f6678..e3efdc9a 100644
--- a/src/routes/_actions/addInstance.js
+++ b/src/routes/_actions/addInstance.js
@@ -1,12 +1,12 @@
-import { getAccessTokenFromAuthCode, registerApplication, generateAuthLink } from '../_api/oauth'
-import { getInstanceInfo } from '../_api/instance'
-import { goto } from '../../../__sapper__/client'
-import { DEFAULT_THEME, switchToTheme } from '../_utils/themeEngine'
-import { store } from '../_store/store'
-import { updateVerifyCredentialsForInstance } from './instances'
-import { updateCustomEmojiForInstance } from './emoji'
-import { database } from '../_database/database'
-import { DOMAIN_BLOCKS } from '../_static/blocks'
+import { getAccessTokenFromAuthCode, registerApplication, generateAuthLink } from '../_api/oauth.js'
+import { getInstanceInfo } from '../_api/instance.js'
+import { goto } from '../../../__sapper__/client.js'
+import { DEFAULT_THEME, switchToTheme } from '../_utils/themeEngine.js'
+import { store } from '../_store/store.js'
+import { updateVerifyCredentialsForInstance } from './instances.js'
+import { updateCustomEmojiForInstance } from './emoji.js'
+import { database } from '../_database/database.js'
+import { DOMAIN_BLOCKS } from '../_static/blocks.js'
const GENERIC_ERROR = `
Is this a valid Mastodon instance? Is a browser extension
diff --git a/src/routes/_actions/addStatusOrNotification.js b/src/routes/_actions/addStatusOrNotification.js
index 76eb558c..2e772dbc 100644
--- a/src/routes/_actions/addStatusOrNotification.js
+++ b/src/routes/_actions/addStatusOrNotification.js
@@ -1,11 +1,10 @@
-import { mark, stop } from '../_utils/marks'
-import { store } from '../_store/store'
-import uniqBy from 'lodash-es/uniqBy'
-import isEqual from 'lodash-es/isEqual'
-import { database } from '../_database/database'
-import { concat } from '../_utils/arrays'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import { timelineItemToSummary } from '../_utils/timelineItemToSummary'
+import { mark, stop } from '../_utils/marks.js'
+import { store } from '../_store/store.js'
+import { uniqBy, isEqual } from '../_thirdparty/lodash/objects.js'
+import { database } from '../_database/database.js'
+import { concat } from '../_utils/arrays.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import { timelineItemToSummary } from '../_utils/timelineItemToSummary.js'
function getExistingItemIdsSet (instanceName, timelineName) {
const timelineItemSummaries = store.getForTimeline(instanceName, timelineName, 'timelineItemSummaries') || []
diff --git a/src/routes/_actions/autosuggest.js b/src/routes/_actions/autosuggest.js
index d4181e37..5a0951dc 100644
--- a/src/routes/_actions/autosuggest.js
+++ b/src/routes/_actions/autosuggest.js
@@ -1,4 +1,4 @@
-import { store } from '../_store/store'
+import { store } from '../_store/store.js'
const emojiMapper = emoji => emoji.unicode ? emoji.unicode : `:${emoji.shortcodes[0]}:`
const hashtagMapper = hashtag => `#${hashtag.name}`
diff --git a/src/routes/_actions/autosuggestAccountSearch.js b/src/routes/_actions/autosuggestAccountSearch.js
index c848d826..8eecb19b 100644
--- a/src/routes/_actions/autosuggestAccountSearch.js
+++ b/src/routes/_actions/autosuggestAccountSearch.js
@@ -1,11 +1,11 @@
-import { database } from '../_database/database'
-import { store } from '../_store/store'
-import { search } from '../_api/search'
-import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
-import { concat } from '../_utils/arrays'
-import uniqBy from 'lodash-es/uniqBy'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import { RequestThrottler } from '../_utils/RequestThrottler'
+import { database } from '../_database/database.js'
+import { store } from '../_store/store.js'
+import { search } from '../_api/search.js'
+import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest.js'
+import { concat } from '../_utils/arrays.js'
+import { uniqBy } from '../_thirdparty/lodash/objects.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import { RequestThrottler } from '../_utils/RequestThrottler.js'
const DATABASE_SEARCH_RESULTS_LIMIT = 30
diff --git a/src/routes/_actions/autosuggestEmojiSearch.js b/src/routes/_actions/autosuggestEmojiSearch.js
index 81928932..853a5410 100644
--- a/src/routes/_actions/autosuggestEmojiSearch.js
+++ b/src/routes/_actions/autosuggestEmojiSearch.js
@@ -1,9 +1,9 @@
-import { store } from '../_store/store'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import * as emojiDatabase from '../_utils/emojiDatabase'
-import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
-import { testEmojiSupported } from '../_utils/testEmojiSupported'
-import { mark, stop } from '../_utils/marks'
+import { store } from '../_store/store.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import * as emojiDatabase from '../_utils/emojiDatabase.js'
+import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest.js'
+import { testEmojiSupported } from '../_utils/testEmojiSupported.js'
+import { mark, stop } from '../_utils/marks.js'
async function searchEmoji (searchText) {
let emojis = await emojiDatabase.findBySearchQuery(searchText)
diff --git a/src/routes/_actions/autosuggestHashtagSearch.js b/src/routes/_actions/autosuggestHashtagSearch.js
index 94576c4b..6b0847bc 100644
--- a/src/routes/_actions/autosuggestHashtagSearch.js
+++ b/src/routes/_actions/autosuggestHashtagSearch.js
@@ -1,9 +1,9 @@
-import { search } from '../_api/search'
-import { store } from '../_store/store'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest'
-import { RequestThrottler } from '../_utils/RequestThrottler'
-import { sum } from '../_utils/lodash-lite'
+import { search } from '../_api/search.js'
+import { store } from '../_store/store.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import { SEARCH_RESULTS_LIMIT } from '../_static/autosuggest.js'
+import { RequestThrottler } from '../_utils/RequestThrottler.js'
+import { sum } from '../_utils/lodash-lite.js'
const HASHTAG_SEARCH_LIMIT = 10
diff --git a/src/routes/_actions/block.js b/src/routes/_actions/block.js
index 10409f61..7f53f654 100644
--- a/src/routes/_actions/block.js
+++ b/src/routes/_actions/block.js
@@ -1,9 +1,9 @@
-import { store } from '../_store/store'
-import { blockAccount, unblockAccount } from '../_api/block'
-import { toast } from '../_components/toast/toast'
-import { updateLocalRelationship } from './accounts'
-import { emit } from '../_utils/eventBus'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { blockAccount, unblockAccount } from '../_api/block.js'
+import { toast } from '../_components/toast/toast.js'
+import { updateLocalRelationship } from './accounts.js'
+import { emit } from '../_utils/eventBus.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setAccountBlocked (accountId, block, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/bookmark.js b/src/routes/_actions/bookmark.js
index 3759c03c..109d27f7 100644
--- a/src/routes/_actions/bookmark.js
+++ b/src/routes/_actions/bookmark.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { bookmarkStatus, unbookmarkStatus } from '../_api/bookmark'
-import { database } from '../_database/database'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { bookmarkStatus, unbookmarkStatus } from '../_api/bookmark.js'
+import { database } from '../_database/database.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/compose.js b/src/routes/_actions/compose.js
index 74842d1b..d2451555 100644
--- a/src/routes/_actions/compose.js
+++ b/src/routes/_actions/compose.js
@@ -1,13 +1,13 @@
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { postStatus as postStatusToServer } from '../_api/statuses'
-import { addStatusOrNotification } from './addStatusOrNotification'
-import { database } from '../_database/database'
-import { emit } from '../_utils/eventBus'
-import { putMediaMetadata } from '../_api/media'
-import uniqBy from 'lodash-es/uniqBy'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { postStatus as postStatusToServer } from '../_api/statuses.js'
+import { addStatusOrNotification } from './addStatusOrNotification.js'
+import { database } from '../_database/database.js'
+import { emit } from '../_utils/eventBus.js'
+import { putMediaMetadata } from '../_api/media.js'
+import { uniqBy } from '../_thirdparty/lodash/objects.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function insertHandleForReply (statusId) {
const { currentInstance } = store.get()
diff --git a/src/routes/_actions/composePoll.js b/src/routes/_actions/composePoll.js
index 020fe5a0..bfb9381e 100644
--- a/src/routes/_actions/composePoll.js
+++ b/src/routes/_actions/composePoll.js
@@ -1,4 +1,4 @@
-import { store } from '../_store/store'
+import { store } from '../_store/store.js'
export function enablePoll (realm) {
store.setComposeData(realm, {
diff --git a/src/routes/_actions/contentWarnings.js b/src/routes/_actions/contentWarnings.js
index f8463597..064d70f7 100644
--- a/src/routes/_actions/contentWarnings.js
+++ b/src/routes/_actions/contentWarnings.js
@@ -1,4 +1,4 @@
-import { store } from '../_store/store'
+import { store } from '../_store/store.js'
export function toggleContentWarningShown (realm) {
const shown = store.getComposeData(realm, 'contentWarningShown')
diff --git a/src/routes/_actions/copyText.js b/src/routes/_actions/copyText.js
index 4b3fcb0e..18963b29 100644
--- a/src/routes/_actions/copyText.js
+++ b/src/routes/_actions/copyText.js
@@ -1,5 +1,5 @@
import { importShowCopyDialog } from '../_components/dialog/asyncDialogs/importShowCopyDialog.js'
-import { toast } from '../_components/toast/toast'
+import { toast } from '../_components/toast/toast.js'
export async function copyText (text) {
if (navigator.clipboard) { // not supported in all browsers
diff --git a/src/routes/_actions/createMakeProps.js b/src/routes/_actions/createMakeProps.js
index 7e372e07..e53ae926 100644
--- a/src/routes/_actions/createMakeProps.js
+++ b/src/routes/_actions/createMakeProps.js
@@ -1,9 +1,9 @@
-import { database } from '../_database/database'
-import { decode as decodeBlurhash, init as initBlurhash } from '../_utils/blurhash'
-import { mark, stop } from '../_utils/marks'
-import { get } from '../_utils/lodash-lite'
-import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
+import { database } from '../_database/database.js'
+import { decode as decodeBlurhash, init as initBlurhash } from '../_utils/blurhash.js'
+import { mark, stop } from '../_utils/marks.js'
+import { get } from '../_utils/lodash-lite.js'
+import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
async function getNotification (instanceName, timelineType, timelineValue, itemId) {
return {
diff --git a/src/routes/_actions/delete.js b/src/routes/_actions/delete.js
index 8e4a260a..c5d980ff 100644
--- a/src/routes/_actions/delete.js
+++ b/src/routes/_actions/delete.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { deleteStatus } from '../_api/delete'
-import { toast } from '../_components/toast/toast'
-import { deleteStatus as deleteStatusLocally } from './deleteStatuses'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { deleteStatus } from '../_api/delete.js'
+import { toast } from '../_components/toast/toast.js'
+import { deleteStatus as deleteStatusLocally } from './deleteStatuses.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function doDeleteStatus (statusId) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/deleteAndRedraft.js b/src/routes/_actions/deleteAndRedraft.js
index a2b4b7b4..d57c65bf 100644
--- a/src/routes/_actions/deleteAndRedraft.js
+++ b/src/routes/_actions/deleteAndRedraft.js
@@ -1,7 +1,7 @@
-import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText'
+import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText.js'
import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/importShowComposeDialog.js'
-import { doDeleteStatus } from './delete'
-import { store } from '../_store/store'
+import { doDeleteStatus } from './delete.js'
+import { store } from '../_store/store.js'
export async function deleteAndRedraft (status) {
const deleteStatusPromise = doDeleteStatus(status.id)
diff --git a/src/routes/_actions/deleteStatuses.js b/src/routes/_actions/deleteStatuses.js
index e6ed6efd..08f613be 100644
--- a/src/routes/_actions/deleteStatuses.js
+++ b/src/routes/_actions/deleteStatuses.js
@@ -1,8 +1,8 @@
-import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses'
-import { store } from '../_store/store'
-import isEqual from 'lodash-es/isEqual'
-import { database } from '../_database/database'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
+import { getIdsThatRebloggedThisStatus, getNotificationIdsForStatuses } from './statuses.js'
+import { store } from '../_store/store.js'
+import { isEqual } from '../_thirdparty/lodash/objects.js'
+import { database } from '../_database/database.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
function filterItemIdsFromTimelines (instanceName, timelineFilter, idFilter) {
const keys = ['timelineItemSummaries', 'timelineItemSummariesToAdd']
diff --git a/src/routes/_actions/doQuickLoginIfNecessary.js b/src/routes/_actions/doQuickLoginIfNecessary.js
index 8f297151..2df02c90 100644
--- a/src/routes/_actions/doQuickLoginIfNecessary.js
+++ b/src/routes/_actions/doQuickLoginIfNecessary.js
@@ -2,8 +2,8 @@
// Used in the integration tests. Can't see a problem with exposing this publicly
// since you would have to know the access token anyway.
-import { store } from '../_store/store'
-import { goto } from '../../../__sapper__/client'
+import { store } from '../_store/store.js'
+import { goto } from '../../../__sapper__/client.js'
export function doQuickLoginIfNecessary () {
const params = new URLSearchParams(location.search)
diff --git a/src/routes/_actions/emoji.js b/src/routes/_actions/emoji.js
index 81888f4a..a45d182a 100644
--- a/src/routes/_actions/emoji.js
+++ b/src/routes/_actions/emoji.js
@@ -1,11 +1,11 @@
import {
cacheFirstUpdateAfter,
cacheFirstUpdateOnlyIfNotInCache
-} from '../_utils/sync'
-import { database } from '../_database/database'
-import { getCustomEmoji } from '../_api/emoji'
-import { store } from '../_store/store'
-import isEqual from 'lodash-es/isEqual'
+} from '../_utils/sync.js'
+import { database } from '../_database/database.js'
+import { getCustomEmoji } from '../_api/emoji.js'
+import { store } from '../_store/store.js'
+import { isEqual } from '../_thirdparty/lodash/objects.js'
async function syncEmojiForInstance (instanceName, syncMethod) {
await syncMethod(
diff --git a/src/routes/_actions/favorite.js b/src/routes/_actions/favorite.js
index c8d43958..2291a18e 100644
--- a/src/routes/_actions/favorite.js
+++ b/src/routes/_actions/favorite.js
@@ -1,8 +1,8 @@
-import { favoriteStatus, unfavoriteStatus } from '../_api/favorite'
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { database } from '../_database/database'
-import { formatIntl } from '../_utils/formatIntl'
+import { favoriteStatus, unfavoriteStatus } from '../_api/favorite.js'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { database } from '../_database/database.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setFavorited (statusId, favorited) {
const { online } = store.get()
diff --git a/src/routes/_actions/filters.js b/src/routes/_actions/filters.js
index d59ca455..9f96f4c6 100644
--- a/src/routes/_actions/filters.js
+++ b/src/routes/_actions/filters.js
@@ -1,11 +1,11 @@
-import { store } from '../_store/store'
-import { createFilter, getFilters, updateFilter, deleteFilter as doDeleteFilter } from '../_api/filters'
-import { cacheFirstUpdateAfter, cacheFirstUpdateOnlyIfNotInCache } from '../_utils/sync'
-import { database } from '../_database/database'
-import { isEqual } from 'lodash-es'
-import { toast } from '../_components/toast/toast'
-import { formatIntl } from '../_utils/formatIntl'
-import { emit } from '../_utils/eventBus'
+import { store } from '../_store/store.js'
+import { createFilter, getFilters, updateFilter, deleteFilter as doDeleteFilter } from '../_api/filters.js'
+import { cacheFirstUpdateAfter, cacheFirstUpdateOnlyIfNotInCache } from '../_utils/sync.js'
+import { database } from '../_database/database.js'
+import { isEqual } from '../_thirdparty/lodash/objects.js'
+import { toast } from '../_components/toast/toast.js'
+import { formatIntl } from '../_utils/formatIntl.js'
+import { emit } from '../_utils/eventBus.js'
async function syncFilters (instanceName, syncMethod) {
const { loggedInInstances } = store.get()
diff --git a/src/routes/_actions/follow.js b/src/routes/_actions/follow.js
index e1eed3c0..59aa466a 100644
--- a/src/routes/_actions/follow.js
+++ b/src/routes/_actions/follow.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { followAccount, unfollowAccount } from '../_api/follow'
-import { toast } from '../_components/toast/toast'
-import { updateLocalRelationship } from './accounts'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { followAccount, unfollowAccount } from '../_api/follow.js'
+import { toast } from '../_components/toast/toast.js'
+import { updateLocalRelationship } from './accounts.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setAccountFollowed (accountId, follow, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/followRequests.js b/src/routes/_actions/followRequests.js
index b2edcab2..d65c83fc 100644
--- a/src/routes/_actions/followRequests.js
+++ b/src/routes/_actions/followRequests.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { cacheFirstUpdateAfter } from '../_utils/sync'
-import { database } from '../_database/database'
-import { getFollowRequests } from '../_api/followRequests'
-import { get } from '../_utils/lodash-lite'
+import { store } from '../_store/store.js'
+import { cacheFirstUpdateAfter } from '../_utils/sync.js'
+import { database } from '../_database/database.js'
+import { getFollowRequests } from '../_api/followRequests.js'
+import { get } from '../_utils/lodash-lite.js'
export async function updateFollowRequestCountIfLockedAccount (instanceName) {
const { verifyCredentials, loggedInInstances } = store.get()
diff --git a/src/routes/_actions/getRecentStatusesForAccount.js b/src/routes/_actions/getRecentStatusesForAccount.js
index 8c64a914..3feee6f7 100644
--- a/src/routes/_actions/getRecentStatusesForAccount.js
+++ b/src/routes/_actions/getRecentStatusesForAccount.js
@@ -1,5 +1,5 @@
-import { store } from '../_store/store'
-import { getTimeline } from '../_api/timelines'
+import { store } from '../_store/store.js'
+import { getTimeline } from '../_api/timelines.js'
export async function getRecentStatusesForAccount (accountId) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/goToSearch.js b/src/routes/_actions/goToSearch.js
index 264ac4fc..664618ca 100644
--- a/src/routes/_actions/goToSearch.js
+++ b/src/routes/_actions/goToSearch.js
@@ -1,6 +1,6 @@
-import { store } from '../_store/store'
-import { goto } from '../../../__sapper__/client'
-import { emit } from '../_utils/eventBus'
+import { store } from '../_store/store.js'
+import { goto } from '../../../__sapper__/client.js'
+import { emit } from '../_utils/eventBus.js'
// Go to the search page, and also focus the search input. For accessibility
// and usability reasons, this only happens on pressing these particular hotkeys.
diff --git a/src/routes/_actions/instances.js b/src/routes/_actions/instances.js
index a1812af8..a9f7df8a 100644
--- a/src/routes/_actions/instances.js
+++ b/src/routes/_actions/instances.js
@@ -1,13 +1,13 @@
-import { getVerifyCredentials } from '../_api/user'
-import { store } from '../_store/store'
-import { switchToTheme } from '../_utils/themeEngine'
-import { toast } from '../_components/toast/toast'
-import { goto } from '../../../__sapper__/client'
-import { cacheFirstUpdateAfter } from '../_utils/sync'
-import { getInstanceInfo } from '../_api/instance'
-import { database } from '../_database/database'
+import { getVerifyCredentials } from '../_api/user.js'
+import { store } from '../_store/store.js'
+import { switchToTheme } from '../_utils/themeEngine.js'
+import { toast } from '../_components/toast/toast.js'
+import { goto } from '../../../__sapper__/client.js'
+import { cacheFirstUpdateAfter } from '../_utils/sync.js'
+import { getInstanceInfo } from '../_api/instance.js'
+import { database } from '../_database/database.js'
import { importVirtualListStore } from '../_utils/asyncModules/importVirtualListStore.js'
-import { formatIntl } from '../_utils/formatIntl'
+import { formatIntl } from '../_utils/formatIntl.js'
export function changeTheme (instanceName, newTheme) {
const { instanceThemes } = store.get()
diff --git a/src/routes/_actions/lists.js b/src/routes/_actions/lists.js
index 96a04c82..7c7366d1 100644
--- a/src/routes/_actions/lists.js
+++ b/src/routes/_actions/lists.js
@@ -1,7 +1,7 @@
-import { store } from '../_store/store'
-import { getLists } from '../_api/lists'
-import { cacheFirstUpdateAfter, cacheFirstUpdateOnlyIfNotInCache } from '../_utils/sync'
-import { database } from '../_database/database'
+import { store } from '../_store/store.js'
+import { getLists } from '../_api/lists.js'
+import { cacheFirstUpdateAfter, cacheFirstUpdateOnlyIfNotInCache } from '../_utils/sync.js'
+import { database } from '../_database/database.js'
async function syncLists (instanceName, syncMethod) {
const { loggedInInstances } = store.get()
diff --git a/src/routes/_actions/media.js b/src/routes/_actions/media.js
index 48e3f363..56f5e324 100644
--- a/src/routes/_actions/media.js
+++ b/src/routes/_actions/media.js
@@ -1,9 +1,9 @@
-import { store } from '../_store/store'
-import { uploadMedia } from '../_api/media'
-import { toast } from '../_components/toast/toast'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import { formatIntl } from '../_utils/formatIntl'
-import { database } from '../_database/database'
+import { store } from '../_store/store.js'
+import { uploadMedia } from '../_api/media.js'
+import { toast } from '../_components/toast/toast.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import { formatIntl } from '../_utils/formatIntl.js'
+import { database } from '../_database/database.js'
export async function doMediaUpload (realm, file) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/mention.js b/src/routes/_actions/mention.js
index db70522b..ba4b41c5 100644
--- a/src/routes/_actions/mention.js
+++ b/src/routes/_actions/mention.js
@@ -1,5 +1,5 @@
import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/importShowComposeDialog.js'
-import { store } from '../_store/store'
+import { store } from '../_store/store.js'
export async function composeNewStatusMentioning (account) {
store.setComposeData('dialog', { text: `@${account.acct} ` })
diff --git a/src/routes/_actions/mute.js b/src/routes/_actions/mute.js
index d4bbe531..765ecd64 100644
--- a/src/routes/_actions/mute.js
+++ b/src/routes/_actions/mute.js
@@ -1,9 +1,9 @@
-import { store } from '../_store/store'
-import { muteAccount, unmuteAccount } from '../_api/mute'
-import { toast } from '../_components/toast/toast'
-import { updateLocalRelationship } from './accounts'
-import { emit } from '../_utils/eventBus'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { muteAccount, unmuteAccount } from '../_api/mute.js'
+import { toast } from '../_components/toast/toast.js'
+import { updateLocalRelationship } from './accounts.js'
+import { emit } from '../_utils/eventBus.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setAccountMuted (accountId, mute, notifications, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/muteConversation.js b/src/routes/_actions/muteConversation.js
index 2b3df5b2..c52da6f4 100644
--- a/src/routes/_actions/muteConversation.js
+++ b/src/routes/_actions/muteConversation.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { muteConversation, unmuteConversation } from '../_api/muteConversation'
-import { toast } from '../_components/toast/toast'
-import { database } from '../_database/database'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { muteConversation, unmuteConversation } from '../_api/muteConversation.js'
+import { toast } from '../_components/toast/toast.js'
+import { database } from '../_database/database.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setConversationMuted (statusId, mute, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/pin.js b/src/routes/_actions/pin.js
index 3d44afc0..3aba62f5 100644
--- a/src/routes/_actions/pin.js
+++ b/src/routes/_actions/pin.js
@@ -1,9 +1,9 @@
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { pinStatus, unpinStatus } from '../_api/pin'
-import { database } from '../_database/database'
-import { emit } from '../_utils/eventBus'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { pinStatus, unpinStatus } from '../_api/pin.js'
+import { database } from '../_database/database.js'
+import { emit } from '../_utils/eventBus.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setStatusPinnedOrUnpinned (statusId, pinned, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/pinnedStatuses.js b/src/routes/_actions/pinnedStatuses.js
index f38f5c58..7c4db7ab 100644
--- a/src/routes/_actions/pinnedStatuses.js
+++ b/src/routes/_actions/pinnedStatuses.js
@@ -1,9 +1,9 @@
-import { store } from '../_store/store'
-import { cacheFirstUpdateAfter } from '../_utils/sync'
-import { database } from '../_database/database'
+import { store } from '../_store/store.js'
+import { cacheFirstUpdateAfter } from '../_utils/sync.js'
+import { database } from '../_database/database.js'
import {
getPinnedStatuses
-} from '../_api/pinnedStatuses'
+} from '../_api/pinnedStatuses.js'
export async function updatePinnedStatusesForAccount (accountId) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/polls.js b/src/routes/_actions/polls.js
index aba6173a..a0fc6418 100644
--- a/src/routes/_actions/polls.js
+++ b/src/routes/_actions/polls.js
@@ -1,7 +1,7 @@
-import { getPoll as getPollApi, voteOnPoll as voteOnPollApi } from '../_api/polls'
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { formatIntl } from '../_utils/formatIntl'
+import { getPoll as getPollApi, voteOnPoll as voteOnPollApi } from '../_api/polls.js'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function getPoll (pollId) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/postPrivacy.js b/src/routes/_actions/postPrivacy.js
index a9468797..98db7ffa 100644
--- a/src/routes/_actions/postPrivacy.js
+++ b/src/routes/_actions/postPrivacy.js
@@ -1,5 +1,5 @@
-import { store } from '../_store/store'
+import { store } from '../_store/store.js'
export function setPostPrivacy (realm, postPrivacyKey) {
store.setComposeData(realm, { postPrivacy: postPrivacyKey })
diff --git a/src/routes/_actions/pushSubscription.js b/src/routes/_actions/pushSubscription.js
index f4b07631..28f4ee49 100644
--- a/src/routes/_actions/pushSubscription.js
+++ b/src/routes/_actions/pushSubscription.js
@@ -1,6 +1,6 @@
-import { getSubscription, deleteSubscription, postSubscription, putSubscription } from '../_api/pushSubscription'
-import { store } from '../_store/store'
-import { urlBase64ToUint8Array } from '../_utils/base64'
+import { getSubscription, deleteSubscription, postSubscription, putSubscription } from '../_api/pushSubscription.js'
+import { store } from '../_store/store.js'
+import { urlBase64ToUint8Array } from '../_utils/base64.js'
const dummyApplicationServerKey = 'BImgAz4cF_yvNFp8uoBJCaGpCX4d0atNIFMHfBvAAXCyrnn9IMAFQ10DW_ZvBCzGeR4fZI5FnEi2JVcRE-L88jY='
diff --git a/src/routes/_actions/reblog.js b/src/routes/_actions/reblog.js
index ba9a24d9..b2268b32 100644
--- a/src/routes/_actions/reblog.js
+++ b/src/routes/_actions/reblog.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { reblogStatus, unreblogStatus } from '../_api/reblog'
-import { database } from '../_database/database'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { reblogStatus, unreblogStatus } from '../_api/reblog.js'
+import { database } from '../_database/database.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setReblogged (statusId, reblogged) {
const online = store.get()
diff --git a/src/routes/_actions/reportStatuses.js b/src/routes/_actions/reportStatuses.js
index 67e6c76f..e3a4f288 100644
--- a/src/routes/_actions/reportStatuses.js
+++ b/src/routes/_actions/reportStatuses.js
@@ -1,7 +1,7 @@
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { report } from '../_api/report'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { report } from '../_api/report.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function reportStatuses (account, statusIds, comment, forward) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/requests.js b/src/routes/_actions/requests.js
index ad763ade..1697c827 100644
--- a/src/routes/_actions/requests.js
+++ b/src/routes/_actions/requests.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { approveFollowRequest, rejectFollowRequest } from '../_api/requests'
-import { emit } from '../_utils/eventBus'
-import { toast } from '../_components/toast/toast'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { approveFollowRequest, rejectFollowRequest } from '../_api/requests.js'
+import { emit } from '../_utils/eventBus.js'
+import { toast } from '../_components/toast/toast.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setFollowRequestApprovedOrRejected (accountId, approved, toastOnSuccess) {
const {
diff --git a/src/routes/_actions/search.js b/src/routes/_actions/search.js
index 43b057ad..3de3a52c 100644
--- a/src/routes/_actions/search.js
+++ b/src/routes/_actions/search.js
@@ -1,7 +1,7 @@
-import { store } from '../_store/store'
-import { toast } from '../_components/toast/toast'
-import { search } from '../_api/search'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { toast } from '../_components/toast/toast.js'
+import { search } from '../_api/search.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function doSearch () {
const { currentInstance, accessToken, queryInSearch } = store.get()
diff --git a/src/routes/_actions/setDomainBlocked.js b/src/routes/_actions/setDomainBlocked.js
index ff5ac0ef..970bd4cd 100644
--- a/src/routes/_actions/setDomainBlocked.js
+++ b/src/routes/_actions/setDomainBlocked.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { blockDomain, unblockDomain } from '../_api/blockDomain'
-import { toast } from '../_components/toast/toast'
-import { updateRelationship } from './accounts'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { blockDomain, unblockDomain } from '../_api/blockDomain.js'
+import { toast } from '../_components/toast/toast.js'
+import { updateRelationship } from './accounts.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setDomainBlocked (accountId, domain, block, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/setShowReblogs.js b/src/routes/_actions/setShowReblogs.js
index 9f00afa1..aab36801 100644
--- a/src/routes/_actions/setShowReblogs.js
+++ b/src/routes/_actions/setShowReblogs.js
@@ -1,8 +1,8 @@
-import { store } from '../_store/store'
-import { setShowReblogs as setShowReblogsApi } from '../_api/showReblogs'
-import { toast } from '../_components/toast/toast'
-import { updateLocalRelationship } from './accounts'
-import { formatIntl } from '../_utils/formatIntl'
+import { store } from '../_store/store.js'
+import { setShowReblogs as setShowReblogsApi } from '../_api/showReblogs.js'
+import { toast } from '../_components/toast/toast.js'
+import { updateLocalRelationship } from './accounts.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function setShowReblogs (accountId, showReblogs, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
diff --git a/src/routes/_actions/share.js b/src/routes/_actions/share.js
index 38cbdbfe..8e3a6d77 100644
--- a/src/routes/_actions/share.js
+++ b/src/routes/_actions/share.js
@@ -1,6 +1,6 @@
-import { toast } from '../_components/toast/toast'
-import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText'
-import { formatIntl } from '../_utils/formatIntl'
+import { toast } from '../_components/toast/toast.js'
+import { statusHtmlToPlainText } from '../_utils/statusHtmlToPlainText.js'
+import { formatIntl } from '../_utils/formatIntl.js'
export async function shareStatus (status) {
try {
diff --git a/src/routes/_actions/showComposeDialog.js b/src/routes/_actions/showComposeDialog.js
index da167191..8886c89d 100644
--- a/src/routes/_actions/showComposeDialog.js
+++ b/src/routes/_actions/showComposeDialog.js
@@ -1,7 +1,7 @@
-import { store } from '../_store/store'
-import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/importShowComposeDialog'
-import { database } from '../_database/database'
-import { doMediaUpload } from './media'
+import { store } from '../_store/store.js'
+import { importShowComposeDialog } from '../_components/dialog/asyncDialogs/importShowComposeDialog.js'
+import { database } from '../_database/database.js'
+import { doMediaUpload } from './media.js'
// show a compose dialog, typically invoked by the Web Share API or a PWA shortcut
export async function showComposeDialog () {
diff --git a/src/routes/_actions/showMoreAndScrollToTop.js b/src/routes/_actions/showMoreAndScrollToTop.js
index d1450cd1..6793c75d 100644
--- a/src/routes/_actions/showMoreAndScrollToTop.js
+++ b/src/routes/_actions/showMoreAndScrollToTop.js
@@ -1,8 +1,8 @@
-import { showMoreItemsForCurrentTimeline } from './timeline'
-import { scrollToTop } from '../_utils/scrollToTop'
-import { createStatusOrNotificationUuid } from '../_utils/createStatusOrNotificationUuid'
-import { store } from '../_store/store'
-import { tryToFocusElement } from '../_utils/tryToFocusElement'
+import { showMoreItemsForCurrentTimeline } from './timeline.js'
+import { scrollToTop } from '../_utils/scrollToTop.js'
+import { createStatusOrNotificationUuid } from '../_utils/createStatusOrNotificationUuid.js'
+import { store } from '../_store/store.js'
+import { tryToFocusElement } from '../_utils/tryToFocusElement.js'
export function showMoreAndScrollToTop () {
// Similar to Twitter, pressing "." will click the "show more" button and select
diff --git a/src/routes/_actions/statuses.js b/src/routes/_actions/statuses.js
index 295e307f..990a783d 100644
--- a/src/routes/_actions/statuses.js
+++ b/src/routes/_actions/statuses.js
@@ -1,4 +1,4 @@
-import { database } from '../_database/database'
+import { database } from '../_database/database.js'
export async function getIdThatThisStatusReblogged (instanceName, statusId) {
const status = await database.getStatus(instanceName, statusId)
diff --git a/src/routes/_actions/stream/fillStreamingGap.js b/src/routes/_actions/stream/fillStreamingGap.js
index a34550f6..355dd559 100644
--- a/src/routes/_actions/stream/fillStreamingGap.js
+++ b/src/routes/_actions/stream/fillStreamingGap.js
@@ -1,5 +1,5 @@
-import { addStatusesOrNotifications } from '../addStatusOrNotification'
-import { getTimeline } from '../../_api/timelines'
+import { addStatusesOrNotifications } from '../addStatusOrNotification.js'
+import { getTimeline } from '../../_api/timelines.js'
const TIMELINE_GAP_BATCH_SIZE = 20 // Mastodon timeline API maximum limit
const MAX_NUM_REQUESTS = 15 // to avoid getting caught in an infinite loop somehow
diff --git a/src/routes/_actions/stream/processMessage.js b/src/routes/_actions/stream/processMessage.js
index 4e66a052..63bcac6e 100644
--- a/src/routes/_actions/stream/processMessage.js
+++ b/src/routes/_actions/stream/processMessage.js
@@ -1,7 +1,7 @@
-import { mark, stop } from '../../_utils/marks'
-import { deleteStatus } from '../deleteStatuses'
-import { addStatusOrNotification } from '../addStatusOrNotification'
-import { emit } from '../../_utils/eventBus'
+import { mark, stop } from '../../_utils/marks.js'
+import { deleteStatus } from '../deleteStatuses.js'
+import { addStatusOrNotification } from '../addStatusOrNotification.js'
+import { emit } from '../../_utils/eventBus.js'
const KNOWN_EVENTS = ['update', 'delete', 'notification', 'conversation', 'filters_changed']
diff --git a/src/routes/_actions/stream/streaming.js b/src/routes/_actions/stream/streaming.js
index 61cd45ac..e2dce402 100644
--- a/src/routes/_actions/stream/streaming.js
+++ b/src/routes/_actions/stream/streaming.js
@@ -1,7 +1,7 @@
-import { TimelineStream } from '../../_api/stream/TimelineStream'
-import { processMessage } from './processMessage'
-import { fillStreamingGap } from './fillStreamingGap'
-import { store } from '../../_store/store'
+import { TimelineStream } from '../../_api/stream/TimelineStream.js'
+import { processMessage } from './processMessage.js'
+import { fillStreamingGap } from './fillStreamingGap.js'
+import { store } from '../../_store/store.js'
export function createStream (api, instanceName, accessToken, timelineName, firstStatusId, firstNotificationId) {
console.log(`streaming ${instanceName} ${timelineName}: createStream`, 'firstStatusId', firstStatusId,
diff --git a/src/routes/_actions/timeline.js b/src/routes/_actions/timeline.js
index 900fabfc..6eabed10 100644
--- a/src/routes/_actions/timeline.js
+++ b/src/routes/_actions/timeline.js
@@ -1,19 +1,18 @@
-import { store } from '../_store/store'
-import { getTimeline } from '../_api/timelines'
-import { toast } from '../_components/toast/toast'
-import { mark, stop } from '../_utils/marks'
-import { concat, mergeArrays } from '../_utils/arrays'
-import { compareTimelineItemSummaries } from '../_utils/statusIdSorting'
-import isEqual from 'lodash-es/isEqual'
-import { database } from '../_database/database'
-import { getStatus, getStatusContext } from '../_api/statuses'
-import { emit } from '../_utils/eventBus'
-import { TIMELINE_BATCH_SIZE } from '../_static/timelines'
-import { timelineItemToSummary } from '../_utils/timelineItemToSummary'
-import uniqBy from 'lodash-es/uniqBy'
-import { addStatusesOrNotifications } from './addStatusOrNotification'
-import { scheduleIdleTask } from '../_utils/scheduleIdleTask'
-import { sortItemSummariesForThread } from '../_utils/sortItemSummariesForThread'
+import { store } from '../_store/store.js'
+import { getTimeline } from '../_api/timelines.js'
+import { toast } from '../_components/toast/toast.js'
+import { mark, stop } from '../_utils/marks.js'
+import { concat, mergeArrays } from '../_utils/arrays.js'
+import { compareTimelineItemSummaries } from '../_utils/statusIdSorting.js'
+import { uniqBy, isEqual } from '../_thirdparty/lodash/objects.js'
+import { database } from '../_database/database.js'
+import { getStatus, getStatusContext } from '../_api/statuses.js'
+import { emit } from '../_utils/eventBus.js'
+import { TIMELINE_BATCH_SIZE } from '../_static/timelines.js'
+import { timelineItemToSummary } from '../_utils/timelineItemToSummary.js'
+import { addStatusesOrNotifications } from './addStatusOrNotification.js'
+import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
+import { sortItemSummariesForThread } from '../_utils/sortItemSummariesForThread.js'
import li from 'li'
const byId = _ => _.id
diff --git a/src/routes/_actions/toggleMute.js b/src/routes/_actions/toggleMute.js
index d4387f67..9ceb5550 100644
--- a/src/routes/_actions/toggleMute.js
+++ b/src/routes/_actions/toggleMute.js
@@ -1,5 +1,5 @@
import { importShowMuteDialog } from '../_components/dialog/asyncDialogs/importShowMuteDialog.js'
-import { setAccountMuted } from './mute'
+import { setAccountMuted } from './mute.js'
export async function toggleMute (account, mute) {
if (mute) {
diff --git a/src/routes/_api/block.js b/src/routes/_api/block.js
index 730d0c3c..8afb5306 100644
--- a/src/routes/_api/block.js
+++ b/src/routes/_api/block.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
export async function blockAccount (instanceName, accessToken, accountId) {
const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/block`
diff --git a/src/routes/_api/blockDomain.js b/src/routes/_api/blockDomain.js
index ae022e95..e44dd7b4 100644
--- a/src/routes/_api/blockDomain.js
+++ b/src/routes/_api/blockDomain.js
@@ -1,5 +1,5 @@
-import { post, WRITE_TIMEOUT, paramsString, del } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { post, WRITE_TIMEOUT, paramsString, del } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function blockDomain (instanceName, accessToken, domain) {
const url = `${basename(instanceName)}/api/v1/domain_blocks?${paramsString({ domain })}`
diff --git a/src/routes/_api/blockedAndMuted.js b/src/routes/_api/blockedAndMuted.js
index 1928c02b..fe2ade66 100644
--- a/src/routes/_api/blockedAndMuted.js
+++ b/src/routes/_api/blockedAndMuted.js
@@ -1,5 +1,5 @@
-import { DEFAULT_TIMEOUT, get, paramsString } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { DEFAULT_TIMEOUT, get, paramsString } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function getBlockedAccounts (instanceName, accessToken, limit = 80) {
let url = `${basename(instanceName)}/api/v1/blocks`
diff --git a/src/routes/_api/bookmark.js b/src/routes/_api/bookmark.js
index 3fde63d2..1e3a3ecc 100644
--- a/src/routes/_api/bookmark.js
+++ b/src/routes/_api/bookmark.js
@@ -1,5 +1,5 @@
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function bookmarkStatus (instanceName, accessToken, statusId) {
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/bookmark`
diff --git a/src/routes/_api/delete.js b/src/routes/_api/delete.js
index d8a9a765..b497659b 100644
--- a/src/routes/_api/delete.js
+++ b/src/routes/_api/delete.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { del, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { del, WRITE_TIMEOUT } from '../_utils/ajax.js'
export async function deleteStatus (instanceName, accessToken, statusId) {
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}`
diff --git a/src/routes/_api/emoji.js b/src/routes/_api/emoji.js
index e0a30089..58bcd923 100644
--- a/src/routes/_api/emoji.js
+++ b/src/routes/_api/emoji.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { DEFAULT_TIMEOUT, get } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { DEFAULT_TIMEOUT, get } from '../_utils/ajax.js'
export function getCustomEmoji (instanceName, accessToken) {
const url = `${basename(instanceName)}/api/v1/custom_emojis`
diff --git a/src/routes/_api/favorite.js b/src/routes/_api/favorite.js
index 5232cac7..dbb38a65 100644
--- a/src/routes/_api/favorite.js
+++ b/src/routes/_api/favorite.js
@@ -1,5 +1,5 @@
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { basename, auth } from './utils'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { basename, auth } from './utils.js'
export async function favoriteStatus (instanceName, accessToken, statusId) {
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/favourite`
diff --git a/src/routes/_api/filters.js b/src/routes/_api/filters.js
index 98ec6050..4849098a 100644
--- a/src/routes/_api/filters.js
+++ b/src/routes/_api/filters.js
@@ -1,5 +1,5 @@
-import { get, DEFAULT_TIMEOUT, post, WRITE_TIMEOUT, put, del } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, DEFAULT_TIMEOUT, post, WRITE_TIMEOUT, put, del } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export function getFilters (instanceName, accessToken) {
const url = `${basename(instanceName)}/api/v1/filters`
diff --git a/src/routes/_api/follow.js b/src/routes/_api/follow.js
index 3e16b0a7..5fdb75e1 100644
--- a/src/routes/_api/follow.js
+++ b/src/routes/_api/follow.js
@@ -1,5 +1,5 @@
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function followAccount (instanceName, accessToken, accountId) {
const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow`
diff --git a/src/routes/_api/followRequests.js b/src/routes/_api/followRequests.js
index 7f6c87c5..c6b23e23 100644
--- a/src/routes/_api/followRequests.js
+++ b/src/routes/_api/followRequests.js
@@ -1,5 +1,5 @@
-import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function getFollowRequests (instanceName, accessToken) {
const url = `${basename(instanceName)}/api/v1/follow_requests`
diff --git a/src/routes/_api/followsAndFollowers.js b/src/routes/_api/followsAndFollowers.js
index 0f540bc2..f6d25d7b 100644
--- a/src/routes/_api/followsAndFollowers.js
+++ b/src/routes/_api/followsAndFollowers.js
@@ -1,5 +1,5 @@
-import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function getFollows (instanceName, accessToken, accountId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/following`
diff --git a/src/routes/_api/instance.js b/src/routes/_api/instance.js
index 1b7cb624..7538b8dd 100644
--- a/src/routes/_api/instance.js
+++ b/src/routes/_api/instance.js
@@ -1,5 +1,5 @@
-import { get, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { basename } from './utils'
+import { get, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { basename } from './utils.js'
export function getInstanceInfo (instanceName) {
const url = `${basename(instanceName)}/api/v1/instance`
diff --git a/src/routes/_api/lists.js b/src/routes/_api/lists.js
index 9c92b63e..14434c05 100644
--- a/src/routes/_api/lists.js
+++ b/src/routes/_api/lists.js
@@ -1,5 +1,5 @@
-import { get, DEFAULT_TIMEOUT, post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, DEFAULT_TIMEOUT, post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export function getLists (instanceName, accessToken) {
const url = `${basename(instanceName)}/api/v1/lists`
diff --git a/src/routes/_api/media.js b/src/routes/_api/media.js
index 68bd0d21..30f30a60 100644
--- a/src/routes/_api/media.js
+++ b/src/routes/_api/media.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post, put, MEDIA_WRITE_TIMEOUT, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post, put, MEDIA_WRITE_TIMEOUT, WRITE_TIMEOUT } from '../_utils/ajax.js'
export async function uploadMedia (instanceName, accessToken, file, description) {
const formData = new FormData()
diff --git a/src/routes/_api/mute.js b/src/routes/_api/mute.js
index cecc7f69..04ce61cd 100644
--- a/src/routes/_api/mute.js
+++ b/src/routes/_api/mute.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
export async function muteAccount (instanceName, accessToken, accountId, notifications) {
const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/mute`
diff --git a/src/routes/_api/muteConversation.js b/src/routes/_api/muteConversation.js
index c9c9a7da..e9672e08 100644
--- a/src/routes/_api/muteConversation.js
+++ b/src/routes/_api/muteConversation.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
export async function muteConversation (instanceName, accessToken, statusId) {
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/mute`
diff --git a/src/routes/_api/oauth.js b/src/routes/_api/oauth.js
index b804dc88..630082cc 100644
--- a/src/routes/_api/oauth.js
+++ b/src/routes/_api/oauth.js
@@ -1,5 +1,5 @@
-import { post, paramsString, WRITE_TIMEOUT } from '../_utils/ajax'
-import { basename } from './utils'
+import { post, paramsString, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { basename } from './utils.js'
const WEBSITE = 'https://pinafore.social'
const SCOPES = 'read write follow push'
diff --git a/src/routes/_api/pin.js b/src/routes/_api/pin.js
index 61960eeb..3b18205b 100644
--- a/src/routes/_api/pin.js
+++ b/src/routes/_api/pin.js
@@ -1,5 +1,5 @@
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function pinStatus (instanceName, accessToken, statusId) {
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/pin`
diff --git a/src/routes/_api/pinnedStatuses.js b/src/routes/_api/pinnedStatuses.js
index d17aa35d..bf6fadbe 100644
--- a/src/routes/_api/pinnedStatuses.js
+++ b/src/routes/_api/pinnedStatuses.js
@@ -1,5 +1,5 @@
-import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function getPinnedStatuses (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/statuses`
diff --git a/src/routes/_api/polls.js b/src/routes/_api/polls.js
index b4163779..5d4be01a 100644
--- a/src/routes/_api/polls.js
+++ b/src/routes/_api/polls.js
@@ -1,5 +1,5 @@
-import { get, post, DEFAULT_TIMEOUT, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, post, DEFAULT_TIMEOUT, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function getPoll (instanceName, accessToken, pollId) {
const url = `${basename(instanceName)}/api/v1/polls/${pollId}`
diff --git a/src/routes/_api/pushSubscription.js b/src/routes/_api/pushSubscription.js
index a17a1455..43ffc1e3 100644
--- a/src/routes/_api/pushSubscription.js
+++ b/src/routes/_api/pushSubscription.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post, put, get, del } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post, put, get, del } from '../_utils/ajax.js'
export async function postSubscription (instanceName, accessToken, subscription, alerts) {
const url = `${basename(instanceName)}/api/v1/push/subscription`
diff --git a/src/routes/_api/reblog.js b/src/routes/_api/reblog.js
index ccfc650a..5ba4ab7d 100644
--- a/src/routes/_api/reblog.js
+++ b/src/routes/_api/reblog.js
@@ -1,5 +1,5 @@
-import { post } from '../_utils/ajax'
-import { basename, auth } from './utils'
+import { post } from '../_utils/ajax.js'
+import { basename, auth } from './utils.js'
export async function reblogStatus (instanceName, accessToken, statusId) {
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/reblog`
diff --git a/src/routes/_api/reblogsAndFavs.js b/src/routes/_api/reblogsAndFavs.js
index 6185fc48..f2b358b4 100644
--- a/src/routes/_api/reblogsAndFavs.js
+++ b/src/routes/_api/reblogsAndFavs.js
@@ -1,5 +1,5 @@
-import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function getReblogs (instanceName, accessToken, statusId, limit = 80) {
let url = `${basename(instanceName)}/api/v1/statuses/${statusId}/reblogged_by`
diff --git a/src/routes/_api/relationships.js b/src/routes/_api/relationships.js
index e1a6acd9..6aa701d6 100644
--- a/src/routes/_api/relationships.js
+++ b/src/routes/_api/relationships.js
@@ -1,5 +1,5 @@
-import { basename, auth } from './utils'
-import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
+import { basename, auth } from './utils.js'
+import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
export async function getRelationship (instanceName, accessToken, accountId) {
const url = `${basename(instanceName)}/api/v1/accounts/relationships?${paramsString({ id: accountId })}`
diff --git a/src/routes/_api/report.js b/src/routes/_api/report.js
index f002f96d..b7a876a4 100644
--- a/src/routes/_api/report.js
+++ b/src/routes/_api/report.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post } from '../_utils/ajax.js'
export async function report (instanceName, accessToken, accountId, statusIds, comment, forward) {
const url = `${basename(instanceName)}/api/v1/reports`
diff --git a/src/routes/_api/requests.js b/src/routes/_api/requests.js
index fc8c0668..746d30c7 100644
--- a/src/routes/_api/requests.js
+++ b/src/routes/_api/requests.js
@@ -1,5 +1,5 @@
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function approveFollowRequest (instanceName, accessToken, accountId) {
const url = `${basename(instanceName)}/api/v1/follow_requests/${accountId}/authorize`
diff --git a/src/routes/_api/search.js b/src/routes/_api/search.js
index 4e8e96cc..f2050311 100644
--- a/src/routes/_api/search.js
+++ b/src/routes/_api/search.js
@@ -1,5 +1,5 @@
-import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
function doSearch (version, instanceName, accessToken, query, resolve, limit, excludeUnreviewed, signal) {
const url = `${basename(instanceName)}/api/${version}/search?` + paramsString({
diff --git a/src/routes/_api/showReblogs.js b/src/routes/_api/showReblogs.js
index 45bf9bcc..b655d970 100644
--- a/src/routes/_api/showReblogs.js
+++ b/src/routes/_api/showReblogs.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { post, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
export function setShowReblogs (instanceName, accessToken, accountId, showReblogs) {
const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow`
diff --git a/src/routes/_api/statuses.js b/src/routes/_api/statuses.js
index 5edf0a72..4ce8c115 100644
--- a/src/routes/_api/statuses.js
+++ b/src/routes/_api/statuses.js
@@ -1,5 +1,5 @@
-import { auth, basename } from './utils'
-import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax'
+import { auth, basename } from './utils.js'
+import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax.js'
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
sensitive, spoilerText, visibility, poll) {
diff --git a/src/routes/_api/stream/TimelineStream.js b/src/routes/_api/stream/TimelineStream.js
index 80d1f74d..1b539d23 100644
--- a/src/routes/_api/stream/TimelineStream.js
+++ b/src/routes/_api/stream/TimelineStream.js
@@ -1,9 +1,9 @@
-import { WebSocketClient } from '../../_thirdparty/websocket/websocket'
-import { lifecycle } from '../../_utils/lifecycle'
-import { getStreamUrl } from './getStreamUrl'
+import { WebSocketClient } from '../../_thirdparty/websocket/websocket.js'
+import { lifecycle } from '../../_utils/lifecycle.js'
+import { getStreamUrl } from './getStreamUrl.js'
import { EventEmitter } from 'events-light'
-import { eventBus } from '../../_utils/eventBus'
-import { safeParse } from '../../_utils/safeParse'
+import { eventBus } from '../../_utils/eventBus.js'
+import { safeParse } from '../../_utils/safeParse.js'
export class TimelineStream extends EventEmitter {
constructor (streamingApi, accessToken, timeline) {
diff --git a/src/routes/_api/stream/getStreamUrl.js b/src/routes/_api/stream/getStreamUrl.js
index fa9268c4..20b9acba 100644
--- a/src/routes/_api/stream/getStreamUrl.js
+++ b/src/routes/_api/stream/getStreamUrl.js
@@ -1,4 +1,4 @@
-import { paramsString } from '../../_utils/ajax'
+import { paramsString } from '../../_utils/ajax.js'
function getStreamName (timeline) {
switch (timeline) {
diff --git a/src/routes/_api/timelines.js b/src/routes/_api/timelines.js
index fe436dfa..bbc98876 100644
--- a/src/routes/_api/timelines.js
+++ b/src/routes/_api/timelines.js
@@ -1,5 +1,5 @@
-import { getWithHeaders, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { getWithHeaders, paramsString, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
function getTimelineUrlPath (timeline) {
switch (timeline) {
diff --git a/src/routes/_api/updateCredentials.js b/src/routes/_api/updateCredentials.js
index 4226a70d..604550d5 100644
--- a/src/routes/_api/updateCredentials.js
+++ b/src/routes/_api/updateCredentials.js
@@ -1,5 +1,5 @@
-import { WRITE_TIMEOUT, patch } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { WRITE_TIMEOUT, patch } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export async function updateCredentials (instanceName, accessToken, accountData) {
const url = `${basename(instanceName)}/api/v1/accounts/update_credentials`
diff --git a/src/routes/_api/user.js b/src/routes/_api/user.js
index 0ad2778a..4d479935 100644
--- a/src/routes/_api/user.js
+++ b/src/routes/_api/user.js
@@ -1,5 +1,5 @@
-import { get, DEFAULT_TIMEOUT } from '../_utils/ajax'
-import { auth, basename } from './utils'
+import { get, DEFAULT_TIMEOUT } from '../_utils/ajax.js'
+import { auth, basename } from './utils.js'
export function getVerifyCredentials (instanceName, accessToken) {
const url = `${basename(instanceName)}/api/v1/accounts/verify_credentials`
diff --git a/src/routes/_components/AccountsListPage.html b/src/routes/_components/AccountsListPage.html
index 9179befe..cc3fba14 100644
--- a/src/routes/_components/AccountsListPage.html
+++ b/src/routes/_components/AccountsListPage.html
@@ -31,12 +31,12 @@
}
\ No newline at end of file
+
diff --git a/src/routes/_components/settings/SettingsLayout.html b/src/routes/_components/settings/SettingsLayout.html
index f2ba5d86..f5262cbe 100644
--- a/src/routes/_components/settings/SettingsLayout.html
+++ b/src/routes/_components/settings/SettingsLayout.html
@@ -25,7 +25,7 @@
\ No newline at end of file
+
diff --git a/src/routes/_components/virtualList/VirtualListHeader.html b/src/routes/_components/virtualList/VirtualListHeader.html
index 993c7891..b653e030 100644
--- a/src/routes/_components/virtualList/VirtualListHeader.html
+++ b/src/routes/_components/virtualList/VirtualListHeader.html
@@ -21,9 +21,9 @@
}