Commit 64fe8bcb authored by lain's avatar lain

Merge branch 'fixes-to-MR8' into 'master'

Fixes to MR!8: status threads and some minor issues that caused JS errors in the console.

See merge request pleroma/mastofe!10
parents 30e6eabc 9f2e46b3
Pipeline #2205 passed with stage
in 5 minutes and 11 seconds
......@@ -14,13 +14,13 @@ const urlBase64ToUint8Array = (base64String) => {
return decodeBase64(base64);
};
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
/*
//WV const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
const getApplicationServerKey = () => {
const k = document.querySelector('[name="applicationServerKey"]');
return k === null ? '' : k.getAttribute('content');
}
*/
const getRegistration = () => navigator.serviceWorker.ready;
const getPushSubscription = (registration) =>
......
......@@ -14,8 +14,11 @@ export const getLinks = response => {
let csrfHeader = {};
function setCSRFHeader() {
const csrfToken = document.querySelector('meta[name=csrf-token]').content;
const csrfTokenTag = document.querySelector('meta[name=csrf-token]');
const csrfToken = csrfTokenTag === null ? '' : document.querySelector('meta[name=csrf-token]').content;
if (csrfToken != '') {
csrfHeader['X-CSRF-Token'] = csrfToken;
}
}
ready(setCSRFHeader);
......
......@@ -132,7 +132,8 @@ export default class Header extends ImmutablePureComponent {
const displayNameHtml = { __html: account.get('display_name_html') };
//WV: Pleroma does not yet support fields
const fields = account.get('fields') || [];
const badge = account.get('bot') ? (<div className='roles'><div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div></div>) : null;
// WV: not in pleroma atm const badge = account.get('bot') ? (<div className='roles'><div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div></div>) : null;
const badge = null;
return (
<div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${account.get('header')})` }}>
......
import Immutable from 'immutable';
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
......@@ -14,8 +13,6 @@ import {
unfavourite,
reblog,
unreblog,
pin,
unpin,
} from '../../actions/interactions';
import {
replyCompose,
......@@ -55,44 +52,11 @@ const messages = defineMessages({
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = (state, props) => {
const status = getStatus(state, props.params.statusId);
let ancestorsIds = Immutable.List();
let descendantsIds = Immutable.List();
if (status) {
ancestorsIds = ancestorsIds.withMutations(mutable => {
let id = status.get('in_reply_to_id');
while (id) {
mutable.unshift(id);
id = state.getIn(['contexts', 'inReplyTos', id]);
}
});
descendantsIds = descendantsIds.withMutations(mutable => {
const ids = [status.get('id')];
while (ids.length > 0) {
let id = ids.shift();
const replies = state.getIn(['contexts', 'replies', id]);
if (replies) {
replies.forEach(reply => {
mutable.push(reply);
ids.unshift(reply);
});
}
}
});
}
return {
status,
ancestorsIds,
descendantsIds,
};
};
const mapStateToProps = (state, props) => ({
status: getStatus(state, props.params.statusId),
ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
});
return mapStateToProps;
};
......@@ -141,14 +105,6 @@ export default class Status extends ImmutablePureComponent {
}
}
handlePin = (status) => {
if (status.get('pinned')) {
this.props.dispatch(unpin(status));
} else {
this.props.dispatch(pin(status));
}
}
handleReplyClick = (status) => {
this.props.dispatch(replyCompose(status, this.context.router.history));
}
......@@ -244,10 +200,6 @@ export default class Status extends ImmutablePureComponent {
this.props.dispatch(initReport(status.get('account'), status));
}
handleEmbed = (status) => {
this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
}
handleHotkeyMoveUp = () => {
this.handleMoveUp(this.props.status.get('id'));
}
......@@ -278,10 +230,6 @@ export default class Status extends ImmutablePureComponent {
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
}
handleHotkeyToggleHidden = () => {
this.handleToggleHidden(this.props.status);
}
handleMoveUp = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
......@@ -392,7 +340,6 @@ export default class Status extends ImmutablePureComponent {
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
openProfile: this.handleHotkeyOpenProfile,
toggleHidden: this.handleHotkeyToggleHidden,
};
return (
......@@ -429,8 +376,6 @@ export default class Status extends ImmutablePureComponent {
onMuteConversation={this.handleConversationMuteClick}
onBlock={this.handleBlockClick}
onReport={this.handleReport}
onPin={this.handlePin}
onEmbed={this.handleEmbed}
/>
</div>
</HotKeys>
......
import classNames from 'classnames';
import classNames from 'classnames';
import React from 'react';
import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types';
......@@ -86,8 +86,8 @@ const keyMap = {
goToPinned: 'g p',
goToProfile: 'g u',
goToBlocked: 'g b',
goToMuted: 'g m',
toggleHidden: 'x',
//goToMuted: 'g m',
//toggleHidden: 'x',
};
class SwitchingColumnsArea extends React.PureComponent {
......
......@@ -5,90 +5,67 @@ import {
import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses';
import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
// WV
const TIMELINE_CONTEXT_UPDATE = TIMELINE_UPDATE;
const initialState = ImmutableMap({
inReplyTos: ImmutableMap(),
replies: ImmutableMap(),
ancestors: ImmutableMap(),
descendants: ImmutableMap(),
});
const normalizeContext = (immutableState, id, ancestors, descendants) => immutableState.withMutations(state => {
state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => {
state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => {
function addReply({ id, in_reply_to_id }) {
if (in_reply_to_id) {
const siblings = replies.get(in_reply_to_id, ImmutableList());
const normalizeContext = (state, id, ancestors, descendants) => {
const ancestorsIds = ImmutableList(ancestors.map(ancestor => ancestor.id));
const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id));
if (!siblings.includes(id)) {
const index = siblings.findLastIndex(sibling => sibling.id < id);
replies.set(in_reply_to_id, siblings.insert(index + 1, id));
}
inReplyTos.set(id, in_reply_to_id);
}
}
if (ancestors[0]) {
addReply({ id, in_reply_to_id: ancestors[0].id });
}
if (descendants[0]) {
addReply({ id: descendants[0].id, in_reply_to_id: id });
}
[ancestors, descendants].forEach(statuses => statuses.forEach(addReply));
}));
}));
});
return state.withMutations(map => {
map.setIn(['ancestors', id], ancestorsIds);
map.setIn(['descendants', id], descendantsIds);
});
};
const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => {
state.update('inReplyTos', immutableAncestors => immutableAncestors.withMutations(inReplyTos => {
state.update('replies', immutableDescendants => immutableDescendants.withMutations(replies => {
state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => {
state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => {
ids.forEach(id => {
const inReplyToIdOfId = inReplyTos.get(id);
const repliesOfId = replies.get(id);
const siblings = replies.get(inReplyToIdOfId);
descendants.get(id, ImmutableList()).forEach(descendantId => {
ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id));
});
if (siblings) {
replies.set(inReplyToIdOfId, siblings.filterNot(sibling => sibling === id));
}
if (repliesOfId) {
repliesOfId.forEach(reply => inReplyTos.delete(reply));
}
ancestors.get(id, ImmutableList()).forEach(ancestorId => {
descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id));
});
inReplyTos.delete(id);
replies.delete(id);
descendants.delete(id);
ancestors.delete(id);
});
}));
}));
});
const filterContexts = (state, relationship, statuses) => {
const ownedStatusIds = statuses
.filter(status => status.get('account') === relationship.id)
.map(status => status.get('id'));
const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id)
.map(status => status.get('id'));
return deleteFromContexts(state, ownedStatusIds);
};
const updateContext = (state, status) => {
if (status.in_reply_to_id) {
return state.withMutations(mutable => {
const replies = mutable.getIn(['replies', status.in_reply_to_id], ImmutableList());
mutable.setIn(['inReplyTos', status.id], status.in_reply_to_id);
const updateContext = (state, status, references) => {
return state.update('descendants', map => {
references.forEach(parentId => {
map = map.update(parentId, ImmutableList(), list => {
if (list.includes(status.id)) {
return list;
}
if (!replies.includes(status.id)) {
mutable.setIn(['replies', status.id], replies.push(status.id));
}
return list.push(status.id);
});
});
}
return state;
return map;
});
};
export default function replies(state = initialState, action) {
export default function contexts(state = initialState, action) {
switch(action.type) {
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
......@@ -97,8 +74,8 @@ export default function replies(state = initialState, action) {
return normalizeContext(state, action.id, action.ancestors, action.descendants);
case TIMELINE_DELETE:
return deleteFromContexts(state, [action.id]);
case TIMELINE_UPDATE:
return updateContext(state, action.status);
case TIMELINE_CONTEXT_UPDATE:
return updateContext(state, action.status, action.references);
default:
return state;
}
......
import { freeStorage, storageFreeable } from '../storage/modifier';
import { freeStorage } from '../storage/modifier';
import './web_push_notifications';
function openSystemCache() {
......@@ -10,16 +10,13 @@ function openWebCache() {
}
function fetchRoot() {
return fetch('/', { credentials: 'include', redirect: 'manual' });
return fetch('/web', { credentials: 'include' });
}
const firefox = navigator.userAgent.match(/Firefox\/(\d+)/);
const invalidOnlyIfCached = firefox && firefox[1] < 60;
// Cause a new version of a registered Service Worker to replace an existing one
// that is already installed, and replace the currently active worker on open pages.
self.addEventListener('install', function(event) {
event.waitUntil(Promise.all([openWebCache(), fetchRoot()]).then(([cache, root]) => cache.put('/', root)));
event.waitUntil(Promise.all([openWebCache(), fetchRoot()]).then(([cache, root]) => cache.put('/web', root)));
});
self.addEventListener('activate', function(event) {
event.waitUntil(self.clients.claim());
......@@ -27,53 +24,62 @@ self.addEventListener('activate', function(event) {
self.addEventListener('fetch', function(event) {
const url = new URL(event.request.url);
if (url.pathname.startsWith('/web/')) {
if (url.pathname.startsWith('/web')) {
// we always make /web/login go through
if (url.pathname.startsWith('/web/login')) {
return;
}
const asyncResponse = fetchRoot();
const asyncCache = openWebCache();
event.respondWith(asyncResponse.then(
response => asyncCache.then(cache => cache.put('/', response.clone()))
.then(() => response),
() => asyncCache.then(cache => cache.match('/'))));
event.respondWith(asyncResponse.then(async response => {
// response was redirected - let's actually do the request from the event
// and return its response
if (response.redirected || response.type === 'opaqueredirect') {
return await fetch(event.request);
}
if (response.ok) {
const cache = await asyncCache;
await cache.put('/web', response);
return response.clone();
}
throw null;
}).catch(() => asyncCache.then(cache => cache.match('/web'))));
} else if (url.pathname === '/auth/sign_out') {
const asyncResponse = fetch(event.request);
const asyncCache = openWebCache();
event.respondWith(asyncResponse.then(response => {
event.respondWith(asyncResponse.then(async response => {
if (response.ok || response.type === 'opaqueredirect') {
return Promise.all([
asyncCache.then(cache => cache.delete('/')),
await Promise.all([
asyncCache.then(cache => cache.delete('/web')),
indexedDB.deleteDatabase('mastodon'),
]).then(() => response);
]);
}
return response;
}));
} else if (storageFreeable && process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) {
event.respondWith(openSystemCache().then(cache => {
return cache.match(event.request.url).then(cached => {
if (cached === undefined) {
const asyncResponse = invalidOnlyIfCached && event.request.cache === 'only-if-cached' ?
fetch(event.request, { cache: 'no-cache' }) : fetch(event.request);
return asyncResponse.then(response => {
if (response.ok) {
const put = cache.put(event.request.url, response.clone());
} else if (process.env.CDN_HOST ? url.host === process.env.CDN_HOST : url.pathname.startsWith('/system/')) {
event.respondWith(openSystemCache().then(async cache => {
const cached = await cache.match(event.request.url);
put.catch(() => freeStorage());
if (cached === undefined) {
const fetched = await fetch(event.request);
return put.then(() => {
freeStorage();
return response;
});
}
return response;
});
if (fetched.ok) {
try {
await cache.put(event.request.url, fetched.clone());
} finally {
freeStorage();
}
}
return cached;
});
return fetched;
}
return cached;
}));
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment