Merge remote-tracking branch 'origin/develop' into users-statuses-pinia
This commit is contained in:
commit
36672031f9
7 changed files with 308 additions and 80 deletions
1
changelog.d/config-migration.fix
Normal file
1
changelog.d/config-migration.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix legacy settings migration being discarded after it ran.
|
||||||
1
changelog.d/fix-chat-post.fix
Normal file
1
changelog.d/fix-chat-post.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix error dialog when posting in chat
|
||||||
1
changelog.d/media-proxy-sw-cache.fix
Normal file
1
changelog.d/media-proxy-sw-cache.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Prevent service worker from caching media-proxy responses
|
||||||
|
|
@ -576,11 +576,14 @@ const Chat = {
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
onPosted(data) {
|
onPosted(data) {
|
||||||
this.explicitReplyStatus = null
|
// only conversation poster has the returned data
|
||||||
this.$router.push({
|
if (this.isConversation) {
|
||||||
name: 'conversation2',
|
this.explicitReplyStatus = null
|
||||||
params: { statusId: data.id },
|
this.$router.push({
|
||||||
})
|
name: 'conversation2',
|
||||||
|
params: { statusId: data.id },
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleVisibilityChange() {
|
handleVisibilityChange() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
|
||||||
|
|
@ -688,83 +688,13 @@ export const useSyncConfigStore = defineStore('sync_config', {
|
||||||
)
|
)
|
||||||
recent = _wrapData({
|
recent = _wrapData({
|
||||||
flagStorage: { ...flagsTemplate },
|
flagStorage: { ...flagsTemplate },
|
||||||
prefsStorage: { ...defaultState.prefsStorage },
|
prefsStorage: cloneDeep(defaultState.prefsStorage),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
recent = recent && (await _doMigrations(recent, this.setPreference))
|
recent = recent && (await _doMigrations(recent, this.setPreference))
|
||||||
stale = stale && (await _doMigrations(stale, this.setPreference))
|
stale = stale && (await _doMigrations(stale, this.setPreference))
|
||||||
|
|
||||||
// Various migrations
|
|
||||||
console.debug('Migrating from old config')
|
|
||||||
const vuexState = (await storage.getItem('vuex-lz')) ?? {}
|
|
||||||
const config = vuexState.config ?? {}
|
|
||||||
|
|
||||||
const migratedEntries = new Set(config._syncMigration ?? [])
|
|
||||||
console.debug(
|
|
||||||
`Already migrated Values: ${[...migratedEntries].join() || '[none]'}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
Object.entries(oldDefaultConfigSync).forEach(([key, value]) => {
|
|
||||||
const oldValue = config[key]
|
|
||||||
const defaultValue = value
|
|
||||||
|
|
||||||
const present = oldValue !== undefined
|
|
||||||
const migrated = migratedEntries.has(key)
|
|
||||||
const different = !isEqual(oldValue, defaultValue)
|
|
||||||
|
|
||||||
if (present && !migrated && different) {
|
|
||||||
console.debug(`Migrating config ${key}: ${oldValue}`)
|
|
||||||
if (key === 'theme3hacks') {
|
|
||||||
useLocalConfigStore().set({
|
|
||||||
path: 'fontInterface',
|
|
||||||
value: oldValue.fonts.interface,
|
|
||||||
})
|
|
||||||
useLocalConfigStore().set({
|
|
||||||
path: 'fontInput',
|
|
||||||
value: oldValue.fonts.input,
|
|
||||||
})
|
|
||||||
useLocalConfigStore().set({
|
|
||||||
path: 'fontPost',
|
|
||||||
value: oldValue.fonts.post,
|
|
||||||
})
|
|
||||||
useLocalConfigStore().set({
|
|
||||||
path: 'fontMonospace',
|
|
||||||
value: oldValue.fonts.monospace,
|
|
||||||
})
|
|
||||||
useSyncConfigStore().setSimplePrefAndSave({
|
|
||||||
path: 'underlay',
|
|
||||||
value: oldValue.underlay,
|
|
||||||
})
|
|
||||||
} else if (key == 'muteWords') {
|
|
||||||
oldValue.forEach((word, order) => {
|
|
||||||
const uniqueId = uuidv4()
|
|
||||||
|
|
||||||
useSyncConfigStore().setPreference({
|
|
||||||
path: 'simple.muteFilters.' + uniqueId,
|
|
||||||
value: {
|
|
||||||
type: 'word',
|
|
||||||
value: word,
|
|
||||||
name: word,
|
|
||||||
enabled: true,
|
|
||||||
expires: null,
|
|
||||||
hide: false,
|
|
||||||
order,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setPreference({ path: `simple.${key}`, value: oldValue })
|
|
||||||
}
|
|
||||||
migratedEntries.add(key)
|
|
||||||
needUpload = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
config._syncMigration = [...migratedEntries]
|
|
||||||
vuexState.config = config
|
|
||||||
storage.setItem('vuex-lz', vuexState)
|
|
||||||
|
|
||||||
if (!needUpload && recent && stale) {
|
if (!needUpload && recent && stale) {
|
||||||
console.debug('Checking if data needs merging...')
|
console.debug('Checking if data needs merging...')
|
||||||
// discarding timestamps and versions
|
// discarding timestamps and versions
|
||||||
|
|
@ -795,14 +725,99 @@ export const useSyncConfigStore = defineStore('sync_config', {
|
||||||
recent.flagStorage = { ...flagsTemplate, ...totalFlags }
|
recent.flagStorage = { ...flagsTemplate, ...totalFlags }
|
||||||
recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
|
recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
|
||||||
|
|
||||||
this.dirty = dirty || needUpload
|
|
||||||
this.cache = recent
|
this.cache = recent
|
||||||
|
this.flagStorage = this.cache.flagStorage
|
||||||
|
this.prefsStorage = this.cache.prefsStorage
|
||||||
|
if (!Array.isArray(this.prefsStorage._journal)) {
|
||||||
|
this.prefsStorage._journal = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Various migrations
|
||||||
|
console.debug('Migrating from old config')
|
||||||
|
const vuexState = (await storage.getItem('vuex-lz')) ?? {}
|
||||||
|
const config = vuexState.config ?? {}
|
||||||
|
|
||||||
|
const migratedEntries = new Set(config._syncMigration ?? [])
|
||||||
|
console.debug(
|
||||||
|
`Already migrated Values: ${[...migratedEntries].join() || '[none]'}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
Object.entries(oldDefaultConfigSync).forEach(([key, value]) => {
|
||||||
|
const oldValue = config[key]
|
||||||
|
const defaultValue = value
|
||||||
|
const migrated = migratedEntries.has(key)
|
||||||
|
const preferencePath =
|
||||||
|
key === 'theme3hacks' ? 'simple.underlay' : `simple.${key}`
|
||||||
|
|
||||||
|
const present = oldValue !== undefined
|
||||||
|
const different = !isEqual(oldValue, defaultValue)
|
||||||
|
const preferenceHandled =
|
||||||
|
get(this.prefsStorage, preferencePath) !== undefined ||
|
||||||
|
this.prefsStorage._journal.some(
|
||||||
|
(entry) =>
|
||||||
|
entry?.path === preferencePath ||
|
||||||
|
entry?.path?.startsWith?.(`${preferencePath}.`),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (present && different && !preferenceHandled) {
|
||||||
|
console.debug(`Migrating config ${key}: ${oldValue}`)
|
||||||
|
if (key === 'theme3hacks') {
|
||||||
|
if (!migrated) {
|
||||||
|
useLocalConfigStore().set({
|
||||||
|
path: 'fontInterface',
|
||||||
|
value: oldValue.fonts.interface,
|
||||||
|
})
|
||||||
|
useLocalConfigStore().set({
|
||||||
|
path: 'fontInput',
|
||||||
|
value: oldValue.fonts.input,
|
||||||
|
})
|
||||||
|
useLocalConfigStore().set({
|
||||||
|
path: 'fontPosts',
|
||||||
|
value: oldValue.fonts.post,
|
||||||
|
})
|
||||||
|
useLocalConfigStore().set({
|
||||||
|
path: 'fontMonospace',
|
||||||
|
value: oldValue.fonts.monospace,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setPreference({
|
||||||
|
path: 'simple.underlay',
|
||||||
|
value: oldValue.underlay,
|
||||||
|
})
|
||||||
|
} else if (key == 'muteWords') {
|
||||||
|
oldValue.forEach((word, order) => {
|
||||||
|
const uniqueId = uuidv4()
|
||||||
|
|
||||||
|
this.setPreference({
|
||||||
|
path: 'simple.muteFilters.' + uniqueId,
|
||||||
|
value: {
|
||||||
|
type: 'word',
|
||||||
|
value: word,
|
||||||
|
name: word,
|
||||||
|
enabled: true,
|
||||||
|
expires: null,
|
||||||
|
hide: false,
|
||||||
|
order,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.setPreference({ path: preferencePath, value: oldValue })
|
||||||
|
}
|
||||||
|
migratedEntries.add(key)
|
||||||
|
needUpload = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
config._syncMigration = [...migratedEntries]
|
||||||
|
vuexState.config = config
|
||||||
|
storage.setItem('vuex-lz', vuexState)
|
||||||
|
|
||||||
|
this.dirty = dirty || needUpload
|
||||||
// set local timestamp to smaller one if we don't have any changes
|
// set local timestamp to smaller one if we don't have any changes
|
||||||
if (stale && recent && !this.dirty) {
|
if (stale && recent && !this.dirty) {
|
||||||
this.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
|
this.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
|
||||||
}
|
}
|
||||||
this.flagStorage = this.cache.flagStorage
|
|
||||||
this.prefsStorage = this.cache.prefsStorage
|
|
||||||
this.pushSyncConfig()
|
this.pushSyncConfig()
|
||||||
},
|
},
|
||||||
pushSyncConfig({ force = false } = {}) {
|
pushSyncConfig({ force = false } = {}) {
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,9 @@ const isNotMedia = (req) => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
return !url.pathname.startsWith('/media/')
|
return (
|
||||||
|
!url.pathname.startsWith('/media/') && !url.pathname.startsWith('/proxy/')
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const isAsset = (req) => {
|
const isAsset = (req) => {
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
import { createPinia, setActivePinia } from 'pinia'
|
import { createPinia, setActivePinia } from 'pinia'
|
||||||
|
|
||||||
|
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||||
import {
|
import {
|
||||||
_getAllFlags,
|
_getAllFlags,
|
||||||
_getRecentData,
|
_getRecentData,
|
||||||
|
|
@ -16,11 +17,17 @@ import {
|
||||||
VERSION,
|
VERSION,
|
||||||
} from 'src/stores/sync_config.js'
|
} from 'src/stores/sync_config.js'
|
||||||
|
|
||||||
|
import { storage } from 'src/lib/storage.js'
|
||||||
|
|
||||||
describe('The SyncConfig store', () => {
|
describe('The SyncConfig store', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createPinia())
|
setActivePinia(createPinia())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
describe('mutations', () => {
|
describe('mutations', () => {
|
||||||
describe('initSyncConfig', () => {
|
describe('initSyncConfig', () => {
|
||||||
const user = {
|
const user = {
|
||||||
|
|
@ -163,6 +170,204 @@ describe('The SyncConfig store', () => {
|
||||||
expect(store.cache._timestamp).to.be.a('number')
|
expect(store.cache._timestamp).to.be.a('number')
|
||||||
expect(store.cache.flagStorage).to.eql(defaultState.flagStorage)
|
expect(store.cache.flagStorage).to.eql(defaultState.flagStorage)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should recover legacy preferences marked as migrated', async () => {
|
||||||
|
vi.spyOn(storage, 'getItem').mockResolvedValue({
|
||||||
|
config: {
|
||||||
|
_syncMigration: ['modalOnUnfollow'],
|
||||||
|
modalOnUnfollow: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||||
|
const store = useSyncConfigStore()
|
||||||
|
store.pushSyncConfig = vi.fn()
|
||||||
|
|
||||||
|
await store.initSyncConfig({ ...user })
|
||||||
|
|
||||||
|
expect(store.prefsStorage.simple.modalOnUnfollow).to.eql(true)
|
||||||
|
expect(store.cache.prefsStorage.simple.modalOnUnfollow).to.eql(true)
|
||||||
|
expect(store.prefsStorage._journal).to.deep.include({
|
||||||
|
path: 'simple.modalOnUnfollow',
|
||||||
|
operation: 'set',
|
||||||
|
args: [true],
|
||||||
|
timestamp: store.prefsStorage._journal[0].timestamp,
|
||||||
|
})
|
||||||
|
expect(store.dirty).to.eql(true)
|
||||||
|
expect(store.pushSyncConfig).toHaveBeenCalledOnce()
|
||||||
|
expect(defaultState.prefsStorage.simple.modalOnUnfollow).to.eql(
|
||||||
|
undefined,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve an explicit synced preference during recovery', async () => {
|
||||||
|
vi.spyOn(storage, 'getItem').mockResolvedValue({
|
||||||
|
config: {
|
||||||
|
_syncMigration: ['modalOnRepeat'],
|
||||||
|
modalOnRepeat: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||||
|
const store = useSyncConfigStore()
|
||||||
|
store.pushSyncConfig = () => {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.initSyncConfig({
|
||||||
|
...user,
|
||||||
|
storage: {
|
||||||
|
_timestamp: 1,
|
||||||
|
_version: VERSION,
|
||||||
|
flagStorage: cloneDeep(defaultState.flagStorage),
|
||||||
|
prefsStorage: {
|
||||||
|
...cloneDeep(defaultState.prefsStorage),
|
||||||
|
simple: {
|
||||||
|
...cloneDeep(defaultState.prefsStorage.simple),
|
||||||
|
modalOnRepeat: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(store.prefsStorage.simple.modalOnRepeat).to.eql(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve a journaled preference removal during recovery', async () => {
|
||||||
|
vi.spyOn(storage, 'getItem').mockResolvedValue({
|
||||||
|
config: {
|
||||||
|
_syncMigration: ['modalOnMute'],
|
||||||
|
modalOnMute: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||||
|
const store = useSyncConfigStore()
|
||||||
|
store.pushSyncConfig = () => {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.initSyncConfig({
|
||||||
|
...user,
|
||||||
|
storage: {
|
||||||
|
_timestamp: 1,
|
||||||
|
_version: VERSION,
|
||||||
|
flagStorage: cloneDeep(defaultState.flagStorage),
|
||||||
|
prefsStorage: {
|
||||||
|
...cloneDeep(defaultState.prefsStorage),
|
||||||
|
_journal: [
|
||||||
|
{
|
||||||
|
path: 'simple.modalOnMute',
|
||||||
|
operation: 'unset',
|
||||||
|
args: [],
|
||||||
|
timestamp: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(store.prefsStorage.simple.modalOnMute).to.eql(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should preserve a journaled nested preference during recovery', async () => {
|
||||||
|
vi.spyOn(storage, 'getItem').mockResolvedValue({
|
||||||
|
config: {
|
||||||
|
_syncMigration: ['notificationVisibility'],
|
||||||
|
notificationVisibility: {
|
||||||
|
...cloneDeep(
|
||||||
|
defaultState.prefsStorage.simple.notificationVisibility,
|
||||||
|
),
|
||||||
|
likes: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||||
|
const store = useSyncConfigStore()
|
||||||
|
const setPreference = vi.spyOn(store, 'setPreference')
|
||||||
|
store.pushSyncConfig = () => {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.initSyncConfig({
|
||||||
|
...user,
|
||||||
|
storage: {
|
||||||
|
_timestamp: 1,
|
||||||
|
_version: VERSION,
|
||||||
|
flagStorage: cloneDeep(defaultState.flagStorage),
|
||||||
|
prefsStorage: {
|
||||||
|
...cloneDeep(defaultState.prefsStorage),
|
||||||
|
_journal: [
|
||||||
|
{
|
||||||
|
path: 'simple.notificationVisibility.likes',
|
||||||
|
operation: 'unset',
|
||||||
|
args: [],
|
||||||
|
timestamp: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(setPreference).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should recover with a malformed synced journal', async () => {
|
||||||
|
vi.spyOn(storage, 'getItem').mockResolvedValue({
|
||||||
|
config: {
|
||||||
|
_syncMigration: ['modalOnUnfollow'],
|
||||||
|
modalOnUnfollow: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||||
|
const store = useSyncConfigStore()
|
||||||
|
store.pushSyncConfig = () => {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.initSyncConfig({
|
||||||
|
...user,
|
||||||
|
storage: {
|
||||||
|
_timestamp: 1,
|
||||||
|
_version: VERSION,
|
||||||
|
flagStorage: cloneDeep(defaultState.flagStorage),
|
||||||
|
prefsStorage: {
|
||||||
|
...cloneDeep(defaultState.prefsStorage),
|
||||||
|
_journal: [{ path: 1, operation: 'set' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(store.prefsStorage.simple.modalOnUnfollow).to.eql(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not replay local theme migration during recovery', async () => {
|
||||||
|
vi.spyOn(storage, 'getItem').mockResolvedValue({
|
||||||
|
config: {
|
||||||
|
_syncMigration: ['theme3hacks'],
|
||||||
|
theme3hacks: {
|
||||||
|
underlay: 'grid',
|
||||||
|
fonts: {
|
||||||
|
interface: 'Legacy interface',
|
||||||
|
input: 'Legacy input',
|
||||||
|
post: 'Legacy posts',
|
||||||
|
monospace: 'Legacy monospace',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
vi.spyOn(storage, 'setItem').mockResolvedValue()
|
||||||
|
const localStore = useLocalConfigStore()
|
||||||
|
localStore.set({ path: 'fontInterface', value: 'Current interface' })
|
||||||
|
const store = useSyncConfigStore()
|
||||||
|
store.pushSyncConfig = () => {
|
||||||
|
/* no-op */
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.initSyncConfig({ ...user })
|
||||||
|
|
||||||
|
expect(store.prefsStorage.simple.underlay).to.eql('grid')
|
||||||
|
expect(localStore.prefsStorage.fontInterface).to.eql(
|
||||||
|
'Current interface',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
describe('setPreference', () => {
|
describe('setPreference', () => {
|
||||||
it('should set preference and update journal log accordingly', () => {
|
it('should set preference and update journal log accordingly', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue