use standard

This commit is contained in:
Nolan Lawson 2018-02-08 22:29:29 -08:00
parent 537a112adb
commit 2e83bc0ff9
66 changed files with 1269 additions and 448 deletions

View File

@ -21,7 +21,7 @@ const scssDir = path.join(__dirname, '../scss')
const themesScssDir = path.join(__dirname, '../scss/themes') const themesScssDir = path.join(__dirname, '../scss/themes')
const assetsDir = path.join(__dirname, '../assets') const assetsDir = path.join(__dirname, '../assets')
function doWatch() { function doWatch () {
var start = now() var start = now()
chokidar.watch(scssDir).on('change', debounce(() => { chokidar.watch(scssDir).on('change', debounce(() => {
console.log('Recompiling SCSS...') console.log('Recompiling SCSS...')
@ -35,7 +35,7 @@ function doWatch() {
chokidar.watch() chokidar.watch()
} }
async function compileGlobalSass() { async function compileGlobalSass () {
let results = await Promise.all([ let results = await Promise.all([
render({file: defaultThemeScss, outputStyle: 'compressed'}), render({file: defaultThemeScss, outputStyle: 'compressed'}),
render({file: globalScss, outputStyle: 'compressed'}), render({file: globalScss, outputStyle: 'compressed'}),
@ -51,7 +51,7 @@ async function compileGlobalSass() {
await writeFile(html2xxFile, html, 'utf8') await writeFile(html2xxFile, html, 'utf8')
} }
async function compileThemesSass() { async function compileThemesSass () {
let files = (await readdir(themesScssDir)).filter(file => !path.basename(file).startsWith('_')) let files = (await readdir(themesScssDir)).filter(file => !path.basename(file).startsWith('_'))
await Promise.all(files.map(async file => { await Promise.all(files.map(async file => {
let res = await render({file: path.join(themesScssDir, file)}) let res = await render({file: path.join(themesScssDir, file)})
@ -60,7 +60,7 @@ async function compileThemesSass() {
})) }))
} }
async function main() { async function main () {
await Promise.all([compileGlobalSass(), compileThemesSass()]) await Promise.all([compileGlobalSass(), compileThemesSass()])
if (argv.watch) { if (argv.watch) {
doWatch() doWatch()
@ -70,4 +70,4 @@ async function main() {
Promise.resolve().then(main).catch(err => { Promise.resolve().then(main).catch(err => {
console.error(err) console.error(err)
process.exit(1) process.exit(1)
}) })

View File

@ -11,7 +11,7 @@ const $ = require('cheerio')
const readFile = pify(fs.readFile.bind(fs)) const readFile = pify(fs.readFile.bind(fs))
const writeFile = pify(fs.writeFile.bind(fs)) const writeFile = pify(fs.writeFile.bind(fs))
async function main() { async function main () {
let result = (await Promise.all(svgs.map(async svg => { let result = (await Promise.all(svgs.map(async svg => {
let filepath = path.join(__dirname, '../', svg.src) let filepath = path.join(__dirname, '../', svg.src)
let content = await readFile(filepath, 'utf8') let content = await readFile(filepath, 'utf8')
@ -40,4 +40,4 @@ async function main() {
main().catch(err => { main().catch(err => {
console.error(err) console.error(err)
process.exit(1) process.exit(1)
}) })

View File

@ -11,10 +11,10 @@ const readFile = pify(fs.readFile.bind(fs))
const glob = pify(require('glob')) const glob = pify(require('glob'))
const rimraf = pify(require('rimraf')) const rimraf = pify(require('rimraf'))
const selectorRegex = /\n[ \t]*([0-9\w\- \t\.:#,]+?)[ \t]*\{/g const selectorRegex = /\n[ \t]*([0-9\w\- \t.:#,]+?)[ \t]*{/g
const styleRegex = /<style>[\s\S]+?<\/style>/ const styleRegex = /<style>[\s\S]+?<\/style>/
async function main() { async function main () {
if (argv.reverse) { // reverse the operation we just did if (argv.reverse) { // reverse the operation we just did
let tmpComponents = await glob('./routes/**/.tmp-*.html') let tmpComponents = await glob('./routes/**/.tmp-*.html')
for (let filename of tmpComponents) { for (let filename of tmpComponents) {
@ -29,7 +29,7 @@ async function main() {
let text = await readFile(filename, 'utf8') let text = await readFile(filename, 'utf8')
let newText = text.replace(styleRegex, style => { let newText = text.replace(styleRegex, style => {
return style.replace(selectorRegex, selectorMatch => { return style.replace(selectorRegex, selectorMatch => {
return selectorMatch.replace(/\S[^\{]+/, selector => `:global(${selector})`) return selectorMatch.replace(/\S[^{]+/, selector => `:global(${selector})`)
}) })
}) })
let newFilename = path.join(path.dirname(filename), '.tmp-' + path.basename(filename)) let newFilename = path.join(path.dirname(filename), '.tmp-' + path.basename(filename))
@ -43,4 +43,4 @@ async function main() {
Promise.resolve().then(main).catch(err => { Promise.resolve().then(main).catch(err => {
console.error(err) console.error(err)
process.exit(1) process.exit(1)
}) })

View File

@ -1,26 +1,26 @@
module.exports = [ module.exports = [
{id: 'pinafore-logo', src: 'original-assets/sailboat.svg', title: 'Home'}, {id: 'pinafore-logo', src: 'original-assets/sailboat.svg', title: 'Home'},
{id:'fa-bell', src:'node_modules/font-awesome-svg-png/white/svg/bell.svg', title: 'Notifications'}, {id: 'fa-bell', src: 'node_modules/font-awesome-svg-png/white/svg/bell.svg', title: 'Notifications'},
{id:'fa-users', src:'node_modules/font-awesome-svg-png/white/svg/users.svg', title: 'Local'}, {id: 'fa-users', src: 'node_modules/font-awesome-svg-png/white/svg/users.svg', title: 'Local'},
{id:'fa-globe', src:'node_modules/font-awesome-svg-png/white/svg/globe.svg', title: 'Federated'}, {id: 'fa-globe', src: 'node_modules/font-awesome-svg-png/white/svg/globe.svg', title: 'Federated'},
{id:'fa-gear', src:'node_modules/font-awesome-svg-png/white/svg/gear.svg', title: 'Settings'}, {id: 'fa-gear', src: 'node_modules/font-awesome-svg-png/white/svg/gear.svg', title: 'Settings'},
{id:'fa-reply', src:'node_modules/font-awesome-svg-png/white/svg/reply.svg', title: 'Reply'}, {id: 'fa-reply', src: 'node_modules/font-awesome-svg-png/white/svg/reply.svg', title: 'Reply'},
{id:'fa-retweet', src:'node_modules/font-awesome-svg-png/white/svg/retweet.svg', title: 'Boost'}, {id: 'fa-retweet', src: 'node_modules/font-awesome-svg-png/white/svg/retweet.svg', title: 'Boost'},
{id:'fa-star', src:'node_modules/font-awesome-svg-png/white/svg/star.svg', title: 'Favorite'}, {id: 'fa-star', src: 'node_modules/font-awesome-svg-png/white/svg/star.svg', title: 'Favorite'},
{id:'fa-ellipsis-h', src:'node_modules/font-awesome-svg-png/white/svg/ellipsis-h.svg', title: 'More'}, {id: 'fa-ellipsis-h', src: 'node_modules/font-awesome-svg-png/white/svg/ellipsis-h.svg', title: 'More'},
{id:'fa-spinner', src:'node_modules/font-awesome-svg-png/white/svg/spinner.svg', title: 'Spinner'}, {id: 'fa-spinner', src: 'node_modules/font-awesome-svg-png/white/svg/spinner.svg', title: 'Spinner'},
{id:'fa-user', src:'node_modules/font-awesome-svg-png/white/svg/user.svg', title: 'Empty user profile'}, {id: 'fa-user', src: 'node_modules/font-awesome-svg-png/white/svg/user.svg', title: 'Empty user profile'},
{id:'fa-play-circle', src:'node_modules/font-awesome-svg-png/white/svg/play-circle.svg', title: 'Play'}, {id: 'fa-play-circle', src: 'node_modules/font-awesome-svg-png/white/svg/play-circle.svg', title: 'Play'},
{id:'fa-eye', src:'node_modules/font-awesome-svg-png/white/svg/eye.svg', title: 'Show Sensitive Content'}, {id: 'fa-eye', src: 'node_modules/font-awesome-svg-png/white/svg/eye.svg', title: 'Show Sensitive Content'},
{id:'fa-eye-slash', src:'node_modules/font-awesome-svg-png/white/svg/eye-slash.svg', title: 'Hide Sensitive Content'}, {id: 'fa-eye-slash', src: 'node_modules/font-awesome-svg-png/white/svg/eye-slash.svg', title: 'Hide Sensitive Content'},
{id:'fa-lock', src:'node_modules/font-awesome-svg-png/white/svg/lock.svg', title: 'Locked'}, {id: 'fa-lock', src: 'node_modules/font-awesome-svg-png/white/svg/lock.svg', title: 'Locked'},
{id:'fa-envelope', src:'node_modules/font-awesome-svg-png/white/svg/envelope.svg', title: 'Sealed Envelope'}, {id: 'fa-envelope', src: 'node_modules/font-awesome-svg-png/white/svg/envelope.svg', title: 'Sealed Envelope'},
{id:'fa-user-times', src:'node_modules/font-awesome-svg-png/white/svg/user-times.svg', title: 'Stop Following'}, {id: 'fa-user-times', src: 'node_modules/font-awesome-svg-png/white/svg/user-times.svg', title: 'Stop Following'},
{id:'fa-user-plus', src:'node_modules/font-awesome-svg-png/white/svg/user-plus.svg', title: 'Follow'}, {id: 'fa-user-plus', src: 'node_modules/font-awesome-svg-png/white/svg/user-plus.svg', title: 'Follow'},
{id:'fa-external-link', src:'node_modules/font-awesome-svg-png/white/svg/external-link.svg', title: 'External Link'}, {id: 'fa-external-link', src: 'node_modules/font-awesome-svg-png/white/svg/external-link.svg', title: 'External Link'},
{id:'fa-search', src:'node_modules/font-awesome-svg-png/white/svg/search.svg', title: 'Search'}, {id: 'fa-search', src: 'node_modules/font-awesome-svg-png/white/svg/search.svg', title: 'Search'},
{id:'fa-comments', src:'node_modules/font-awesome-svg-png/white/svg/comments.svg', title: 'Conversations'}, {id: 'fa-comments', src: 'node_modules/font-awesome-svg-png/white/svg/comments.svg', title: 'Conversations'},
{id:'fa-paperclip', src:'node_modules/font-awesome-svg-png/white/svg/paperclip.svg', title: 'Paperclip'}, {id: 'fa-paperclip', src: 'node_modules/font-awesome-svg-png/white/svg/paperclip.svg', title: 'Paperclip'},
{id:'fa-thumbtack', src:'node_modules/font-awesome-svg-png/white/svg/thumb-tack.svg', title: 'Thumbtack'}, {id: 'fa-thumbtack', src: 'node_modules/font-awesome-svg-png/white/svg/thumb-tack.svg', title: 'Thumbtack'},
{id:'fa-bars', src:'node_modules/font-awesome-svg-png/white/svg/bars.svg', title: 'List'}, {id: 'fa-bars', src: 'node_modules/font-awesome-svg-png/white/svg/bars.svg', title: 'List'}
] ]

802
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"description": "TODO", "description": "TODO",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"lint": "standard",
"dev": "npm run build-svg && concurrently --kill-others \"npm run build-sass-watch\" \"node server.js\"", "dev": "npm run build-svg && concurrently --kill-others \"npm run build-sass-watch\" \"node server.js\"",
"build": "npm run globalize-css && npm run build-sass && npm run build-svg && sapper build && npm run deglobalize-css", "build": "npm run globalize-css && npm run build-sass && npm run build-svg && sapper build && npm run deglobalize-css",
"start": "cross-env NODE_ENV=production node server.js", "start": "cross-env NODE_ENV=production node server.js",
@ -47,6 +48,7 @@
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"sapper": "nolanlawson/sapper#fix-style-loader-built", "sapper": "nolanlawson/sapper#fix-style-loader-built",
"serve-static": "^1.13.1", "serve-static": "^1.13.1",
"standard": "^10.0.3",
"style-loader": "^0.19.1", "style-loader": "^0.19.1",
"svelte": "^1.54.0", "svelte": "^1.54.0",
"svelte-extras": "^1.6.0", "svelte-extras": "^1.6.0",
@ -63,5 +65,25 @@
}, },
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
},
"standard": {
"globals": [
"fetch",
"IDBKeyRange",
"IDBObjectStore",
"indexedDB",
"requestAnimationFrame",
"requestIdleCallback",
"location",
"localStorage",
"URLSearchParams",
"IntersectionObserver",
"URL"
],
"ignore": [
"dist",
"cypress",
"routes/_utils/asyncModules.js"
]
} }
} }

View File

@ -2,7 +2,7 @@ import { getAccount, getRelationship } from '../_api/user'
import { database } from '../_database/database' import { database } from '../_database/database'
import { store } from '../_store/store' import { store } from '../_store/store'
async function updateAccount(accountId, instanceName, accessToken) { async function updateAccount (accountId, instanceName, accessToken) {
let localPromise = database.getAccount(instanceName, accountId) let localPromise = database.getAccount(instanceName, accountId)
let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => { let remotePromise = getAccount(instanceName, accessToken, accountId).then(account => {
database.setAccount(instanceName, account) database.setAccount(instanceName, account)
@ -21,7 +21,7 @@ async function updateAccount(accountId, instanceName, accessToken) {
} }
} }
async function updateRelationship(accountId, instanceName, accessToken) { async function updateRelationship (accountId, instanceName, accessToken) {
let localPromise = database.getRelationship(instanceName, accountId) let localPromise = database.getRelationship(instanceName, accountId)
let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => { let remotePromise = getRelationship(instanceName, accessToken, accountId).then(relationship => {
database.setRelationship(instanceName, relationship) database.setRelationship(instanceName, relationship)
@ -39,7 +39,7 @@ async function updateRelationship(accountId, instanceName, accessToken) {
} }
} }
export async function updateProfileAndRelationship(accountId) { export async function updateProfileAndRelationship (accountId) {
store.set({ store.set({
currentAccountProfile: null, currentAccountProfile: null,
currentAccountRelationship: null currentAccountRelationship: null
@ -51,4 +51,4 @@ export async function updateProfileAndRelationship(accountId) {
updateAccount(accountId, instanceName, accessToken), updateAccount(accountId, instanceName, accessToken),
updateRelationship(accountId, instanceName, accessToken) updateRelationship(accountId, instanceName, accessToken)
]) ])
} }

View File

@ -6,10 +6,10 @@ import { database } from '../_database/database'
import { store } from '../_store/store' import { store } from '../_store/store'
import { updateVerifyCredentialsForInstance } from './instances' import { updateVerifyCredentialsForInstance } from './instances'
const REDIRECT_URI = (typeof location !== 'undefined' ? const REDIRECT_URI = (typeof location !== 'undefined'
location.origin : 'https://pinafore.social') + '/settings/instances/add' ? location.origin : 'https://pinafore.social') + '/settings/instances/add'
async function redirectToOauth() { async function redirectToOauth () {
let instanceName = store.get('instanceNameInSearch') let instanceName = store.get('instanceNameInSearch')
let loggedInInstances = store.get('loggedInInstances') let loggedInInstances = store.get('loggedInInstances')
instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '').toLowerCase() instanceName = instanceName.replace(/^https?:\/\//, '').replace('/$', '').toLowerCase()
@ -34,7 +34,7 @@ async function redirectToOauth() {
document.location.href = oauthUrl document.location.href = oauthUrl
} }
export async function logInToInstance() { export async function logInToInstance () {
store.set({logInToInstanceLoading: true}) store.set({logInToInstanceLoading: true})
store.set({logInToInstanceError: null}) store.set({logInToInstanceError: null})
try { try {
@ -42,16 +42,16 @@ export async function logInToInstance() {
} catch (err) { } catch (err) {
console.error(err) console.error(err)
let error = `${err.message || err.name}. ` + let error = `${err.message || err.name}. ` +
(navigator.onLine ? (navigator.onLine
`Is this a valid Mastodon instance?` : ? `Is this a valid Mastodon instance?`
`Are you offline?`) : `Are you offline?`)
store.set({logInToInstanceError: error}) store.set({logInToInstanceError: error})
} finally { } finally {
store.set({logInToInstanceLoading: false}) store.set({logInToInstanceLoading: false})
} }
} }
async function registerNewInstance(code) { async function registerNewInstance (code) {
let currentRegisteredInstanceName = store.get('currentRegisteredInstanceName') let currentRegisteredInstanceName = store.get('currentRegisteredInstanceName')
let currentRegisteredInstance = store.get('currentRegisteredInstance') let currentRegisteredInstance = store.get('currentRegisteredInstance')
let instanceData = await getAccessTokenFromAuthCode( let instanceData = await getAccessTokenFromAuthCode(
@ -85,7 +85,7 @@ async function registerNewInstance(code) {
goto('/') goto('/')
} }
export async function handleOauthCode(code) { export async function handleOauthCode (code) {
try { try {
store.set({logInToInstanceLoading: true}) store.set({logInToInstanceLoading: true})
await registerNewInstance(code) await registerNewInstance(code)
@ -95,4 +95,3 @@ export async function handleOauthCode(code) {
store.set({logInToInstanceLoading: false}) store.set({logInToInstanceLoading: false})
} }
} }

View File

@ -6,7 +6,7 @@ import { database } from '../_database/database'
import { goto } from 'sapper/runtime.js' import { goto } from 'sapper/runtime.js'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
export function changeTheme(instanceName, newTheme) { export function changeTheme (instanceName, newTheme) {
let instanceThemes = store.get('instanceThemes') let instanceThemes = store.get('instanceThemes')
instanceThemes[instanceName] = newTheme instanceThemes[instanceName] = newTheme
store.set({instanceThemes: instanceThemes}) store.set({instanceThemes: instanceThemes})
@ -16,22 +16,22 @@ export function changeTheme(instanceName, newTheme) {
} }
} }
export function switchToInstance(instanceName) { export function switchToInstance (instanceName) {
let instanceThemes = store.get('instanceThemes') let instanceThemes = store.get('instanceThemes')
store.set({currentInstance: instanceName}) store.set({currentInstance: instanceName})
store.save() store.save()
switchToTheme(instanceThemes[instanceName]) switchToTheme(instanceThemes[instanceName])
} }
export async function logOutOfInstance(instanceName) { export async function logOutOfInstance (instanceName) {
let loggedInInstances = store.get('loggedInInstances') let loggedInInstances = store.get('loggedInInstances')
let instanceThemes = store.get('instanceThemes') let instanceThemes = store.get('instanceThemes')
let loggedInInstancesInOrder = store.get('loggedInInstancesInOrder') let loggedInInstancesInOrder = store.get('loggedInInstancesInOrder')
let currentInstance = store.get('currentInstance') let currentInstance = store.get('currentInstance')
loggedInInstancesInOrder.splice(loggedInInstancesInOrder.indexOf(instanceName), 1) loggedInInstancesInOrder.splice(loggedInInstancesInOrder.indexOf(instanceName), 1)
let newInstance = instanceName === currentInstance ? let newInstance = instanceName === currentInstance
loggedInInstancesInOrder[0] : ? loggedInInstancesInOrder[0]
currentInstance : currentInstance
delete loggedInInstances[instanceName] delete loggedInInstances[instanceName]
delete instanceThemes[instanceName] delete instanceThemes[instanceName]
store.set({ store.set({
@ -47,13 +47,13 @@ export async function logOutOfInstance(instanceName) {
goto('/settings/instances') goto('/settings/instances')
} }
function setStoreVerifyCredentials(instanceName, thisVerifyCredentials) { function setStoreVerifyCredentials (instanceName, thisVerifyCredentials) {
let verifyCredentials = store.get('verifyCredentials') || {} let verifyCredentials = store.get('verifyCredentials') || {}
verifyCredentials[instanceName] = thisVerifyCredentials verifyCredentials[instanceName] = thisVerifyCredentials
store.set({verifyCredentials: verifyCredentials}) store.set({verifyCredentials: verifyCredentials})
} }
export async function updateVerifyCredentialsForInstance(instanceName) { export async function updateVerifyCredentialsForInstance (instanceName) {
let loggedInInstances = store.get('loggedInInstances') let loggedInInstances = store.get('loggedInInstances')
let accessToken = loggedInInstances[instanceName].access_token let accessToken = loggedInInstances[instanceName].access_token
await cacheFirstUpdateAfter( await cacheFirstUpdateAfter(
@ -62,4 +62,4 @@ export async function updateVerifyCredentialsForInstance(instanceName) {
verifyCredentials => database.setInstanceVerifyCredentials(instanceName, verifyCredentials), verifyCredentials => database.setInstanceVerifyCredentials(instanceName, verifyCredentials),
verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials) verifyCredentials => setStoreVerifyCredentials(instanceName, verifyCredentials)
) )
} }

View File

@ -3,7 +3,7 @@ import { database } from '../_database/database'
import { getLists } from '../_api/lists' import { getLists } from '../_api/lists'
import { cacheFirstUpdateAfter } from '../_utils/sync' import { cacheFirstUpdateAfter } from '../_utils/sync'
export async function updateLists() { export async function updateLists () {
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
let accessToken = store.get('accessToken') let accessToken = store.get('accessToken')
@ -17,4 +17,4 @@ export async function updateLists() {
store.set({instanceLists: instanceLists}) store.set({instanceLists: instanceLists})
} }
) )
} }

View File

@ -7,7 +7,7 @@ import { mergeArrays } from '../_utils/arrays'
const FETCH_LIMIT = 20 const FETCH_LIMIT = 20
async function fetchTimelineItems(instanceName, accessToken, timelineName, lastTimelineItemId, online) { async function fetchTimelineItems (instanceName, accessToken, timelineName, lastTimelineItemId, online) {
mark('fetchTimelineItems') mark('fetchTimelineItems')
let items let items
if (!online) { if (!online) {
@ -26,7 +26,7 @@ async function fetchTimelineItems(instanceName, accessToken, timelineName, lastT
return items return items
} }
async function addTimelineItems(instanceName, timelineName, newItems) { async function addTimelineItems (instanceName, timelineName, newItems) {
console.log('addTimelineItems, length:', newItems.length) console.log('addTimelineItems, length:', newItems.length)
mark('addTimelineItems') mark('addTimelineItems')
let newIds = newItems.map(item => item.id) let newIds = newItems.map(item => item.id)
@ -36,7 +36,7 @@ async function addTimelineItems(instanceName, timelineName, newItems) {
stop('addTimelineItems') stop('addTimelineItems')
} }
async function fetchTimelineItemsAndPossiblyFallBack() { async function fetchTimelineItemsAndPossiblyFallBack () {
mark('fetchTimelineItemsAndPossiblyFallBack') mark('fetchTimelineItemsAndPossiblyFallBack')
let timelineName = store.get('currentTimeline') let timelineName = store.get('currentTimeline')
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
@ -49,7 +49,7 @@ async function fetchTimelineItemsAndPossiblyFallBack() {
stop('fetchTimelineItemsAndPossiblyFallBack') stop('fetchTimelineItemsAndPossiblyFallBack')
} }
export function initializeTimeline() { export function initializeTimeline () {
mark('initializeTimeline') mark('initializeTimeline')
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
let timeline = store.get('currentTimeline') let timeline = store.get('currentTimeline')
@ -61,7 +61,7 @@ export function initializeTimeline() {
stop('initializeTimeline') stop('initializeTimeline')
} }
export async function setupTimeline() { export async function setupTimeline () {
mark('setupTimeline') mark('setupTimeline')
if (!store.get('timelineItemIds')) { if (!store.get('timelineItemIds')) {
await fetchTimelineItemsAndPossiblyFallBack() await fetchTimelineItemsAndPossiblyFallBack()
@ -69,10 +69,10 @@ export async function setupTimeline() {
stop('setupTimeline') stop('setupTimeline')
} }
export async function fetchTimelineItemsOnScrollToBottom() { export async function fetchTimelineItemsOnScrollToBottom () {
let timelineName = store.get('currentTimeline') let timelineName = store.get('currentTimeline')
let instanceName = store.get('currentInstance') let instanceName = store.get('currentInstance')
store.setForTimeline(instanceName, timelineName, { runningUpdate: true }) store.setForTimeline(instanceName, timelineName, { runningUpdate: true })
await fetchTimelineItemsAndPossiblyFallBack() await fetchTimelineItemsAndPossiblyFallBack()
store.setForTimeline(instanceName, timelineName, { runningUpdate: false }) store.setForTimeline(instanceName, timelineName, { runningUpdate: false })
} }

View File

@ -2,7 +2,7 @@ import { paramsString } from '../_utils/ajax'
import noop from 'lodash/noop' import noop from 'lodash/noop'
import WebSocketClient from '@gamestdio/websocket' import WebSocketClient from '@gamestdio/websocket'
function getStreamName(timeline) { function getStreamName (timeline) {
switch (timeline) { switch (timeline) {
case 'local': case 'local':
return 'public:local' return 'public:local'
@ -18,7 +18,7 @@ function getStreamName(timeline) {
} }
} }
function getUrl(streamingApi, accessToken, timeline) { function getUrl (streamingApi, accessToken, timeline) {
let url = `${streamingApi}/api/v1/streaming` let url = `${streamingApi}/api/v1/streaming`
let streamName = getStreamName(timeline) let streamName = getStreamName(timeline)
@ -38,7 +38,7 @@ function getUrl(streamingApi, accessToken, timeline) {
} }
export class StatusStream { export class StatusStream {
constructor(streamingApi, accessToken, timeline, opts) { constructor (streamingApi, accessToken, timeline, opts) {
let url = getUrl(streamingApi, accessToken, timeline) let url = getUrl(streamingApi, accessToken, timeline)
const ws = new WebSocketClient(url, null, { backoff: 'exponential' }) const ws = new WebSocketClient(url, null, { backoff: 'exponential' })
@ -52,7 +52,7 @@ export class StatusStream {
this._ws = ws this._ws = ws
} }
close() { close () {
this._ws.close() this._ws.close()
} }
} }

View File

@ -1,7 +1,7 @@
import { get } from '../_utils/ajax' import { get } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export function getInstanceInfo(instanceName) { export function getInstanceInfo (instanceName) {
let url = `${basename(instanceName)}/api/v1/instance` let url = `${basename(instanceName)}/api/v1/instance`
return get(url) return get(url)
} }

View File

@ -1,8 +1,8 @@
import { get } from '../_utils/ajax' import { get } from '../_utils/ajax'
export function getLists(instanceName, accessToken) { export function getLists (instanceName, accessToken) {
let url = `https://${instanceName}/api/v1/lists` let url = `https://${instanceName}/api/v1/lists`
return get(url, { return get(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,20 +1,21 @@
import { post, paramsString } from '../_utils/ajax'
import { basename } from './utils'
const WEBSITE = 'https://pinafore.social' const WEBSITE = 'https://pinafore.social'
const SCOPES = 'read write follow' const SCOPES = 'read write follow'
const CLIENT_NAME = 'Pinafore' const CLIENT_NAME = 'Pinafore'
import { post, get, paramsString } from '../_utils/ajax'
import { basename } from './utils'
export function registerApplication(instanceName, redirectUri) { export function registerApplication (instanceName, redirectUri) {
const url = `${basename(instanceName)}/api/v1/apps` const url = `${basename(instanceName)}/api/v1/apps`
return post(url, { return post(url, {
client_name: CLIENT_NAME, client_name: CLIENT_NAME,
redirect_uris: redirectUri, redirect_uris: redirectUri,
scopes: SCOPES, scopes: SCOPES,
website: WEBSITE website: WEBSITE
}) })
} }
export function generateAuthLink(instanceName, clientId, redirectUri) { export function generateAuthLink (instanceName, clientId, redirectUri) {
let params = paramsString({ let params = paramsString({
'client_id': clientId, 'client_id': clientId,
'redirect_uri': redirectUri, 'redirect_uri': redirectUri,
@ -24,7 +25,7 @@ export function generateAuthLink(instanceName, clientId, redirectUri) {
return `${basename(instanceName)}/oauth/authorize?${params}` return `${basename(instanceName)}/oauth/authorize?${params}`
} }
export function getAccessTokenFromAuthCode(instanceName, clientId, clientSecret, code, redirectUri) { export function getAccessTokenFromAuthCode (instanceName, clientId, clientSecret, code, redirectUri) {
let url = `${basename(instanceName)}/oauth/token` let url = `${basename(instanceName)}/oauth/token`
return post(url, { return post(url, {
client_id: clientId, client_id: clientId,
@ -33,4 +34,4 @@ export function getAccessTokenFromAuthCode(instanceName, clientId, clientSecret,
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: code code: code
}) })
} }

View File

@ -1,6 +1,6 @@
import { get, paramsString } from '../_utils/ajax' import { get, paramsString } from '../_utils/ajax'
export function search(instanceName, accessToken, query) { export function search (instanceName, accessToken, query) {
let url = `https://${instanceName}/api/v1/search?` + paramsString({ let url = `https://${instanceName}/api/v1/search?` + paramsString({
q: query, q: query,
resolve: true resolve: true
@ -9,4 +9,3 @@ export function search(instanceName, accessToken, query) {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,7 +1,7 @@
import { get, paramsString } from '../_utils/ajax' import { get, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
function getTimelineUrlPath(timeline) { function getTimelineUrlPath (timeline) {
switch (timeline) { switch (timeline) {
case 'local': case 'local':
case 'federated': case 'federated':
@ -24,7 +24,7 @@ function getTimelineUrlPath(timeline) {
} }
} }
export function getTimeline(instanceName, accessToken, timeline, maxId, since) { export function getTimeline (instanceName, accessToken, timeline, maxId, since) {
let timelineUrlName = getTimelineUrlPath(timeline) let timelineUrlName = getTimelineUrlPath(timeline)
let url = `${basename(instanceName)}/api/v1/${timelineUrlName}` let url = `${basename(instanceName)}/api/v1/${timelineUrlName}`
@ -67,4 +67,4 @@ export function getTimeline(instanceName, accessToken, timeline, maxId, since) {
return get(url, { return get(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }

View File

@ -1,25 +1,25 @@
import { get, paramsString } from '../_utils/ajax' import { get, paramsString } from '../_utils/ajax'
import { basename } from './utils' import { basename } from './utils'
export function getVerifyCredentials(instanceName, accessToken) { export function getVerifyCredentials (instanceName, accessToken) {
let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials` let url = `${basename(instanceName)}/api/v1/accounts/verify_credentials`
return get(url, { return get(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }
export function getAccount(instanceName, accessToken, accountId) { export function getAccount (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/${accountId}` let url = `${basename(instanceName)}/api/v1/accounts/${accountId}`
return get(url, { return get(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
} }
export async function getRelationship(instanceName, accessToken, accountId) { export async function getRelationship (instanceName, accessToken, accountId) {
let url = `${basename(instanceName)}/api/v1/accounts/relationships` let url = `${basename(instanceName)}/api/v1/accounts/relationships`
url += '?' + paramsString({id: accountId}) url += '?' + paramsString({id: accountId})
let res = await get(url, { let res = await get(url, {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
}) })
return res[0] return res[0]
} }

View File

@ -2,14 +2,14 @@ const isLocalhost = process.browser && process.env.NODE_ENV !== 'production' &&
(document.location.hostname === 'localhost' || (document.location.hostname === 'localhost' ||
document.location.hostname === '127.0.0.1') document.location.hostname === '127.0.0.1')
function targetIsLocalhost(instanceName) { function targetIsLocalhost (instanceName) {
return process.browser && process.env.NODE_ENV !== 'production' && return process.browser && process.env.NODE_ENV !== 'production' &&
(instanceName === 'localhost:3000' || instanceName === '127.0.0.1:3000') (instanceName === 'localhost:3000' || instanceName === '127.0.0.1:3000')
} }
export function basename(instanceName) { export function basename (instanceName) {
if (isLocalhost && targetIsLocalhost(instanceName)) { if (isLocalhost && targetIsLocalhost(instanceName)) {
return `http://${instanceName}` return `http://${instanceName}`
} }
return `https://${instanceName}` return `https://${instanceName}`
} }

View File

@ -1,6 +1,6 @@
import ConfirmationDialog from './ConfirmationDialog.html' import ConfirmationDialog from './ConfirmationDialog.html'
export function showConfirmationDialog(options) { export function showConfirmationDialog (options) {
let dialog = new ConfirmationDialog({ let dialog = new ConfirmationDialog({
target: document.getElementById('modal-dialog'), target: document.getElementById('modal-dialog'),
data: Object.assign({ data: Object.assign({
@ -8,4 +8,4 @@ export function showConfirmationDialog(options) {
}, options) }, options)
}) })
dialog.show() dialog.show()
} }

View File

@ -1,6 +1,6 @@
import ImageDialog from './ImageDialog.html' import ImageDialog from './ImageDialog.html'
export function showImageDialog(poster, src, type, width, height, description) { export function showImageDialog (poster, src, type, width, height, description) {
let imageDialog = new ImageDialog({ let imageDialog = new ImageDialog({
target: document.getElementById('modal-dialog'), target: document.getElementById('modal-dialog'),
data: { data: {
@ -14,4 +14,4 @@ export function showImageDialog(poster, src, type, width, height, description) {
} }
}) })
imageDialog.show() imageDialog.show()
} }

View File

@ -1,6 +1,6 @@
import VideoDialog from './VideoDialog.html' import VideoDialog from './VideoDialog.html'
export function showVideoDialog(poster, src, width, height, description) { export function showVideoDialog (poster, src, width, height, description) {
let videoDialog = new VideoDialog({ let videoDialog = new VideoDialog({
target: document.getElementById('modal-dialog'), target: document.getElementById('modal-dialog'),
data: { data: {
@ -13,4 +13,4 @@ export function showVideoDialog(poster, src, width, height, description) {
} }
}) })
videoDialog.show() videoDialog.show()
} }

View File

@ -1,7 +1,7 @@
import { RealmStore } from '../../_utils/RealmStore' import { RealmStore } from '../../_utils/RealmStore'
class PseudoVirtualListStore extends RealmStore { class PseudoVirtualListStore extends RealmStore {
constructor(state) { constructor (state) {
super(state, /* maxSize */ 10) super(state, /* maxSize */ 10)
} }
} }
@ -14,4 +14,4 @@ if (process.browser && process.env.NODE_NODE !== 'production') {
window.pseudoVirtualListStore = pseudoVirtualListStore window.pseudoVirtualListStore = pseudoVirtualListStore
} }
export { pseudoVirtualListStore } export { pseudoVirtualListStore }

View File

@ -4,7 +4,7 @@ import { RealmStore } from '../../_utils/RealmStore'
const VIEWPORT_RENDER_FACTOR = 4 const VIEWPORT_RENDER_FACTOR = 4
class VirtualListStore extends RealmStore { class VirtualListStore extends RealmStore {
constructor(state) { constructor (state) {
super(state, /* maxSize */ 10) super(state, /* maxSize */ 10)
} }
} }
@ -22,77 +22,76 @@ virtualListStore.computeForRealm('itemHeights', {})
virtualListStore.compute('visibleItems', virtualListStore.compute('visibleItems',
['items', 'scrollTop', 'itemHeights', 'offsetHeight'], ['items', 'scrollTop', 'itemHeights', 'offsetHeight'],
(items, scrollTop, itemHeights, offsetHeight) => { (items, scrollTop, itemHeights, offsetHeight) => {
mark('compute visibleItems') mark('compute visibleItems')
if (!items) { if (!items) {
return null return null
}
let renderBuffer = VIEWPORT_RENDER_FACTOR * offsetHeight
let visibleItems = []
let totalOffset = 0
let len = items.length
let i = -1
while (++i < len) {
let key = items[i]
let height = itemHeights[key] || 0
let currentOffset = totalOffset
totalOffset += height
let isBelowViewport = (currentOffset < scrollTop)
if (isBelowViewport) {
if (scrollTop - renderBuffer > currentOffset) {
continue // below the area we want to render
} }
} else { let renderBuffer = VIEWPORT_RENDER_FACTOR * offsetHeight
if (currentOffset > (scrollTop + height + renderBuffer)) { let visibleItems = []
break // above the area we want to render let totalOffset = 0
let len = items.length
let i = -1
while (++i < len) {
let key = items[i]
let height = itemHeights[key] || 0
let currentOffset = totalOffset
totalOffset += height
let isBelowViewport = (currentOffset < scrollTop)
if (isBelowViewport) {
if (scrollTop - renderBuffer > currentOffset) {
continue // below the area we want to render
}
} else {
if (currentOffset > (scrollTop + height + renderBuffer)) {
break // above the area we want to render
}
}
visibleItems.push({
offset: currentOffset,
key: key,
index: i
})
} }
} stop('compute visibleItems')
visibleItems.push({ return visibleItems
offset: currentOffset,
key: key,
index: i
}) })
}
stop('compute visibleItems')
return visibleItems
})
virtualListStore.compute('heightWithoutFooter', virtualListStore.compute('heightWithoutFooter',
['items', 'itemHeights'], ['items', 'itemHeights'],
(items, itemHeights) => { (items, itemHeights) => {
if (!items) { if (!items) {
return 0 return 0
} }
let sum = 0 let sum = 0
let i = -1 let i = -1
let len = items.length let len = items.length
while (++i < len) { while (++i < len) {
sum += itemHeights[items[i]] || 0 sum += itemHeights[items[i]] || 0
} }
return sum return sum
}) })
virtualListStore.compute('height', virtualListStore.compute('height',
['heightWithoutFooter', 'showFooter', 'footerHeight'], ['heightWithoutFooter', 'showFooter', 'footerHeight'],
(heightWithoutFooter, showFooter, footerHeight) => { (heightWithoutFooter, showFooter, footerHeight) => {
return showFooter ? (heightWithoutFooter + footerHeight) : heightWithoutFooter return showFooter ? (heightWithoutFooter + footerHeight) : heightWithoutFooter
}) })
virtualListStore.compute('length', ['items'], (items) => items ? items.length : 0) virtualListStore.compute('length', ['items'], (items) => items ? items.length : 0)
virtualListStore.compute('allVisibleItemsHaveHeight', virtualListStore.compute('allVisibleItemsHaveHeight',
['visibleItems', 'itemHeights'], ['visibleItems', 'itemHeights'],
(visibleItems, itemHeights) => { (visibleItems, itemHeights) => {
if (!visibleItems) { if (!visibleItems) {
return false return false
} }
for (let visibleItem of visibleItems) { for (let visibleItem of visibleItems) {
if (!itemHeights[visibleItem.key]) { if (!itemHeights[visibleItem.key]) {
return false return false
} }
} }
return true return true
}) })
if (process.browser && process.env.NODE_ENV !== 'production') { if (process.browser && process.env.NODE_ENV !== 'production') {
window.virtualListStore = virtualListStore window.virtualListStore = virtualListStore
@ -100,4 +99,4 @@ if (process.browser && process.env.NODE_ENV !== 'production') {
export { export {
virtualListStore virtualListStore
} }

View File

@ -2,18 +2,18 @@ import { ACCOUNTS_STORE, RELATIONSHIPS_STORE } from './constants'
import { accountsCache, relationshipsCache } from './cache' import { accountsCache, relationshipsCache } from './cache'
import { getGenericEntityWithId, setGenericEntityWithId } from './helpers' import { getGenericEntityWithId, setGenericEntityWithId } from './helpers'
export async function getAccount(instanceName, accountId) { export async function getAccount (instanceName, accountId) {
return await getGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, accountId) return getGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, accountId)
} }
export async function setAccount(instanceName, account) { export async function setAccount (instanceName, account) {
return await setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, account) return setGenericEntityWithId(ACCOUNTS_STORE, accountsCache, instanceName, account)
} }
export async function getRelationship(instanceName, accountId) { export async function getRelationship (instanceName, accountId) {
return await getGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, accountId) return getGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, accountId)
} }
export async function setRelationship(instanceName, relationship) { export async function setRelationship (instanceName, relationship) {
return await setGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, relationship) return setGenericEntityWithId(RELATIONSHIPS_STORE, relationshipsCache, instanceName, relationship)
} }

View File

@ -31,7 +31,7 @@ if (process.browser && process.env.NODE_ENV !== 'production') {
} }
} }
function getOrCreateInstanceCache(cache, instanceName) { function getOrCreateInstanceCache (cache, instanceName) {
let cached = cache.caches[instanceName] let cached = cache.caches[instanceName]
if (!cached) { if (!cached) {
cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize}) cached = cache.caches[instanceName] = new QuickLRU({maxSize: cache.maxSize})
@ -39,20 +39,20 @@ function getOrCreateInstanceCache(cache, instanceName) {
return cached return cached
} }
export function clearCache(cache, instanceName) { export function clearCache (cache, instanceName) {
delete cache.caches[instanceName] delete cache.caches[instanceName]
} }
export function setInCache(cache, instanceName, key, value) { export function setInCache (cache, instanceName, key, value) {
let instanceCache = getOrCreateInstanceCache(cache, instanceName) let instanceCache = getOrCreateInstanceCache(cache, instanceName)
return instanceCache.set(key, value) return instanceCache.set(key, value)
} }
export function getInCache(cache, instanceName, key) { export function getInCache (cache, instanceName, key) {
let instanceCache = getOrCreateInstanceCache(cache, instanceName) let instanceCache = getOrCreateInstanceCache(cache, instanceName)
return instanceCache.get(key) return instanceCache.get(key)
} }
export function hasInCache(cache, instanceName, key) { export function hasInCache (cache, instanceName, key) {
let instanceCache = getOrCreateInstanceCache(cache, instanceName) let instanceCache = getOrCreateInstanceCache(cache, instanceName)
let res = instanceCache.has(key) let res = instanceCache.has(key)
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
@ -63,4 +63,4 @@ export function hasInCache(cache, instanceName, key) {
} }
} }
return res return res
} }

View File

@ -1,9 +1,9 @@
import { accountsCache, clearCache, metaCache, statusesCache } from './cache' import { accountsCache, clearCache, metaCache, statusesCache } from './cache'
import { deleteDatabase } from './databaseLifecycle' import { deleteDatabase } from './databaseLifecycle'
export async function clearDatabaseForInstance(instanceName) { export async function clearDatabaseForInstance (instanceName) {
clearCache(statusesCache, instanceName) clearCache(statusesCache, instanceName)
clearCache(accountsCache, instanceName) clearCache(accountsCache, instanceName)
clearCache(metaCache, instanceName) clearCache(metaCache, instanceName)
await deleteDatabase(instanceName) await deleteDatabase(instanceName)
} }

View File

@ -4,4 +4,4 @@ export const META_STORE = 'meta'
export const ACCOUNTS_STORE = 'accounts' export const ACCOUNTS_STORE = 'accounts'
export const RELATIONSHIPS_STORE = 'relationships' export const RELATIONSHIPS_STORE = 'relationships'
export const NOTIFICATIONS_STORE = 'notifications' export const NOTIFICATIONS_STORE = 'notifications'
export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines' export const NOTIFICATION_TIMELINES_STORE = 'notification_timelines'

View File

@ -1,3 +1,3 @@
import * as database from './databaseCore' import * as database from './databaseCore'
export { database } export { database }

View File

@ -1,4 +1,4 @@
export * from './accountsAndRelationships' export * from './accountsAndRelationships'
export * from './clear' export * from './clear'
export * from './meta' export * from './meta'
export * from './timelines' export * from './timelines'

View File

@ -1,8 +1,3 @@
const openReqs = {}
const databaseCache = {}
const DB_VERSION = 1
import { import {
META_STORE, META_STORE,
STATUS_TIMELINES_STORE, STATUS_TIMELINES_STORE,
@ -13,7 +8,12 @@ import {
NOTIFICATION_TIMELINES_STORE NOTIFICATION_TIMELINES_STORE
} from './constants' } from './constants'
export function getDatabase(instanceName) { const openReqs = {}
const databaseCache = {}
const DB_VERSION = 1
export function getDatabase (instanceName) {
if (!instanceName) { if (!instanceName) {
throw new Error('instanceName is undefined in getDatabase()') throw new Error('instanceName is undefined in getDatabase()')
} }
@ -29,7 +29,7 @@ export function getDatabase(instanceName) {
console.log('idb blocked') console.log('idb blocked')
} }
req.onupgradeneeded = (e) => { req.onupgradeneeded = (e) => {
let db = req.result; let db = req.result
db.createObjectStore(META_STORE, {keyPath: 'key'}) db.createObjectStore(META_STORE, {keyPath: 'key'})
db.createObjectStore(STATUSES_STORE, {keyPath: 'id'}) db.createObjectStore(STATUSES_STORE, {keyPath: 'id'})
db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'}) db.createObjectStore(ACCOUNTS_STORE, {keyPath: 'id'})
@ -45,26 +45,26 @@ export function getDatabase(instanceName) {
return databaseCache[instanceName] return databaseCache[instanceName]
} }
export async function dbPromise(db, storeName, readOnlyOrReadWrite, cb) { export async function dbPromise (db, storeName, readOnlyOrReadWrite, cb) {
return await new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, readOnlyOrReadWrite) const tx = db.transaction(storeName, readOnlyOrReadWrite)
let store = typeof storeName === 'string' ? let store = typeof storeName === 'string'
tx.objectStore(storeName) : ? tx.objectStore(storeName)
storeName.map(name => tx.objectStore(name)) : storeName.map(name => tx.objectStore(name))
let res let res
cb(store, (result) => { cb(store, (result) => {
res = result res = result
}) })
tx.oncomplete = () => resolve(res) tx.oncomplete = () => resolve(res)
tx.onerror = () => reject(tx.error.name + ' ' + tx.error.message) tx.onerror = () => reject(tx.error)
}) })
} }
export function deleteDatabase(instanceName) { export function deleteDatabase (instanceName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// close any open requests // close any open requests
let openReq = openReqs[instanceName]; let openReq = openReqs[instanceName]
if (openReq && openReq.result) { if (openReq && openReq.result) {
openReq.result.close() openReq.result.close()
} }
@ -72,6 +72,6 @@ export function deleteDatabase(instanceName) {
delete databaseCache[instanceName] delete databaseCache[instanceName]
let req = indexedDB.deleteDatabase(instanceName) let req = indexedDB.deleteDatabase(instanceName)
req.onsuccess = () => resolve() req.onsuccess = () => resolve()
req.onerror = () => reject(req.error.name + ' ' + req.error.message) req.onerror = () => reject(req.error)
}) })
} }

View File

@ -1,7 +1,7 @@
import { dbPromise, getDatabase } from './databaseLifecycle' import { dbPromise, getDatabase } from './databaseLifecycle'
import { getInCache, hasInCache, setInCache } from './cache' import { getInCache, hasInCache, setInCache } from './cache'
export async function getGenericEntityWithId(store, cache, instanceName, id) { export async function getGenericEntityWithId (store, cache, instanceName, id) {
if (hasInCache(cache, instanceName, id)) { if (hasInCache(cache, instanceName, id)) {
return getInCache(cache, instanceName, id) return getInCache(cache, instanceName, id)
} }
@ -13,10 +13,10 @@ export async function getGenericEntityWithId(store, cache, instanceName, id) {
return result return result
} }
export async function setGenericEntityWithId(store, cache, instanceName, entity) { export async function setGenericEntityWithId (store, cache, instanceName, entity) {
setInCache(cache, instanceName, entity.id, entity) setInCache(cache, instanceName, entity.id, entity)
const db = await getDatabase(instanceName) const db = await getDatabase(instanceName)
return await dbPromise(db, store, 'readwrite', (store) => { return dbPromise(db, store, 'readwrite', (store) => {
store.put(entity) store.put(entity)
}) })
} }

View File

@ -2,7 +2,7 @@ import { dbPromise, getDatabase } from './databaseLifecycle'
import { META_STORE } from './constants' import { META_STORE } from './constants'
import { metaCache, hasInCache, getInCache, setInCache } from './cache' import { metaCache, hasInCache, getInCache, setInCache } from './cache'
async function getMetaProperty(instanceName, key) { async function getMetaProperty (instanceName, key) {
if (hasInCache(metaCache, instanceName, key)) { if (hasInCache(metaCache, instanceName, key)) {
return getInCache(metaCache, instanceName, key) return getInCache(metaCache, instanceName, key)
} }
@ -16,10 +16,10 @@ async function getMetaProperty(instanceName, key) {
return result return result
} }
async function setMetaProperty(instanceName, key, value) { async function setMetaProperty (instanceName, key, value) {
setInCache(metaCache, instanceName, key, value) setInCache(metaCache, instanceName, key, value)
const db = await getDatabase(instanceName) const db = await getDatabase(instanceName)
return await dbPromise(db, META_STORE, 'readwrite', (store) => { return dbPromise(db, META_STORE, 'readwrite', (store) => {
store.put({ store.put({
key: key, key: key,
value: value value: value
@ -27,26 +27,26 @@ async function setMetaProperty(instanceName, key, value) {
}) })
} }
export async function getInstanceVerifyCredentials(instanceName) { export async function getInstanceVerifyCredentials (instanceName) {
return await getMetaProperty(instanceName, 'verifyCredentials') return getMetaProperty(instanceName, 'verifyCredentials')
} }
export async function setInstanceVerifyCredentials(instanceName, value) { export async function setInstanceVerifyCredentials (instanceName, value) {
return await setMetaProperty(instanceName, 'verifyCredentials', value) return setMetaProperty(instanceName, 'verifyCredentials', value)
} }
export async function getInstanceInfo(instanceName) { export async function getInstanceInfo (instanceName) {
return await getMetaProperty(instanceName, 'instance') return getMetaProperty(instanceName, 'instance')
} }
export async function setInstanceInfo(instanceName, value) { export async function setInstanceInfo (instanceName, value) {
return await setMetaProperty(instanceName, 'instance', value) return setMetaProperty(instanceName, 'instance', value)
} }
export async function getLists(instanceName) { export async function getLists (instanceName) {
return await getMetaProperty(instanceName, 'lists') return getMetaProperty(instanceName, 'lists')
} }
export async function setLists(instanceName, value) { export async function setLists (instanceName, value) {
return await setMetaProperty(instanceName, 'lists', value) return setMetaProperty(instanceName, 'lists', value)
} }

View File

@ -7,17 +7,17 @@ import {
} from './constants' } from './constants'
import { getGenericEntityWithId } from './helpers' import { getGenericEntityWithId } from './helpers'
function createKeyRange(timeline, maxId) { function createKeyRange (timeline, maxId) {
let negBigInt = maxId && toReversePaddedBigInt(maxId) let negBigInt = maxId && toReversePaddedBigInt(maxId)
let start = negBigInt ? (timeline + '\u0000' + negBigInt) : (timeline + '\u0000') let start = negBigInt ? (timeline + '\u0000' + negBigInt) : (timeline + '\u0000')
let end = timeline + '\u0000\uffff' let end = timeline + '\u0000\uffff'
return IDBKeyRange.bound(start, end, false, false) return IDBKeyRange.bound(start, end, false, false)
} }
async function getNotificationTimeline(instanceName, timeline, maxId, limit) { async function getNotificationTimeline (instanceName, timeline, maxId, limit) {
let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE] let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE]
const db = await getDatabase(instanceName) const db = await getDatabase(instanceName)
return await dbPromise(db, storeNames, 'readonly', (stores, callback) => { return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, notificationsStore ] = stores let [ timelineStore, notificationsStore ] = stores
let keyRange = createKeyRange(timeline, maxId) let keyRange = createKeyRange(timeline, maxId)
@ -34,10 +34,10 @@ async function getNotificationTimeline(instanceName, timeline, maxId, limit) {
}) })
} }
async function getStatusTimeline(instanceName, timeline, maxId, limit) { async function getStatusTimeline (instanceName, timeline, maxId, limit) {
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE] let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE]
const db = await getDatabase(instanceName) const db = await getDatabase(instanceName)
return await dbPromise(db, storeNames, 'readonly', (stores, callback) => { return dbPromise(db, storeNames, 'readonly', (stores, callback) => {
let [ timelineStore, statusesStore ] = stores let [ timelineStore, statusesStore ] = stores
let keyRange = createKeyRange(timeline, maxId) let keyRange = createKeyRange(timeline, maxId)
@ -54,18 +54,18 @@ async function getStatusTimeline(instanceName, timeline, maxId, limit) {
}) })
} }
export async function getTimeline(instanceName, timeline, maxId = null, limit = 20) { export async function getTimeline (instanceName, timeline, maxId = null, limit = 20) {
return timeline === 'notifications' ? return timeline === 'notifications'
await getNotificationTimeline(instanceName, timeline, maxId, limit) : ? getNotificationTimeline(instanceName, timeline, maxId, limit)
await getStatusTimeline(instanceName, timeline, maxId, limit) : getStatusTimeline(instanceName, timeline, maxId, limit)
} }
function createTimelineId(timeline, id) { function createTimelineId (timeline, id) {
// reverse chronological order, prefixed by timeline // reverse chronological order, prefixed by timeline
return timeline + '\u0000' + toReversePaddedBigInt(id) return timeline + '\u0000' + toReversePaddedBigInt(id)
} }
async function insertTimelineNotifications(instanceName, timeline, notifications) { async function insertTimelineNotifications (instanceName, timeline, notifications) {
let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, ACCOUNTS_STORE] let storeNames = [NOTIFICATION_TIMELINES_STORE, NOTIFICATIONS_STORE, ACCOUNTS_STORE]
for (let notification of notifications) { for (let notification of notifications) {
setInCache(notificationsCache, instanceName, notification.id, notification) setInCache(notificationsCache, instanceName, notification.id, notification)
@ -85,7 +85,7 @@ async function insertTimelineNotifications(instanceName, timeline, notifications
}) })
} }
async function insertTimelineStatuses(instanceName, timeline, statuses) { async function insertTimelineStatuses (instanceName, timeline, statuses) {
let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE] let storeNames = [STATUS_TIMELINES_STORE, STATUSES_STORE, ACCOUNTS_STORE]
for (let status of statuses) { for (let status of statuses) {
setInCache(statusesCache, instanceName, status.id, status) setInCache(statusesCache, instanceName, status.id, status)
@ -111,16 +111,16 @@ async function insertTimelineStatuses(instanceName, timeline, statuses) {
}) })
} }
export async function insertTimelineItems(instanceName, timeline, timelineItems) { export async function insertTimelineItems (instanceName, timeline, timelineItems) {
return timeline === 'notifications' ? return timeline === 'notifications'
await insertTimelineNotifications(instanceName, timeline, timelineItems) : ? insertTimelineNotifications(instanceName, timeline, timelineItems)
await insertTimelineStatuses(instanceName, timeline, timelineItems) : insertTimelineStatuses(instanceName, timeline, timelineItems)
} }
export async function getStatus(instanceName, statusId) { export async function getStatus (instanceName, statusId) {
return await getGenericEntityWithId(STATUSES_STORE, statusesCache, instanceName, statusId) return getGenericEntityWithId(STATUSES_STORE, statusesCache, instanceName, statusId)
} }
export async function getNotification(instanceName, notificationId) { export async function getNotification (instanceName, notificationId) {
return await getGenericEntityWithId(NOTIFICATIONS_STORE, notificationsCache, instanceName, notificationId) return getGenericEntityWithId(NOTIFICATIONS_STORE, notificationsCache, instanceName, notificationId)
} }

View File

@ -11,4 +11,4 @@ export function toReversePaddedBigInt (id) {
res += (9 - parseInt(bigInt.charAt(i), 10)).toString(10) res += (9 - parseInt(bigInt.charAt(i), 10)).toString(10)
} }
return res return res
} }

View File

@ -1,2 +1,2 @@
export const DEFAULT_MEDIA_WIDTH = 300 export const DEFAULT_MEDIA_WIDTH = 300
export const DEFAULT_MEDIA_HEIGHT = 200 export const DEFAULT_MEDIA_HEIGHT = 200

View File

@ -29,4 +29,4 @@ const themes = [
} }
] ]
export { themes } export { themes }

View File

@ -4,4 +4,4 @@ const timelines = {
federated: { name: 'federated', label: 'Federated' } federated: { name: 'federated', label: 'Federated' }
} }
export { timelines } export { timelines }

View File

@ -2,12 +2,12 @@ import { Store } from 'svelte/store'
const LS = process.browser && localStorage const LS = process.browser && localStorage
function safeParse(str) { function safeParse (str) {
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str)) return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
} }
export class LocalStorageStore extends Store { export class LocalStorageStore extends Store {
constructor(state, keysToWatch) { constructor (state, keysToWatch) {
super(state) super(state)
if (!process.browser) { if (!process.browser) {
return return
@ -32,7 +32,7 @@ export class LocalStorageStore extends Store {
}) })
} }
save() { save () {
if (!process.browser) { if (!process.browser) {
return return
} }
@ -41,4 +41,4 @@ export class LocalStorageStore extends Store {
}) })
this._keysToSave = {} this._keysToSave = {}
} }
} }

View File

@ -1,7 +1,7 @@
import { instanceComputations } from './instanceComputations' import { instanceComputations } from './instanceComputations'
import { timelineComputations } from './timelineComputations' import { timelineComputations } from './timelineComputations'
export function computations(store) { export function computations (store) {
instanceComputations(store) instanceComputations(store)
timelineComputations(store) timelineComputations(store)
} }

View File

@ -1,4 +1,4 @@
export function instanceComputations(store) { export function instanceComputations (store) {
store.compute( store.compute(
'isUserLoggedIn', 'isUserLoggedIn',
['currentInstance', 'loggedInInstances'], ['currentInstance', 'loggedInInstances'],
@ -70,5 +70,4 @@ export function instanceComputations(store) {
return list ? list.title : '' return list ? list.title : ''
} }
) )
}
}

View File

@ -1,4 +1,4 @@
function timelineMixins(Store) { function timelineMixins (Store) {
Store.prototype.setForTimeline = function (instanceName, timelineName, obj) { Store.prototype.setForTimeline = function (instanceName, timelineName, obj) {
let timelines = this.get('timelines') || {} let timelines = this.get('timelines') || {}
let timelineData = timelines[instanceName] || {} let timelineData = timelines[instanceName] || {}
@ -14,6 +14,6 @@ function timelineMixins(Store) {
} }
} }
export function mixins(Store) { export function mixins (Store) {
timelineMixins(Store) timelineMixins(Store)
} }

View File

@ -1,11 +1,11 @@
import { updateVerifyCredentialsForInstance } from '../_actions/instances' import { updateVerifyCredentialsForInstance } from '../_actions/instances'
import { updateLists } from '../_actions/lists' import { updateLists } from '../_actions/lists'
export function observers(store) { export function observers (store) {
store.observe('currentInstance', (currentInstance) => { store.observe('currentInstance', (currentInstance) => {
if (currentInstance) { if (currentInstance) {
updateVerifyCredentialsForInstance(currentInstance) updateVerifyCredentialsForInstance(currentInstance)
updateLists() updateLists()
} }
}) })
} }

View File

@ -4,20 +4,20 @@ import { mixins } from './mixins'
import { LocalStorageStore } from './LocalStorageStore' import { LocalStorageStore } from './LocalStorageStore'
const KEYS_TO_STORE_IN_LOCAL_STORAGE = new Set([ const KEYS_TO_STORE_IN_LOCAL_STORAGE = new Set([
"currentInstance", 'currentInstance',
"currentRegisteredInstance", 'currentRegisteredInstance',
"currentRegisteredInstanceName", 'currentRegisteredInstanceName',
"instanceNameInSearch", 'instanceNameInSearch',
"instanceThemes", 'instanceThemes',
"loggedInInstances", 'loggedInInstances',
"loggedInInstancesInOrder", 'loggedInInstancesInOrder',
"autoplayGifs", 'autoplayGifs',
"markMediaAsSensitive", 'markMediaAsSensitive',
"pinnedPages" 'pinnedPages'
]) ])
class PinaforeStore extends LocalStorageStore { class PinaforeStore extends LocalStorageStore {
constructor(state) { constructor (state) {
super(state, KEYS_TO_STORE_IN_LOCAL_STORAGE) super(state, KEYS_TO_STORE_IN_LOCAL_STORAGE)
} }
} }
@ -45,4 +45,4 @@ if (process.browser && process.env.NODE_ENV !== 'production') {
window.store = store // for debugging window.store = store // for debugging
} }
export { store } export { store }

View File

@ -1,4 +1,4 @@
export function timelineComputations(store) { export function timelineComputations (store) {
store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'], store.compute('currentTimelineData', ['currentInstance', 'currentTimeline', 'timelines'],
(currentInstance, currentTimeline, timelines) => { (currentInstance, currentTimeline, timelines) => {
return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {} return ((timelines && timelines[currentInstance]) || {})[currentTimeline] || {}
@ -8,4 +8,4 @@ export function timelineComputations(store) {
store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate) store.compute('runningUpdate', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.runningUpdate)
store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized) store.compute('initialized', ['currentTimelineData'], (currentTimelineData) => currentTimelineData.initialized)
store.compute('lastTimelineItemId', ['timelineItemIds'], (timelineItemIds) => timelineItemIds && timelineItemIds.length && timelineItemIds[timelineItemIds.length - 1]) store.compute('lastTimelineItemId', ['timelineItemIds'], (timelineItemIds) => timelineItemIds && timelineItemIds.length && timelineItemIds[timelineItemIds.length - 1])
} }

View File

@ -2,7 +2,7 @@
import { getRectFromEntry } from './getRectFromEntry' import { getRectFromEntry } from './getRectFromEntry'
class AsyncLayout { class AsyncLayout {
constructor(generateKeyFromNode) { constructor (generateKeyFromNode) {
this._onIntersectionCallbacks = {} this._onIntersectionCallbacks = {}
this._intersectionObserver = new IntersectionObserver(entries => { this._intersectionObserver = new IntersectionObserver(entries => {
@ -13,7 +13,7 @@ class AsyncLayout {
}) })
} }
observe(key, node, callback) { observe (key, node, callback) {
if (!node) { if (!node) {
return return
} }
@ -26,7 +26,7 @@ class AsyncLayout {
} }
} }
unobserve(key, node) { unobserve (key, node) {
if (key in this._onIntersectionCallbacks) { if (key in this._onIntersectionCallbacks) {
return return
} }
@ -39,7 +39,7 @@ class AsyncLayout {
delete this._onIntersectionCallbacks[key] delete this._onIntersectionCallbacks[key]
} }
disconnect() { disconnect () {
if (this._intersectionObserver) { if (this._intersectionObserver) {
this._intersectionObserver.disconnect() this._intersectionObserver.disconnect()
this._intersectionObserver = null this._intersectionObserver = null
@ -47,4 +47,4 @@ class AsyncLayout {
} }
} }
export { AsyncLayout } export { AsyncLayout }

View File

@ -6,37 +6,37 @@ import QuickLRU from 'quick-lru'
import { mark, stop } from './marks' import { mark, stop } from './marks'
export class RealmStore extends Store { export class RealmStore extends Store {
constructor(init, maxSize) { constructor (init, maxSize) {
super(init) super(init)
this.set({realms: new QuickLRU({maxSize: maxSize})}) this.set({realms: new QuickLRU({maxSize: maxSize})})
this._batches = {} this._batches = {}
} }
setCurrentRealm(realm) { setCurrentRealm (realm) {
this.set({currentRealm: realm}) this.set({currentRealm: realm})
} }
setForRealm(obj) { setForRealm (obj) {
let realmName = this.get('currentRealm') let realmName = this.get('currentRealm')
let realms = this.get('realms') let realms = this.get('realms')
realms.set(realmName, Object.assign(realms.get(realmName) || {}, obj)) realms.set(realmName, Object.assign(realms.get(realmName) || {}, obj))
this.set({realms: realms}) this.set({realms: realms})
} }
computeForRealm(key, defaultValue) { computeForRealm (key, defaultValue) {
this.compute(key, this.compute(key,
['realms', 'currentRealm'], ['realms', 'currentRealm'],
(realms, currentRealm) => { (realms, currentRealm) => {
let realmData = realms.get(currentRealm) let realmData = realms.get(currentRealm)
return (realmData && realmData[key]) || defaultValue return (realmData && realmData[key]) || defaultValue
}) })
} }
/* /*
* Update several values at once in a realm, assuming the key points * Update several values at once in a realm, assuming the key points
* to a plain old javascript object. * to a plain old javascript object.
*/ */
batchUpdateForRealm(key, subKey, value) { batchUpdateForRealm (key, subKey, value) {
let realm = this.get('currentRealm') let realm = this.get('currentRealm')
let realmBatches = this._batches[realm] let realmBatches = this._batches[realm]
if (!realmBatches) { if (!realmBatches) {
@ -69,4 +69,4 @@ export class RealmStore extends Store {
stop('batchUpdate') stop('batchUpdate')
}) })
} }
} }

View File

@ -1,5 +1,5 @@
export async function post(url, body) { export async function post (url, body) {
return await (await fetch(url, { return (await fetch(url, {
method: 'POST', method: 'POST',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
@ -9,7 +9,7 @@ export async function post(url, body) {
})).json() })).json()
} }
export function paramsString(paramsObject) { export function paramsString (paramsObject) {
let params = new URLSearchParams() let params = new URLSearchParams()
Object.keys(paramsObject).forEach(key => { Object.keys(paramsObject).forEach(key => {
params.set(key, paramsObject[key]) params.set(key, paramsObject[key])
@ -17,11 +17,11 @@ export function paramsString(paramsObject) {
return params.toString() return params.toString()
} }
export async function get(url, headers = {}) { export async function get (url, headers = {}) {
return await (await fetch(url, { return (await fetch(url, {
method: 'GET', method: 'GET',
headers: Object.assign(headers, { headers: Object.assign(headers, {
'Accept': 'application/json', 'Accept': 'application/json'
}) })
})).json() })).json()
} }

View File

@ -1,6 +1,6 @@
// Merge two arrays, assuming both input arrays have the same order // Merge two arrays, assuming both input arrays have the same order
// and items are comparable // and items are comparable
export function mergeArrays(leftArray, rightArray) { export function mergeArrays (leftArray, rightArray) {
let leftIndex = 0 let leftIndex = 0
let rightIndex = 0 let rightIndex = 0
let merged = [] let merged = []
@ -30,4 +30,4 @@ export function mergeArrays(leftArray, rightArray) {
} }
} }
return merged return merged
} }

View File

@ -18,12 +18,12 @@ export function imgLoad (node, callback) {
} }
} }
export function mouseover(node, callback) { export function mouseover (node, callback) {
function onMouseEnter() { function onMouseEnter () {
callback(true) callback(true) // eslint-disable-line
} }
function onMouseLeave() { function onMouseLeave () {
callback(false) callback(false) // eslint-disable-line
} }
node.addEventListener('mouseenter', onMouseEnter) node.addEventListener('mouseenter', onMouseEnter)
node.addEventListener('mouseleave', onMouseLeave) node.addEventListener('mouseleave', onMouseLeave)
@ -33,4 +33,4 @@ export function mouseover(node, callback) {
node.removeEventListener('mouseleave', onMouseLeave) node.removeEventListener('mouseleave', onMouseLeave)
} }
} }
} }

View File

@ -1,23 +1,23 @@
export const isFullscreen = () => !!(document.fullscreenElement || export const isFullscreen = () => !!(document.fullscreenElement ||
document.webkitFullscreenElement || document.webkitFullscreenElement ||
document.mozFullScreenElement); document.mozFullScreenElement)
export const attachFullscreenListener = (listener) => { export const attachFullscreenListener = (listener) => {
if ('onfullscreenchange' in document) { if ('onfullscreenchange' in document) {
document.addEventListener('fullscreenchange', listener); document.addEventListener('fullscreenchange', listener)
} else if ('onwebkitfullscreenchange' in document) { } else if ('onwebkitfullscreenchange' in document) {
document.addEventListener('webkitfullscreenchange', listener); document.addEventListener('webkitfullscreenchange', listener)
} else if ('onmozfullscreenchange' in document) { } else if ('onmozfullscreenchange' in document) {
document.addEventListener('mozfullscreenchange', listener); document.addEventListener('mozfullscreenchange', listener)
} }
}; }
export const detachFullscreenListener = (listener) => { export const detachFullscreenListener = (listener) => {
if ('onfullscreenchange' in document) { if ('onfullscreenchange' in document) {
document.removeEventListener('fullscreenchange', listener); document.removeEventListener('fullscreenchange', listener)
} else if ('onwebkitfullscreenchange' in document) { } else if ('onwebkitfullscreenchange' in document) {
document.removeEventListener('webkitfullscreenchange', listener); document.removeEventListener('webkitfullscreenchange', listener)
} else if ('onmozfullscreenchange' in document) { } else if ('onmozfullscreenchange' in document) {
document.removeEventListener('mozfullscreenchange', listener); document.removeEventListener('mozfullscreenchange', listener)
} }
}; }

View File

@ -1,18 +1,18 @@
// Get the bounding client rect from an IntersectionObserver entry. // Get the bounding client rect from an IntersectionObserver entry.
// This is to work around a bug in Chrome: https://crbug.com/737228 // This is to work around a bug in Chrome: https://crbug.com/737228
let hasBoundingRectBug; let hasBoundingRectBug
export function getRectFromEntry(entry) { export function getRectFromEntry (entry) {
if (typeof hasBoundingRectBug !== 'boolean') { if (typeof hasBoundingRectBug !== 'boolean') {
const boundingRect = entry.target.getBoundingClientRect(); const boundingRect = entry.target.getBoundingClientRect()
const observerRect = entry.boundingClientRect; const observerRect = entry.boundingClientRect
hasBoundingRectBug = boundingRect.height !== observerRect.height || hasBoundingRectBug = boundingRect.height !== observerRect.height ||
boundingRect.top !== observerRect.top || boundingRect.top !== observerRect.top ||
boundingRect.width !== observerRect.width || boundingRect.width !== observerRect.width ||
boundingRect.bottom !== observerRect.bottom || boundingRect.bottom !== observerRect.bottom ||
boundingRect.left !== observerRect.left || boundingRect.left !== observerRect.left ||
boundingRect.right !== observerRect.right; boundingRect.right !== observerRect.right
} }
return hasBoundingRectBug ? entry.target.getBoundingClientRect() : entry.boundingClientRect; return hasBoundingRectBug ? entry.target.getBoundingClientRect() : entry.boundingClientRect
} }

View File

@ -2,15 +2,14 @@ import {
importURLSearchParams, importURLSearchParams,
importIntersectionObserver, importIntersectionObserver,
importRequestIdleCallback, importRequestIdleCallback,
importIndexedDBGetAllShim, importIndexedDBGetAllShim
importDialogPolyfill
} from './asyncModules' } from './asyncModules'
export function loadPolyfills() { export function loadPolyfills () {
return Promise.all([ return Promise.all([
typeof URLSearchParams === 'undefined' && importURLSearchParams(), typeof URLSearchParams === 'undefined' && importURLSearchParams(),
typeof IntersectionObserver === 'undefined' && importIntersectionObserver(), typeof IntersectionObserver === 'undefined' && importIntersectionObserver(),
typeof requestIdleCallback === 'undefined' && importRequestIdleCallback(), typeof requestIdleCallback === 'undefined' && importRequestIdleCallback(),
!IDBObjectStore.prototype.getAll && importIndexedDBGetAllShim() !IDBObjectStore.prototype.getAll && importIndexedDBGetAllShim()
]) ])
} }

View File

@ -11,4 +11,4 @@ const stop = enableMarks ? markyStop : noop
export { export {
mark, mark,
stop stop
} }

View File

@ -19,8 +19,7 @@ const observe = online => {
meta.content = oldTheme || window.__themeColors['default'] meta.content = oldTheme || window.__themeColors['default']
} else { } else {
let offlineThemeColor = window.__themeColors.offline let offlineThemeColor = window.__themeColors.offline
if (meta.content !== offlineThemeColor) if (meta.content !== offlineThemeColor) { oldTheme = meta.content }
oldTheme = meta.content
meta.content = offlineThemeColor meta.content = offlineThemeColor
notifyOffline() notifyOffline()
} }
@ -30,5 +29,5 @@ if (!navigator.onLine) {
observe(false) observe(false)
} }
window.addEventListener('offline', () => observe(false)); window.addEventListener('offline', () => observe(false))
window.addEventListener('online', () => observe(true)); window.addEventListener('online', () => observe(true))

View File

@ -7,7 +7,7 @@ import Queue from 'tiny-queue'
const taskQueue = new Queue() const taskQueue = new Queue()
let runningRequestIdleCallback = false let runningRequestIdleCallback = false
function runTasks(deadline) { function runTasks (deadline) {
while (taskQueue.length && deadline.timeRemaining() > 0) { while (taskQueue.length && deadline.timeRemaining() > 0) {
taskQueue.shift()() taskQueue.shift()()
} }
@ -18,10 +18,10 @@ function runTasks(deadline) {
} }
} }
export function scheduleIdleTask(task) { export function scheduleIdleTask (task) {
taskQueue.push(task) taskQueue.push(task)
if (!runningRequestIdleCallback) { if (!runningRequestIdleCallback) {
runningRequestIdleCallback = true runningRequestIdleCallback = true
requestIdleCallback(runTasks) requestIdleCallback(runTasks)
} }
} }

View File

@ -1,17 +1,17 @@
import { toast } from './toast' import { toast } from './toast'
function onUpdateFound(registration) { function onUpdateFound (registration) {
const newWorker = registration.installing const newWorker = registration.installing
newWorker.addEventListener('statechange', async () => { newWorker.addEventListener('statechange', async () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
toast.say('Update available. Refresh to update.') toast.say('Update available. Refresh to update.')
} }
}); })
} }
if (!location.origin.match('localhost') && 'serviceWorker' in navigator) { if (!location.origin.match('localhost') && 'serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(registration => { navigator.serviceWorker.register('/service-worker.js').then(registration => {
registration.addEventListener('updatefound', () => onUpdateFound(registration)) registration.addEventListener('updatefound', () => onUpdateFound(registration))
}) })
} }

View File

@ -1,4 +1,4 @@
export function replaceAll(string, replacee, replacement) { export function replaceAll (string, replacee, replacement) {
if (!string.length || !replacee.length || !replacement.length) { if (!string.length || !replacee.length || !replacement.length) {
return string return string
} }
@ -13,4 +13,4 @@ export function replaceAll(string, replacee, replacement) {
pos = idx + replacement.length pos = idx + replacement.length
} }
return string return string
} }

View File

@ -1,6 +1,6 @@
// Hit both the cache and the network, setting state for the cached version first, // Hit both the cache and the network, setting state for the cached version first,
// then the network version (as it's assumed to be fresher). Also update the db afterwards. // then the network version (as it's assumed to be fresher). Also update the db afterwards.
export async function cacheFirstUpdateAfter(networkFetcher, dbFetcher, dbUpdater, stateSetter) { export async function cacheFirstUpdateAfter (networkFetcher, dbFetcher, dbUpdater, stateSetter) {
let networkPromise = networkFetcher() // kick off network request immediately let networkPromise = networkFetcher() // kick off network request immediately
let dbResponse let dbResponse
try { try {
@ -15,4 +15,4 @@ export async function cacheFirstUpdateAfter(networkFetcher, dbFetcher, dbUpdater
await fetchAndUpdatePromise await fetchAndUpdatePromise
} }
} }
} }

View File

@ -1,8 +1,8 @@
import { loadCSS } from 'fg-loadcss'; import { loadCSS } from 'fg-loadcss'
let meta = process.browser && document.querySelector('meta[name="theme-color"]') let meta = process.browser && document.querySelector('meta[name="theme-color"]')
export function switchToTheme(themeName) { export function switchToTheme (themeName) {
let clazzList = document.body.classList let clazzList = document.body.classList
for (let i = 0; i < clazzList.length; i++) { for (let i = 0; i < clazzList.length; i++) {
let clazz = clazzList.item(i) let clazz = clazzList.item(i)
@ -16,4 +16,4 @@ export function switchToTheme(themeName) {
clazzList.add(`theme-${themeName}`) clazzList.add(`theme-${themeName}`)
loadCSS(`/theme-${themeName}.css`) loadCSS(`/theme-${themeName}.css`)
} }
} }

View File

@ -15,4 +15,4 @@ if (process.browser) {
} }
} }
export { toast } export { toast }

View File

@ -1,24 +1,23 @@
const fs = require('fs'); const app = require('express')()
const app = require('express')(); const compression = require('compression')
const compression = require('compression'); const sapper = require('sapper')
const sapper = require('sapper'); const serveStatic = require('serve-static')
const static = require('serve-static');
const { PORT = 4002 } = process.env; const { PORT = 4002 } = process.env
// this allows us to do e.g. `fetch('/_api/blog')` on the server // this allows us to do e.g. `fetch('/_api/blog')` on the server
const fetch = require('node-fetch'); const fetch = require('node-fetch')
global.fetch = (url, opts) => { global.fetch = (url, opts) => {
if (url[0] === '/') url = `http://localhost:${PORT}${url}`; if (url[0] === '/') url = `http://localhost:${PORT}${url}`
return fetch(url, opts); return fetch(url, opts)
}; }
app.use(compression({ threshold: 0 })); app.use(compression({ threshold: 0 }))
app.use(static('assets')); app.use(serveStatic('assets'))
app.use(sapper()); app.use(sapper())
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`listening on port ${PORT}`); console.log(`listening on port ${PORT}`)
}); })

View File

@ -1,9 +1,13 @@
/* global __routes__ */
import { init } from 'sapper/runtime.js' import { init } from 'sapper/runtime.js'
import { offlineNotifiction } from '../routes/_utils/offlineNotification' import { offlineNotifiction } from '../routes/_utils/offlineNotification'
import { serviceWorkerClient } from '../routes/_utils/serviceWorkerClient' import { serviceWorkerClient } from '../routes/_utils/serviceWorkerClient'
import { loadPolyfills } from '../routes/_utils/loadPolyfills' import { loadPolyfills } from '../routes/_utils/loadPolyfills'
console.log(offlineNotifiction, serviceWorkerClient)
loadPolyfills().then(() => { loadPolyfills().then(() => {
// `routes` is an array of route objects injected by Sapper // `routes` is an array of route objects injected by Sapper
init(document.querySelector('#sapper'), __routes__) init(document.querySelector('#sapper'), __routes__)
}) })

View File

@ -1,10 +1,12 @@
/* global self, caches, __shell__, __assets__, __routes__ */
const timestamp = '__timestamp__' const timestamp = '__timestamp__'
const ASSETS = `cache${timestamp}` const ASSETS = `cache${timestamp}`
// `shell` is an array of all the files generated by webpack, // `shell` is an array of all the files generated by webpack,
// `assets` is an array of everything in the `assets` directory // `assets` is an array of everything in the `assets` directory
const to_cache = __shell__.concat(__assets__) const toCache = __shell__.concat(__assets__)
const cached = new Set(to_cache) const cached = new Set(toCache)
// `routes` is an array of `{ pattern: RegExp }` objects that // `routes` is an array of `{ pattern: RegExp }` objects that
// match the pages in your app // match the pages in your app
@ -14,7 +16,7 @@ self.addEventListener('install', event => {
event.waitUntil( event.waitUntil(
caches caches
.open(ASSETS) .open(ASSETS)
.then(cache => cache.addAll(to_cache)) .then(cache => cache.addAll(toCache))
.then(() => { .then(() => {
self.skipWaiting() self.skipWaiting()
}) })
@ -46,7 +48,7 @@ self.addEventListener('fetch', event => {
// don't try to handle e.g. data: URIs // don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) { if (!url.protocol.startsWith('http')) {
return return
} }
// always serve assets and webpack-generated files from cache // always serve assets and webpack-generated files from cache

View File

@ -4,70 +4,70 @@ const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') const LodashModuleReplacementPlugin = require('lodash-webpack-plugin')
const isDev = config.dev; const isDev = config.dev
module.exports = { module.exports = {
entry: config.client.entry(), entry: config.client.entry(),
output: config.client.output(), output: config.client.output(),
resolve: { resolve: {
extensions: ['.js', '.html'] extensions: ['.js', '.html']
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.html$/, test: /\.html$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'svelte-loader', loader: 'svelte-loader',
options: { options: {
hydratable: true, hydratable: true,
emitCss: !isDev, emitCss: !isDev,
cascade: false, cascade: false,
store: true store: true
} }
} }
}, },
isDev && { isDev && {
test: /\.css$/, test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' }
]
},
!isDev && {
test: /\.css$/,
/* disable while https://github.com/sveltejs/sapper/issues/79 is open */
/*use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [{ loader: 'css-loader', options: { sourceMap:isDev } }]
}) */
use: [ use: [
{ loader: 'style-loader' }, { loader: 'style-loader' },
{ loader: 'css-loader' } { loader: 'css-loader' }
] ]
} },
].filter(Boolean) !isDev && {
}, test: /\.css$/,
node: { /* disable while https://github.com/sveltejs/sapper/issues/79 is open */
setImmediate: false /* use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [{ loader: 'css-loader', options: { sourceMap:isDev } }]
}) */
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' }
]
}
].filter(Boolean)
}, },
plugins: [ node: {
new webpack.optimize.CommonsChunkPlugin({ setImmediate: false
minChunks: 2, },
async: true, plugins: [
children: true new webpack.optimize.CommonsChunkPlugin({
}) minChunks: 2,
].concat(isDev ? [ async: true,
new webpack.HotModuleReplacementPlugin() children: true
] : [ })
].concat(isDev ? [
new webpack.HotModuleReplacementPlugin()
] : [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.browser': true, 'process.browser': true,
'process.env.NODE_ENV': '"production"' 'process.env.NODE_ENV': '"production"'
}), }),
/* disable while https://github.com/sveltejs/sapper/issues/79 is open */ /* disable while https://github.com/sveltejs/sapper/issues/79 is open */
//new ExtractTextPlugin('main.css'), // new ExtractTextPlugin('main.css'),
new LodashModuleReplacementPlugin(), new LodashModuleReplacementPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(), new webpack.optimize.ModuleConcatenationPlugin(),
new UglifyJSPlugin({ new UglifyJSPlugin({
parallel: true, parallel: true,
uglifyOptions: { uglifyOptions: {
@ -82,11 +82,11 @@ module.exports = {
generateStatsFile: true, generateStatsFile: true,
statsOptions: { statsOptions: {
// allows usage with http://chrisbateman.github.io/webpack-visualizer/ // allows usage with http://chrisbateman.github.io/webpack-visualizer/
chunkModules: true, chunkModules: true
}, },
openAnalyzer: false, openAnalyzer: false,
logLevel: 'silent', // do not bother Webpacker, who runs with --json and parses stdout logLevel: 'silent' // do not bother Webpacker, who runs with --json and parses stdout
}), })
]).filter(Boolean), ]).filter(Boolean),
devtool: isDev && 'inline-source-map' devtool: isDev && 'inline-source-map'
}; }

View File

@ -1,30 +1,27 @@
const config = require('sapper/webpack/config.js'); const config = require('sapper/webpack/config.js')
const webpack = require('webpack');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
module.exports = { module.exports = {
entry: config.server.entry(), entry: config.server.entry(),
output: config.server.output(), output: config.server.output(),
target: 'node', target: 'node',
resolve: { resolve: {
extensions: ['.js', '.html'] extensions: ['.js', '.html']
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.html$/, test: /\.html$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: 'svelte-loader', loader: 'svelte-loader',
options: { options: {
css: false, css: false,
cascade: false, cascade: false,
store: true, store: true,
generate: 'ssr' generate: 'ssr'
} }
} }
} }
] ]
} }
}; }