Merge remote-tracking branch 'origin/master' into add-automated-contrast-checking
This commit is contained in:
commit
00f9a14d66
|
@ -2,8 +2,9 @@ import { mark, stop } from '../../_utils/marks.js'
|
||||||
import { deleteStatus } from '../deleteStatuses.js'
|
import { deleteStatus } from '../deleteStatuses.js'
|
||||||
import { addStatusOrNotification } from '../addStatusOrNotification.js'
|
import { addStatusOrNotification } from '../addStatusOrNotification.js'
|
||||||
import { emit } from '../../_utils/eventBus.js'
|
import { emit } from '../../_utils/eventBus.js'
|
||||||
|
import { updateStatus } from '../updateStatus.js'
|
||||||
|
|
||||||
const KNOWN_EVENTS = ['update', 'delete', 'notification', 'conversation', 'filters_changed']
|
const KNOWN_EVENTS = ['update', 'delete', 'notification', 'conversation', 'filters_changed', 'status.update']
|
||||||
|
|
||||||
export function processMessage (instanceName, timelineName, message) {
|
export function processMessage (instanceName, timelineName, message) {
|
||||||
let { event, payload } = (message || {})
|
let { event, payload } = (message || {})
|
||||||
|
@ -12,7 +13,7 @@ export function processMessage (instanceName, timelineName, message) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mark('processMessage')
|
mark('processMessage')
|
||||||
if (['update', 'notification', 'conversation'].includes(event)) {
|
if (['update', 'notification', 'conversation', 'status.update'].includes(event)) {
|
||||||
payload = JSON.parse(payload) // only these payloads are JSON-encoded for some reason
|
payload = JSON.parse(payload) // only these payloads are JSON-encoded for some reason
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +44,9 @@ export function processMessage (instanceName, timelineName, message) {
|
||||||
case 'filters_changed':
|
case 'filters_changed':
|
||||||
emit('wordFiltersChanged', instanceName)
|
emit('wordFiltersChanged', instanceName)
|
||||||
break
|
break
|
||||||
|
case 'status.update':
|
||||||
|
updateStatus(instanceName, payload)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
stop('processMessage')
|
stop('processMessage')
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { database } from '../_database/database.js'
|
||||||
|
import { scheduleIdleTask } from '../_utils/scheduleIdleTask.js'
|
||||||
|
|
||||||
|
async function doUpdateStatus (instanceName, newStatus) {
|
||||||
|
console.log('updating status', newStatus)
|
||||||
|
await database.updateStatus(instanceName, newStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateStatus (instanceName, newStatus) {
|
||||||
|
scheduleIdleTask(() => {
|
||||||
|
/* no await */ doUpdateStatus(instanceName, newStatus)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,18 +1,20 @@
|
||||||
import { auth, basename } from './utils.js'
|
import { auth, basename } from './utils.js'
|
||||||
import { DEFAULT_TIMEOUT, get, post, WRITE_TIMEOUT } from '../_utils/ajax.js'
|
import { DEFAULT_TIMEOUT, get, post, put, WRITE_TIMEOUT } from '../_utils/ajax.js'
|
||||||
|
|
||||||
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
|
// post is create, put is edit
|
||||||
|
async function postOrPutStatus (url, accessToken, method, text, inReplyToId, mediaIds,
|
||||||
sensitive, spoilerText, visibility, poll) {
|
sensitive, spoilerText, visibility, poll) {
|
||||||
const url = `${basename(instanceName)}/api/v1/statuses`
|
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
status: text,
|
status: text,
|
||||||
in_reply_to_id: inReplyToId,
|
|
||||||
media_ids: mediaIds,
|
media_ids: mediaIds,
|
||||||
sensitive,
|
sensitive,
|
||||||
spoiler_text: spoilerText,
|
spoiler_text: spoilerText,
|
||||||
visibility,
|
poll,
|
||||||
poll
|
...(method === 'post' && {
|
||||||
|
// you can't change these properties when editing
|
||||||
|
in_reply_to_id: inReplyToId,
|
||||||
|
visibility
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(body)) {
|
for (const key of Object.keys(body)) {
|
||||||
|
@ -23,7 +25,23 @@ export async function postStatus (instanceName, accessToken, text, inReplyToId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return post(url, body, auth(accessToken), { timeout: WRITE_TIMEOUT })
|
const func = method === 'post' ? post : put
|
||||||
|
|
||||||
|
return func(url, body, auth(accessToken), { timeout: WRITE_TIMEOUT })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function postStatus (instanceName, accessToken, text, inReplyToId, mediaIds,
|
||||||
|
sensitive, spoilerText, visibility, poll) {
|
||||||
|
const url = `${basename(instanceName)}/api/v1/statuses`
|
||||||
|
return postOrPutStatus(url, accessToken, 'post', text, inReplyToId, mediaIds,
|
||||||
|
sensitive, spoilerText, visibility, poll)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function putStatus (instanceName, accessToken, id, text, inReplyToId, mediaIds,
|
||||||
|
sensitive, spoilerText, visibility, poll) {
|
||||||
|
const url = `${basename(instanceName)}/api/v1/statuses/${id}`
|
||||||
|
return postOrPutStatus(url, accessToken, 'put', text, inReplyToId, mediaIds,
|
||||||
|
sensitive, spoilerText, visibility, poll)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStatusContext (instanceName, accessToken, statusId) {
|
export async function getStatusContext (instanceName, accessToken, statusId) {
|
||||||
|
|
|
@ -3,12 +3,13 @@ import { getInCache, hasInCache, statusesCache } from '../cache.js'
|
||||||
import { STATUSES_STORE } from '../constants.js'
|
import { STATUSES_STORE } from '../constants.js'
|
||||||
import { cacheStatus } from './cacheStatus.js'
|
import { cacheStatus } from './cacheStatus.js'
|
||||||
import { putStatus } from './insertion.js'
|
import { putStatus } from './insertion.js'
|
||||||
|
import { cloneForStorage } from '../helpers.js'
|
||||||
|
|
||||||
//
|
//
|
||||||
// update statuses
|
// update statuses
|
||||||
//
|
//
|
||||||
|
|
||||||
async function updateStatus (instanceName, statusId, updateFunc) {
|
async function doUpdateStatus (instanceName, statusId, updateFunc) {
|
||||||
const db = await getDatabase(instanceName)
|
const db = await getDatabase(instanceName)
|
||||||
if (hasInCache(statusesCache, instanceName, statusId)) {
|
if (hasInCache(statusesCache, instanceName, statusId)) {
|
||||||
const status = getInCache(statusesCache, instanceName, statusId)
|
const status = getInCache(statusesCache, instanceName, statusId)
|
||||||
|
@ -25,7 +26,7 @@ async function updateStatus (instanceName, statusId, updateFunc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setStatusFavorited (instanceName, statusId, favorited) {
|
export async function setStatusFavorited (instanceName, statusId, favorited) {
|
||||||
return updateStatus(instanceName, statusId, status => {
|
return doUpdateStatus(instanceName, statusId, status => {
|
||||||
const delta = (favorited ? 1 : 0) - (status.favourited ? 1 : 0)
|
const delta = (favorited ? 1 : 0) - (status.favourited ? 1 : 0)
|
||||||
status.favourited = favorited
|
status.favourited = favorited
|
||||||
status.favourites_count = (status.favourites_count || 0) + delta
|
status.favourites_count = (status.favourites_count || 0) + delta
|
||||||
|
@ -33,7 +34,7 @@ export async function setStatusFavorited (instanceName, statusId, favorited) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setStatusReblogged (instanceName, statusId, reblogged) {
|
export async function setStatusReblogged (instanceName, statusId, reblogged) {
|
||||||
return updateStatus(instanceName, statusId, status => {
|
return doUpdateStatus(instanceName, statusId, status => {
|
||||||
const delta = (reblogged ? 1 : 0) - (status.reblogged ? 1 : 0)
|
const delta = (reblogged ? 1 : 0) - (status.reblogged ? 1 : 0)
|
||||||
status.reblogged = reblogged
|
status.reblogged = reblogged
|
||||||
status.reblogs_count = (status.reblogs_count || 0) + delta
|
status.reblogs_count = (status.reblogs_count || 0) + delta
|
||||||
|
@ -41,19 +42,36 @@ export async function setStatusReblogged (instanceName, statusId, reblogged) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setStatusPinned (instanceName, statusId, pinned) {
|
export async function setStatusPinned (instanceName, statusId, pinned) {
|
||||||
return updateStatus(instanceName, statusId, status => {
|
return doUpdateStatus(instanceName, statusId, status => {
|
||||||
status.pinned = pinned
|
status.pinned = pinned
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setStatusMuted (instanceName, statusId, muted) {
|
export async function setStatusMuted (instanceName, statusId, muted) {
|
||||||
return updateStatus(instanceName, statusId, status => {
|
return doUpdateStatus(instanceName, statusId, status => {
|
||||||
status.muted = muted
|
status.muted = muted
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setStatusBookmarked (instanceName, statusId, bookmarked) {
|
export async function setStatusBookmarked (instanceName, statusId, bookmarked) {
|
||||||
return updateStatus(instanceName, statusId, status => {
|
return doUpdateStatus(instanceName, statusId, status => {
|
||||||
status.bookmarked = bookmarked
|
status.bookmarked = bookmarked
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For the full list, see https://docs.joinmastodon.org/methods/statuses/#edit
|
||||||
|
const PROPS_THAT_CAN_BE_EDITED = ['content', 'spoiler_text', 'sensitive', 'language', 'media_ids', 'poll']
|
||||||
|
|
||||||
|
export async function updateStatus (instanceName, newStatus) {
|
||||||
|
const clonedNewStatus = cloneForStorage(newStatus)
|
||||||
|
return doUpdateStatus(instanceName, newStatus.id, status => {
|
||||||
|
// We can't use a simple Object.assign() to merge because a prop might have been deleted
|
||||||
|
for (const prop of PROPS_THAT_CAN_BE_EDITED) {
|
||||||
|
if (!(prop in clonedNewStatus)) {
|
||||||
|
delete status[prop]
|
||||||
|
} else {
|
||||||
|
status[prop] = clonedNewStatus[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -42,12 +42,12 @@
|
||||||
--nav-svg-fill-hover: #{$secondary-text-color};
|
--nav-svg-fill-hover: #{$secondary-text-color};
|
||||||
--nav-text-color-hover: #{$secondary-text-color};
|
--nav-text-color-hover: #{$secondary-text-color};
|
||||||
|
|
||||||
--action-button-fill-color: #{lighten($main-theme-color, 18%)};
|
--action-button-fill-color: #{lighten($main-theme-color, 11.5%)};
|
||||||
--action-button-fill-color-hover: #{lighten($main-theme-color, 22%)};
|
--action-button-fill-color-hover: #{lighten($main-theme-color, 6%)};
|
||||||
--action-button-fill-color-active: #{lighten($main-theme-color, 5%)};
|
--action-button-fill-color-active: #{$main-theme-color};
|
||||||
--action-button-fill-color-pressed: #{darken($main-theme-color, 7%)};
|
--action-button-fill-color-pressed: #{darken(saturate($main-theme-color, 5%), 6%)};
|
||||||
--action-button-fill-color-pressed-hover: #{darken($main-theme-color, 2%)};
|
--action-button-fill-color-pressed-hover: #{darken(saturate($main-theme-color, 5%), 12%)};
|
||||||
--action-button-fill-color-pressed-active: #{darken($main-theme-color, 15%)};
|
--action-button-fill-color-pressed-active: #{darken(saturate($main-theme-color, 5%), 15%)};
|
||||||
|
|
||||||
--action-button-deemphasized-fill-color: #{$deemphasized-color};
|
--action-button-deemphasized-fill-color: #{$deemphasized-color};
|
||||||
--action-button-deemphasized-fill-color-hover: #{lighten($deemphasized-color, 22%)};
|
--action-button-deemphasized-fill-color-hover: #{lighten($deemphasized-color, 22%)};
|
||||||
|
|
|
@ -33,9 +33,9 @@ $compose-background: darken($main-theme-color, 12%);
|
||||||
--form-bg: #{$body-bg-color};
|
--form-bg: #{$body-bg-color};
|
||||||
--form-border: #{darken($border-color, 10%)};
|
--form-border: #{darken($border-color, 10%)};
|
||||||
|
|
||||||
--action-button-fill-color: #{lighten($main-theme-color, 20%)};
|
--action-button-fill-color: #{lighten($main-theme-color, 50%)};
|
||||||
--action-button-fill-color-hover: #{lighten($main-theme-color, 30%)};
|
--action-button-fill-color-hover: #{lighten($main-theme-color, 60%)};
|
||||||
--action-button-fill-color-active: #{darken($main-theme-color, 40%)};
|
--action-button-fill-color-active: #{darken($main-theme-color, 70%)};
|
||||||
--action-button-fill-color-pressed: #{lighten($main-theme-color, 85%)};
|
--action-button-fill-color-pressed: #{lighten($main-theme-color, 85%)};
|
||||||
--action-button-fill-color-pressed-hover: #{lighten($main-theme-color, 100%)};
|
--action-button-fill-color-pressed-hover: #{lighten($main-theme-color, 100%)};
|
||||||
--action-button-fill-color-pressed-active: #{lighten($main-theme-color, 80%)};
|
--action-button-fill-color-pressed-active: #{lighten($main-theme-color, 80%)};
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { favoriteStatus } from '../src/routes/_api/favorite.js'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import FileApi from 'file-api'
|
import FileApi from 'file-api'
|
||||||
import { users } from './users.js'
|
import { users } from './users.js'
|
||||||
import { postStatus } from '../src/routes/_api/statuses.js'
|
import { postStatus, putStatus } from '../src/routes/_api/statuses.js'
|
||||||
import { deleteStatus } from '../src/routes/_api/delete.js'
|
import { deleteStatus } from '../src/routes/_api/delete.js'
|
||||||
import { authorizeFollowRequest, getFollowRequests } from '../src/routes/_api/followRequests.js'
|
import { authorizeFollowRequest, getFollowRequests } from '../src/routes/_api/followRequests.js'
|
||||||
import { followAccount, unfollowAccount } from '../src/routes/_api/follow.js'
|
import { followAccount, unfollowAccount } from '../src/routes/_api/follow.js'
|
||||||
|
@ -33,6 +33,11 @@ export async function postAs (username, text) {
|
||||||
null, null, false, null, 'public')
|
null, null, false, null, 'public')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function putAs (username, text, statusId) {
|
||||||
|
return putStatus(instanceName, users[username].accessToken, statusId, text,
|
||||||
|
null, null, false, null, 'public')
|
||||||
|
}
|
||||||
|
|
||||||
export async function postWithSpoilerAndPrivacyAs (username, text, spoiler, privacy) {
|
export async function postWithSpoilerAndPrivacyAs (username, text, spoiler, privacy) {
|
||||||
return postStatus(instanceName, users[username].accessToken, text,
|
return postStatus(instanceName, users[username].accessToken, text,
|
||||||
null, null, true, spoiler, privacy)
|
null, null, true, spoiler, privacy)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {
|
||||||
|
getNthStatus, getUrl, goBack,
|
||||||
|
sleep
|
||||||
|
} from '../utils'
|
||||||
|
import { loginAsFoobar } from '../roles'
|
||||||
|
import { postAs, putAs } from '../serverActions'
|
||||||
|
|
||||||
|
fixture`140-editing.js`
|
||||||
|
.page`http://localhost:4002`
|
||||||
|
|
||||||
|
test('Edited toots are updated in the UI', async t => {
|
||||||
|
const { id: statusId } = await postAs('admin', 'yolo')
|
||||||
|
await sleep(500)
|
||||||
|
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t.expect(getNthStatus(1).innerText).contains('yolo', { timeout: 20000 })
|
||||||
|
|
||||||
|
await putAs('admin', 'wait I mean YOLO', statusId)
|
||||||
|
await sleep(500)
|
||||||
|
|
||||||
|
await t.click(getNthStatus(1))
|
||||||
|
.expect(getUrl()).contains('/statuses')
|
||||||
|
.expect(getNthStatus(1).innerText).contains('wait I mean YOLO', { timeout: 20000 })
|
||||||
|
await goBack()
|
||||||
|
await t
|
||||||
|
.expect(getUrl()).eql('http://localhost:4002/')
|
||||||
|
.expect(getNthStatus(1).innerText).contains('wait I mean YOLO', { timeout: 20000 })
|
||||||
|
})
|
Loading…
Reference in New Issue