Compare commits
41 commits
akkoma
...
akkoma-bac
Author | SHA1 | Date | |
---|---|---|---|
22ca0a7c4a | |||
f4e64e5d4d | |||
ac4bcb04d7 | |||
|
68105a39bf | ||
|
4b041c1551 | ||
|
cfefd0aecd | ||
|
4788384b1f | ||
|
ce085cf1d7 | ||
|
1e59ed8223 | ||
|
f2866f1e99 | ||
|
473e84916f | ||
|
312e3674c3 | ||
|
fae133ca7f | ||
|
463987ffe3 | ||
|
47125c9016 | ||
|
3554d8fc2f | ||
|
4700e27d12 | ||
|
c9d4a963ca | ||
|
f1cd875a4e | ||
|
f6ec8afcda | ||
|
2fa8015c39 | ||
|
1651f733b0 | ||
|
316694bea6 | ||
|
4e170d46ad | ||
|
2942716fb9 | ||
|
1af76fae0e | ||
|
6b3614ae4c | ||
|
6bf8551755 | ||
|
3918d358ee | ||
|
faba7113c8 | ||
|
6599840fa5 | ||
|
f0d1a559ae | ||
|
882d44afdf | ||
|
a915aca1db | ||
|
65787ddcac | ||
|
0aafacfb39 | ||
|
ca704f09dd | ||
|
9377b5ca7b | ||
|
3893e49a1f | ||
|
38ca2a8425 | ||
|
1a151397e5 |
180 changed files with 837 additions and 65759 deletions
23
.gitlab-ci.yml
Normal file
23
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,23 @@
|
|||
image: alpine:latest
|
||||
|
||||
before_script:
|
||||
- apk add yarn
|
||||
- yarn global add node-gyp
|
||||
- yarn install
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
test:
|
||||
script:
|
||||
- yarn run test:jest
|
||||
|
||||
build:
|
||||
script:
|
||||
- yarn run build
|
||||
artifacts:
|
||||
paths:
|
||||
- public/packs
|
||||
- public/assets
|
||||
expire_in: 1 week
|
49
.woodpecker.yml
Normal file
49
.woodpecker.yml
Normal file
|
@ -0,0 +1,49 @@
|
|||
pipeline:
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
image: node:16
|
||||
commands:
|
||||
- yarn
|
||||
- yarn test:lint
|
||||
|
||||
test:
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
image: node:16
|
||||
commands:
|
||||
- apt update
|
||||
- apt install firefox-esr -y --no-install-recommends
|
||||
- yarn
|
||||
- yarn test
|
||||
|
||||
build:
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
image: node:16
|
||||
commands:
|
||||
- yarn
|
||||
- TARGET=distribution ./build.sh
|
||||
|
||||
release:
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
image: node:16
|
||||
secrets:
|
||||
- SCW_ACCESS_KEY
|
||||
- SCW_SECRET_KEY
|
||||
- SCW_DEFAULT_ORGANIZATION_ID
|
||||
commands:
|
||||
- apt-get update && apt-get install -y rclone wget zip
|
||||
- wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
|
||||
- mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
|
||||
- chmod +x scaleway-cli
|
||||
- ./scaleway-cli object config install type=rclone
|
||||
- zip mastofe.zip -r distribution
|
||||
- rclone copyto mastofe.zip scaleway:akkoma-updates/frontend/masto-fe.zip
|
4
Changelog-mastofe.txt
Normal file
4
Changelog-mastofe.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
# MastoFE Changelog
|
||||
Note: This file is to be cleared after each rebase on upstream
|
||||
|
||||
Based on b7e178d2e4102bdaa1ea41dfd8ed50093cf3f60a 2020-05-14T23:30:05Z
|
74
README.md
74
README.md
|
@ -1,12 +1,72 @@
|
|||
# Mastodon Glitch Edition #
|
||||
# Mastodon Frontend, Glitch-soc + Pleroma Edition
|
||||
|
||||
> Now with automated deploys!
|
||||
Here is a distribution of the glitch-soc frontend for pleroma. Everything from the upstream repository is kept and rebased on for easy updates, this does screws up on Merge Requests so they’ll be treated as a patchset if done here.
|
||||
|
||||
[][circleci]
|
||||
# Fork vs. Distribution
|
||||
This repository is not a fork of glitch-soc, there is only a few patches on it that are kept and designed to be easily removable if it needs to (for example when upstream does one of our features on their side too).
|
||||
This also means that there is no merge, only one big rebase to get the patches up-to-date. See the Development section for more information.
|
||||
|
||||
[circleci]: https://circleci.com/gh/glitch-soc/mastodon
|
||||
# Deployement
|
||||
## Fetching CI artifacts
|
||||
Pleroma (backend) has a shell script in `installation/download-mastofe-build.sh` which can be used to get a build from the CI artifacts, by default it will install the one from `rebase/glitch-soc` into `instance/static` (so you can revert to the bundled frontend by removing it and it is free from git conflicts), this behaviour can be changed by modifying the first lines of the script.
|
||||
|
||||
So here's the deal: we all work on this code, and then it runs on dev.glitch.social and anyone who uses that does so absolutely at their own risk. can you dig it?
|
||||
## Updating the bundle
|
||||
This is what you want to do to update the mastofe bundled with pleroma.
|
||||
|
||||
- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
|
||||
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).
|
||||
- Run ``build.sh`` at the root of this repo, you can set the ``TARGET`` environment variable if pleroma isn’t at ``../pleroma`` (default value of ``TARGET``)
|
||||
- Go to pleroma repo:
|
||||
- ``git add priv/static/sw.js priv/static/packs``
|
||||
- ``git commit -m "update mastofe"``
|
||||
|
||||
# Development
|
||||
## Branches
|
||||
- `pleroma` branch which get re-referenced to `rebase/glitch-soc` once it is stable
|
||||
- `master`: Same branch as upstream repository, should be updated before doing an updating rebase
|
||||
- `rebase/glitch-soc`: branch which rebases from upstream, used for testing
|
||||
|
||||
For developement/Merge Requests I would suggest to use `master` when you are introducing new modifications that cannot be in the upstream, and when you are changing current modifications prefer `rebase/glitch-soc`.
|
||||
|
||||
Never use `pleroma` as a base for Merge Requests, it is not meant to be modified directly.
|
||||
|
||||
## Changelog
|
||||
There is a `Changelog-mastofe.txt` file used to do the release notes for the message on tagging a new release. It is cleaned when updating from upstream (`git filter-branch` should be a nice friend).
|
||||
Changes on the behaviour of the frontend should be noted in this file together with their modification (commit at best, MR at worst).
|
||||
|
||||
## Tools
|
||||
- Node.js
|
||||
- yarn (preferred) or npm
|
||||
- HTTP proxy (such as nginx)
|
||||
|
||||
## nginx setup
|
||||
I'll assume that you have already fired up pleroma using the installation guide. To work on the frontend while still having the backend up, use this nginx config.
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name pleroma.testing;
|
||||
|
||||
location /packs {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://localhost:3035;
|
||||
}
|
||||
|
||||
location / {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
proxy_pass http://localhost:4000;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Change the `server_name` if you like. I personally like to create a new entry in /etc/hosts and add `127.0.0.1 pleroma.testing`, but you do what suits you.
|
||||
|
||||
## Running
|
||||
- Getting the node dependencies is done with `yarn install -D` (or `npm install` if you don’t have yarn)
|
||||
- Launching the frontend is done with `npm run dev`. It should be reachable once it finnishes compiling.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// This file will be loaded on settings pages, regardless of theme.
|
||||
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
|
||||
const { delegate } = require('@rails/ujs');
|
||||
|
||||
import emojify from '../mastodon/features/emoji/emoji';
|
||||
|
||||
delegate(document, '#account_display_name', 'input', ({ target }) => {
|
||||
|
@ -62,7 +64,7 @@ delegate(document, '.input-copy button', 'click', ({ target }) => {
|
|||
input.blur();
|
||||
target.parentNode.classList.add('copied');
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
target.parentNode.classList.remove('copied');
|
||||
}, 700);
|
||||
}
|
||||
|
|
|
@ -760,7 +760,7 @@ export function fetchPinnedAccounts() {
|
|||
return (dispatch, getState) => {
|
||||
dispatch(fetchPinnedAccountsRequest());
|
||||
|
||||
api(getState).get(`/api/v1/endorsements`, { params: { limit: 0 } }).then(response => {
|
||||
api(getState).get('/api/v1/endorsements', { params: { limit: 0 } }).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchPinnedAccountsSuccess(response.data));
|
||||
}).catch(err => dispatch(fetchPinnedAccountsFail(err)));
|
||||
|
@ -822,7 +822,7 @@ export function changePinnedAccountsSuggestions(value) {
|
|||
return {
|
||||
type: PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE,
|
||||
value,
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export function resetPinnedAccountsEditor() {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { search as emojiSearch } from 'flavours/glitch/util/emoji/emoji_mart_sea
|
|||
import { useEmoji } from './emojis';
|
||||
import { tagHistory } from 'flavours/glitch/util/settings';
|
||||
import { recoverHashtags } from 'flavours/glitch/util/hashtag';
|
||||
import resizeImage from 'flavours/glitch/util/resize_image';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { updateTimeline } from './timelines';
|
||||
import { showAlertForError } from './alerts';
|
||||
|
@ -83,12 +82,6 @@ export function changeCompose(text) {
|
|||
};
|
||||
};
|
||||
|
||||
export function cycleElefriendCompose() {
|
||||
return {
|
||||
type: COMPOSE_CYCLE_ELEFRIEND,
|
||||
};
|
||||
};
|
||||
|
||||
export function replyCompose(status, routerHistory) {
|
||||
return (dispatch, getState) => {
|
||||
const prependCWRe = getState().getIn(['local_settings', 'prepend_cw_re']);
|
||||
|
@ -250,41 +243,37 @@ export function uploadCompose(files) {
|
|||
|
||||
dispatch(uploadComposeRequest());
|
||||
|
||||
for (const [i, f] of Array.from(files).entries()) {
|
||||
for (const [i, file] of Array.from(files).entries()) {
|
||||
if (media.size + i > 3) break;
|
||||
|
||||
resizeImage(f).then(file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
// Account for disparity in size of original image and resized data
|
||||
total += file.size - f.size;
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
|
||||
return api(getState).post('/api/v2/media', data, {
|
||||
onUploadProgress: function({ loaded }){
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
},
|
||||
}).then(({ status, data }) => {
|
||||
// If server-side processing of the media attachment has not completed yet,
|
||||
// poll the server until it is, before showing the media attachment as uploaded
|
||||
return api(getState).post('/api/v2/media', data, {
|
||||
onUploadProgress: function({ loaded }){
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
},
|
||||
}).then(({ status, data }) => {
|
||||
// If server-side processing of the media attachment has not completed yet,
|
||||
// poll the server until it is, before showing the media attachment as uploaded
|
||||
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 202) {
|
||||
const poll = () => {
|
||||
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
|
||||
if (response.status === 200) {
|
||||
dispatch(uploadComposeSuccess(response.data, f));
|
||||
} else if (response.status === 206) {
|
||||
setTimeout(() => poll(), 1000);
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, file));
|
||||
} else if (status === 202) {
|
||||
const poll = () => {
|
||||
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
|
||||
if (response.status === 200) {
|
||||
dispatch(uploadComposeSuccess(response.data, file));
|
||||
} else if (response.status === 206) {
|
||||
setTimeout(() => poll(), 1000);
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
|
||||
poll();
|
||||
}
|
||||
});
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
poll();
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -36,15 +36,15 @@ export const submitMarkers = () => (dispatch, getState) => {
|
|||
};
|
||||
|
||||
export const fetchMarkers = () => (dispatch, getState) => {
|
||||
const params = { timeline: ['notifications'] };
|
||||
const params = { timeline: ['notifications'] };
|
||||
|
||||
dispatch(fetchMarkersRequest());
|
||||
dispatch(fetchMarkersRequest());
|
||||
|
||||
api(getState).get('/api/v1/markers', { params }).then(response => {
|
||||
dispatch(fetchMarkersSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchMarkersFail(error));
|
||||
});
|
||||
api(getState).get('/api/v1/markers', { params }).then(response => {
|
||||
dispatch(fetchMarkersSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(fetchMarkersFail(error));
|
||||
});
|
||||
};
|
||||
|
||||
export function fetchMarkersRequest() {
|
||||
|
|
|
@ -117,7 +117,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
|||
};
|
||||
};
|
||||
|
||||
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||
export const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
||||
|
||||
|
||||
const excludeTypesFromFilter = filter => {
|
||||
|
|
|
@ -18,7 +18,10 @@ const urlBase64ToUint8Array = (base64String) => {
|
|||
return outputArray;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import api from 'flavours/glitch/util/api';
|
||||
import { debounce } from 'lodash';
|
||||
import { showAlertForError } from './alerts';
|
||||
import { excludeTypesFromSettings } from './notifications';
|
||||
|
||||
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
||||
export const SETTING_SAVE = 'SETTING_SAVE';
|
||||
|
@ -24,6 +25,11 @@ const debouncedSave = debounce((dispatch, getState) => {
|
|||
|
||||
const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS();
|
||||
|
||||
api(getState).put(
|
||||
'/api/pleroma/notification_settings',
|
||||
{ exclude_types: excludeTypesFromSettings(getState()) },
|
||||
).catch(error => dispatch(showAlertForError(error)));
|
||||
|
||||
api(getState).put('/api/web/settings', { data })
|
||||
.then(() => dispatch({ type: SETTING_SAVE }))
|
||||
.catch(error => dispatch(showAlertForError(error)));
|
||||
|
|
|
@ -47,7 +47,7 @@ export function updateTimeline(timeline, status, accept) {
|
|||
timeline,
|
||||
status,
|
||||
usePendingItems: preferPendingItems,
|
||||
filtered
|
||||
filtered,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -87,7 +87,7 @@ export default class DisplayName extends React.PureComponent {
|
|||
)).reduce((prev, cur) => [prev, ', ', cur]);
|
||||
|
||||
if (others.size - 2 > 0) {
|
||||
displayName.push(` +${others.size - 2}`);
|
||||
displayName.push(` +${others.size - 2}`);
|
||||
}
|
||||
|
||||
suffix = (
|
||||
|
|
|
@ -74,7 +74,7 @@ export default class ErrorBoundary extends React.PureComponent {
|
|||
<FormattedMessage
|
||||
id='web_app_crash.report_issue'
|
||||
defaultMessage='Report a bug in the {issuetracker}'
|
||||
values={{ issuetracker: <a href='https://github.com/glitch-soc/mastodon/issues' rel='noopener noreferrer' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }}
|
||||
values={{ issuetracker: <a href='https://git.pleroma.social/pleroma/mastofe/issues' rel='noopener noreferrer' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }}
|
||||
/>
|
||||
{ debugInfo !== '' && (
|
||||
<details>
|
||||
|
@ -104,6 +104,13 @@ export default class ErrorBoundary extends React.PureComponent {
|
|||
/>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id='web_app_crash.load_pleromafe'
|
||||
defaultMessage='Load {pleromafe} instead of mastofe'
|
||||
values={{ pleromafe: <a href='/main/all'><FormattedMessage id='web_app_crash.pleromafe' defaultMessage='Pleroma Frontend' /></a> }}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,7 +20,7 @@ const Hashtag = ({ hashtag }) => (
|
|||
</div>
|
||||
|
||||
<div className='trends__item__sparkline'>
|
||||
<Sparklines width={50} height={28} data={hashtag.get('history').reverse().map(day => day.get('uses')).toArray()}>
|
||||
<Sparklines width={50} height={28} data={hashtag.get('history') && hashtag.get('history').reverse().map(day => day.get('uses')).toArray()}>
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
</Sparklines>
|
||||
</div>
|
||||
|
|
|
@ -121,8 +121,9 @@ export default class IntersectionObserverArticle extends React.Component {
|
|||
aria-setsize={listLength}
|
||||
data-id={id}
|
||||
tabIndex='0'
|
||||
style={style}>
|
||||
{children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
|
||||
style={style}
|
||||
>
|
||||
{children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -226,6 +226,21 @@ class Item extends React.PureComponent {
|
|||
/>
|
||||
</a>
|
||||
);
|
||||
} else if (attachment.get('type') === 'audio') {
|
||||
thumbnail = (
|
||||
<div
|
||||
className='media-gallery__audio'
|
||||
aria-label={attachment.get('description')}
|
||||
role='application'
|
||||
>
|
||||
<span><p>sound<br />only</p></span>
|
||||
<span><p>{attachment.get('description')}</p></span>
|
||||
<audio
|
||||
src={attachment.get('url')}
|
||||
controls
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (attachment.get('type') === 'gifv') {
|
||||
const autoPlay = !isIOS() && this.getAutoPlay();
|
||||
|
||||
|
@ -352,7 +367,7 @@ class MediaGallery extends React.PureComponent {
|
|||
} else if (width) {
|
||||
style.height = width / (16/9);
|
||||
} else {
|
||||
return (<div className={computedClass} ref={this.handleRef}></div>);
|
||||
return (<div className={computedClass} ref={this.handleRef} />);
|
||||
}
|
||||
|
||||
if (this.isStandaloneEligible()) {
|
||||
|
|
|
@ -1,15 +1,10 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import illustration from 'flavours/glitch/images/elephant_ui_disappointed.svg';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const MissingIndicator = ({ fullPage }) => (
|
||||
<div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}>
|
||||
<div className='regeneration-indicator__figure'>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
|
||||
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'wicg-inert';
|
|||
import { createBrowserHistory } from 'history';
|
||||
|
||||
export default class ModalRoot extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
@ -104,7 +105,7 @@ export default class ModalRoot extends React.PureComponent {
|
|||
|
||||
handleModalOpen () {
|
||||
const history = this.history;
|
||||
const state = {...history.location.state, mastodonModalOpen: true};
|
||||
const state = { ...history.location.state, mastodonModalOpen: true };
|
||||
history.push(history.location.pathname, state);
|
||||
this.unlistenHistory = history.listen(() => {
|
||||
this.props.onClose();
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class Permalink extends React.PureComponent {
|
|||
|
||||
if (this.context.router) {
|
||||
e.preventDefault();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(this.props.to, state);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import illustration from 'flavours/glitch/images/elephant_ui_working.svg';
|
||||
|
||||
const MissingIndicator = () => (
|
||||
<div className='regeneration-indicator'>
|
||||
<div className='regeneration-indicator__figure'>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
|
||||
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
|
||||
|
|
|
@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
export default
|
||||
class Spoilers extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
spoilerText: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
|
@ -21,17 +22,17 @@ class Spoilers extends React.PureComponent {
|
|||
const { spoilerText, children } = this.props;
|
||||
const { hidden } = this.state;
|
||||
|
||||
const toggleText = hidden ?
|
||||
<FormattedMessage
|
||||
id='status.show_more'
|
||||
defaultMessage='Show more'
|
||||
key='0'
|
||||
/> :
|
||||
<FormattedMessage
|
||||
id='status.show_less'
|
||||
defaultMessage='Show less'
|
||||
key='0'
|
||||
/>;
|
||||
const toggleText = hidden ?
|
||||
(<FormattedMessage
|
||||
id='status.show_more'
|
||||
defaultMessage='Show more'
|
||||
key='0'
|
||||
/>) :
|
||||
(<FormattedMessage
|
||||
id='status.show_less'
|
||||
defaultMessage='Show less'
|
||||
key='0'
|
||||
/>);
|
||||
|
||||
return ([
|
||||
<p className='spoiler__text'>
|
||||
|
@ -43,8 +44,9 @@ class Spoilers extends React.PureComponent {
|
|||
</p>,
|
||||
<div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ export const defaultMediaVisibility = (status, settings) => {
|
|||
}
|
||||
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
|
||||
}
|
||||
};
|
||||
|
||||
export default @injectIntl
|
||||
class Status extends ImmutablePureComponent {
|
||||
|
@ -291,7 +291,9 @@ class Status extends ImmutablePureComponent {
|
|||
if (this.node && this.props.getScrollPosition) {
|
||||
const position = this.props.getScrollPosition();
|
||||
if (position !== null && this.node.offsetTop < position.top) {
|
||||
requestAnimationFrame(() => { this.props.updateScrollBottom(position.height - position.top); });
|
||||
requestAnimationFrame(() => {
|
||||
this.props.updateScrollBottom(position.height - position.top);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -344,7 +346,7 @@ class Status extends ImmutablePureComponent {
|
|||
status.getIn(['reblog', 'id'], status.get('id'))
|
||||
}`;
|
||||
}
|
||||
let state = {...router.history.location.state};
|
||||
let state = { ...router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
router.history.push(destination, state);
|
||||
}
|
||||
|
@ -360,7 +362,7 @@ class Status extends ImmutablePureComponent {
|
|||
if (this.context.router && e.button === 0) {
|
||||
const id = e.currentTarget.getAttribute('data-id');
|
||||
e.preventDefault();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${id}`, state);
|
||||
}
|
||||
|
@ -415,13 +417,13 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state);
|
||||
}
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import IconButton from './icon_button';
|
|||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me, isStaff } from 'flavours/glitch/util/initial_state';
|
||||
import { me, isStaff, deleteOthersNotice } from 'flavours/glitch/util/initial_state';
|
||||
import RelativeTimestamp from './relative_timestamp';
|
||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links';
|
||||
|
||||
|
@ -122,7 +122,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
_openInteractionDialog = type => {
|
||||
window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||
}
|
||||
}
|
||||
|
||||
handleDeleteClick = () => {
|
||||
this.props.onDelete(this.props.status, this.context.router.history);
|
||||
|
@ -153,7 +153,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleOpen = () => {
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
if (state.mastodonModalOpen) {
|
||||
this.context.router.history.replace(`/statuses/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 });
|
||||
} else {
|
||||
|
@ -231,7 +231,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
|
||||
|
@ -255,6 +254,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
});
|
||||
}
|
||||
}
|
||||
if ( deleteOthersNotice ) {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
}
|
||||
}
|
||||
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
|
|
|
@ -13,7 +13,7 @@ const textMatchesTarget = (text, origin, host) => {
|
|||
return (text === origin || text === host
|
||||
|| text.startsWith(origin + '/') || text.startsWith(host + '/')
|
||||
|| 'www.' + text === host || ('www.' + text).startsWith(host + '/'));
|
||||
}
|
||||
};
|
||||
|
||||
const isLinkMisleading = (link) => {
|
||||
let linkTextParts = [];
|
||||
|
|
|
@ -48,16 +48,16 @@ class StatusIcons extends React.PureComponent {
|
|||
const { intl, mediaIcon } = this.props;
|
||||
|
||||
switch (mediaIcon) {
|
||||
case 'link':
|
||||
return intl.formatMessage(messages.previewCard);
|
||||
case 'picture-o':
|
||||
return intl.formatMessage(messages.pictures);
|
||||
case 'tasks':
|
||||
return intl.formatMessage(messages.poll);
|
||||
case 'video-camera':
|
||||
return intl.formatMessage(messages.video);
|
||||
case 'music':
|
||||
return intl.formatMessage(messages.audio);
|
||||
case 'link':
|
||||
return intl.formatMessage(messages.previewCard);
|
||||
case 'picture-o':
|
||||
return intl.formatMessage(messages.pictures);
|
||||
case 'tasks':
|
||||
return intl.formatMessage(messages.poll);
|
||||
case 'video-camera':
|
||||
return intl.formatMessage(messages.video);
|
||||
case 'music':
|
||||
return intl.formatMessage(messages.audio);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ const mapDispatchToProps = (dispatch, { status, items }) => ({
|
|||
(item, i) => item ? {
|
||||
...item,
|
||||
name: `${item.text}-${i}`,
|
||||
onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null,
|
||||
} : null
|
||||
onClick: item.action ? ((e) => {
|
||||
return onItemClick(i, e);
|
||||
}) : null,
|
||||
} : null,
|
||||
),
|
||||
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
|
||||
},
|
||||
|
|
|
@ -201,7 +201,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
<Spoilers spoilerText={intl.formatMessage(messages.author)}>
|
||||
<AccountContainer id={status.getIn(['account', 'id'])} />
|
||||
</Spoilers>
|
||||
<Spoilers spoilerText={intl.formatMessage(messages.matchingFilters, {count: matchingFilters.size})}>
|
||||
<Spoilers spoilerText={intl.formatMessage(messages.matchingFilters, { count: matchingFilters.size })}>
|
||||
<ul>
|
||||
{matchingFilters.map(filter => (
|
||||
<li>
|
||||
|
@ -221,7 +221,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
))}
|
||||
</ul>
|
||||
</Spoilers>
|
||||
</div>
|
||||
</div>,
|
||||
],
|
||||
confirm: intl.formatMessage(messages.unfilterConfirm),
|
||||
onConfirm: onConfirm,
|
||||
|
|
|
@ -128,8 +128,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
|
||||
info.push(<span className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
|
||||
}
|
||||
else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
||||
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
|
||||
info.push(<span className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
|
||||
}
|
||||
|
||||
|
@ -297,7 +296,7 @@ class Header extends ImmutablePureComponent {
|
|||
{fields.map((pair, i) => (
|
||||
<dl key={i}>
|
||||
<dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
|
||||
|
||||
|
||||
<dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
|
||||
{pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
|
||||
</dd>
|
||||
|
@ -308,9 +307,9 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content' dangerouslySetInnerHTML={content} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ export default class MovedNote extends ImmutablePureComponent {
|
|||
handleAccountClick = e => {
|
||||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${this.props.to.get('id')}`, state);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from 'flavours/glitch/actions/accounts';
|
||||
import {
|
||||
mentionCompose,
|
||||
directCompose
|
||||
directCompose,
|
||||
} from 'flavours/glitch/actions/compose';
|
||||
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
||||
import { initBlockModal } from 'flavours/glitch/actions/blocks';
|
||||
|
|
|
@ -12,7 +12,7 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const mapDispatchToProps = (dispatch, { columnId }) => {
|
||||
return {
|
||||
onChange (key, checked) {
|
||||
|
|
|
@ -11,7 +11,6 @@ import UploadFormContainer from '../containers/upload_form_container';
|
|||
import WarningContainer from '../containers/warning_container';
|
||||
import { isMobile } from 'flavours/glitch/util/is_mobile';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { countableText } from 'flavours/glitch/util/counter';
|
||||
import OptionsContainer from '../containers/options_container';
|
||||
import Publisher from './publisher';
|
||||
import TextareaIcons from './textarea_icons';
|
||||
|
@ -21,9 +20,9 @@ import CharacterCounter from './character_counter';
|
|||
const messages = defineMessages({
|
||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||
missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
|
||||
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
|
||||
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
|
||||
missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
|
||||
defaultMessage: 'Send anyway' },
|
||||
defaultMessage: 'Send anyway' },
|
||||
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
|
||||
});
|
||||
|
||||
|
@ -292,7 +291,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
|
||||
let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia);
|
||||
|
||||
const countText = `${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`;
|
||||
const countText = `${spoilerText}${text}`;
|
||||
|
||||
return (
|
||||
<div className='composer'>
|
||||
|
|
|
@ -141,7 +141,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
onChange(name);
|
||||
component.setState({ needsModalUpdate: true });
|
||||
},
|
||||
})
|
||||
}),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
|||
const { items } = this.props;
|
||||
const { needsModalUpdate } = this.state;
|
||||
if (needsModalUpdate && items.find(
|
||||
(item, i) => item.on !== prevProps.items[i].on
|
||||
(item, i) => item.on !== prevProps.items[i].on,
|
||||
)) {
|
||||
this.handleUpdate();
|
||||
this.setState({ needsModalUpdate: false });
|
||||
|
|
|
@ -167,7 +167,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
|||
if (on !== null && typeof on !== 'undefined') {
|
||||
prefix = <Toggle checked={on} onChange={this.handleClick.bind(this, name)} />;
|
||||
} else if (icon) {
|
||||
prefix = <Icon className='icon' fixedWidth id={icon} />
|
||||
prefix = <Icon className='icon' fixedWidth id={icon} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -47,6 +47,7 @@ const messages = defineMessages({
|
|||
|
||||
export default @injectIntl
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
columns: ImmutablePropTypes.list,
|
||||
unreadNotifications: PropTypes.number,
|
||||
|
@ -71,8 +72,8 @@ class Header extends ImmutablePureComponent {
|
|||
// Only renders the component if the column isn't being shown.
|
||||
const renderForColumn = conditionalRender.bind(null,
|
||||
columnId => !columns || !columns.some(
|
||||
column => column.get('id') === columnId
|
||||
)
|
||||
column => column.get('id') === columnId,
|
||||
),
|
||||
);
|
||||
|
||||
// The result.
|
||||
|
@ -125,10 +126,11 @@ class Header extends ImmutablePureComponent {
|
|||
<a
|
||||
aria-label={intl.formatMessage(messages.logout)}
|
||||
onClick={this.handleLogoutClick}
|
||||
href={ signOutLink }
|
||||
href={signOutLink}
|
||||
title={intl.formatMessage(messages.logout)}
|
||||
><Icon id='sign-out' /></a>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -28,11 +28,12 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||
{ profileLink !== undefined && (
|
||||
<a
|
||||
className='edit'
|
||||
href={ profileLink }
|
||||
href={profileLink}
|
||||
><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -61,6 +61,10 @@ const messages = defineMessages({
|
|||
defaultMessage: 'Markdown',
|
||||
id: 'compose.content-type.markdown',
|
||||
},
|
||||
bbcode: {
|
||||
defaultMessage: 'BBCode',
|
||||
id: 'compose.content-type.bbcode',
|
||||
},
|
||||
plain: {
|
||||
defaultMessage: 'Plain text',
|
||||
id: 'compose.content-type.plain',
|
||||
|
@ -246,13 +250,17 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
name: 'text/markdown',
|
||||
text: <FormattedMessage {...messages.markdown} />,
|
||||
},
|
||||
bbcode: {
|
||||
icon: 'bold',
|
||||
name: 'text/bbcode',
|
||||
text: <FormattedMessage {...messages.bbcode} />,
|
||||
},
|
||||
};
|
||||
|
||||
// The result.
|
||||
return (
|
||||
<div className='composer--options'>
|
||||
<input
|
||||
accept={acceptContentTypes}
|
||||
disabled={disabled || !allowMedia}
|
||||
key={resetFileKey}
|
||||
onChange={this.handleChangeFiles}
|
||||
|
@ -320,6 +328,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
contentTypeItems.plain,
|
||||
contentTypeItems.html,
|
||||
contentTypeItems.markdown,
|
||||
contentTypeItems.bbcode,
|
||||
]}
|
||||
onChange={onChangeContentType}
|
||||
onModalClose={onModalClose}
|
||||
|
@ -342,12 +351,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
|||
disabled={disabled}
|
||||
icon='ellipsis-h'
|
||||
items={advancedOptions ? [
|
||||
{
|
||||
meta: <FormattedMessage {...messages.local_only_long} />,
|
||||
name: 'do_not_federate',
|
||||
on: advancedOptions.get('do_not_federate'),
|
||||
text: <FormattedMessage {...messages.local_only_short} />,
|
||||
},
|
||||
{
|
||||
meta: <FormattedMessage {...messages.threaded_mode_long} />,
|
||||
name: 'threaded_mode',
|
||||
|
|
|
@ -115,4 +115,5 @@ class Publisher extends ImmutablePureComponent {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||
<section>
|
||||
<h5><Icon id='quote-right' fixedWidth /><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
|
||||
|
||||
{results.get('statuses').map(statusId => <StatusContainer id={statusId} key={statusId}/>)}
|
||||
{results.get('statuses').map(statusId => <StatusContainer id={statusId} key={statusId} />)}
|
||||
|
||||
{results.get('statuses').size >= 5 && <LoadMore visible onClick={this.handleLoadMoreStatuses} />}
|
||||
</section>
|
||||
|
@ -131,4 +131,5 @@ class SearchResults extends ImmutablePureComponent {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -51,9 +51,10 @@ class TextareaIcons extends ImmutablePureComponent {
|
|||
id={icon}
|
||||
/>
|
||||
</span>
|
||||
) : null
|
||||
) : null,
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class Upload extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='composer--upload_form--item' tabIndex='0' role='button'>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}>
|
||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ scale }) => (
|
||||
<div style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||
<div className={classNames('composer--upload_form--actions', { active: true })}>
|
||||
|
|
|
@ -7,6 +7,7 @@ import SensitiveButtonContainer from '../containers/sensitive_button_container';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default class UploadForm extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
mediaIds: ImmutablePropTypes.list.isRequired,
|
||||
};
|
||||
|
|
|
@ -30,7 +30,8 @@ export default class UploadProgress extends React.PureComponent {
|
|||
<div className='backdrop'>
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
|
||||
{({ width }) =>
|
||||
(<div className='tracker' style={{ width: `${width}%` }}
|
||||
(<div
|
||||
className='tracker' style={{ width: `${width}%` }}
|
||||
/>)
|
||||
}
|
||||
</Motion>
|
||||
|
|
|
@ -22,11 +22,11 @@ import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
|
|||
|
||||
const messages = defineMessages({
|
||||
missingDescriptionMessage: { id: 'confirmations.missing_media_description.message',
|
||||
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
|
||||
defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
|
||||
missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm',
|
||||
defaultMessage: 'Send anyway' },
|
||||
defaultMessage: 'Send anyway' },
|
||||
missingDescriptionEdit: { id: 'confirmations.missing_media_description.edit',
|
||||
defaultMessage: 'Edit media' },
|
||||
defaultMessage: 'Edit media' },
|
||||
});
|
||||
|
||||
// State mapping.
|
||||
|
@ -38,12 +38,12 @@ function mapStateToProps (state) {
|
|||
const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null;
|
||||
let sideArmPrivacy = null;
|
||||
switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) {
|
||||
case 'copy':
|
||||
sideArmPrivacy = replyPrivacy;
|
||||
break;
|
||||
case 'restrict':
|
||||
sideArmPrivacy = sideArmRestrictedPrivacy;
|
||||
break;
|
||||
case 'copy':
|
||||
sideArmPrivacy = replyPrivacy;
|
||||
break;
|
||||
case 'restrict':
|
||||
sideArmPrivacy = sideArmRestrictedPrivacy;
|
||||
break;
|
||||
}
|
||||
sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
|
||||
return {
|
||||
|
|
|
@ -12,7 +12,6 @@ import Motion from 'flavours/glitch/util/optional_motion';
|
|||
import spring from 'react-motion/lib/spring';
|
||||
import SearchResultsContainer from './containers/search_results_container';
|
||||
import { me, mascot } from 'flavours/glitch/util/initial_state';
|
||||
import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
|
||||
import HeaderContainer from './containers/header_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -20,15 +19,10 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
elefriend: state.getIn(['compose', 'elefriend']),
|
||||
showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : ownProps.isSearchPage,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onClickElefriend () {
|
||||
dispatch(cycleElefriendCompose());
|
||||
},
|
||||
|
||||
onMount () {
|
||||
dispatch(mountCompose());
|
||||
},
|
||||
|
@ -37,16 +31,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
dispatch(unmountCompose());
|
||||
},
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class Compose extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
multiColumn: PropTypes.bool,
|
||||
showSearch: PropTypes.bool,
|
||||
isSearchPage: PropTypes.bool,
|
||||
elefriend: PropTypes.number,
|
||||
onClickElefriend: PropTypes.func,
|
||||
onMount: PropTypes.func,
|
||||
onUnmount: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -70,14 +62,12 @@ class Compose extends React.PureComponent {
|
|||
|
||||
render () {
|
||||
const {
|
||||
elefriend,
|
||||
intl,
|
||||
multiColumn,
|
||||
onClickElefriend,
|
||||
isSearchPage,
|
||||
showSearch,
|
||||
} = this.props;
|
||||
const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
|
||||
const computedClass = classNames('drawer', 'mbstobon');
|
||||
|
||||
return (
|
||||
<div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||
|
@ -92,7 +82,7 @@ class Compose extends React.PureComponent {
|
|||
<ComposeFormContainer />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
{mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
|
||||
{mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' />}
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ class Conversation extends ImmutablePureComponent {
|
|||
}
|
||||
destination = `/statuses/${lastStatus.get('id')}`;
|
||||
}
|
||||
let state = {...router.history.location.state};
|
||||
let state = { ...router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
router.history.push(destination, state);
|
||||
e.preventDefault();
|
||||
|
|
|
@ -7,7 +7,6 @@ import IconButton from 'flavours/glitch/components/icon_button';
|
|||
import Icon from 'flavours/glitch/components/icon';
|
||||
import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl';
|
||||
import { autoPlayGif, reduceMotion } from 'flavours/glitch/util/initial_state';
|
||||
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
|
||||
import { mascot } from 'flavours/glitch/util/initial_state';
|
||||
import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
|
||||
import classNames from 'classnames';
|
||||
|
@ -423,7 +422,7 @@ class Announcements extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='announcements'>
|
||||
<img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
|
||||
mascot && ( <img className='announcements__mastodon' alt='' draggable='false' src={mascot} /> )
|
||||
|
||||
<div className='announcements__container'>
|
||||
<ReactSwipeableViews animateHeight={!reduceMotion} adjustHeight={reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}>
|
||||
|
|
|
@ -16,6 +16,7 @@ import { fetchLists } from 'flavours/glitch/actions/lists';
|
|||
import { preferencesLink } from 'flavours/glitch/util/backend_links';
|
||||
import NavigationBar from '../compose/components/navigation_bar';
|
||||
import LinkFooter from 'flavours/glitch/features/ui/components/link_footer';
|
||||
import { fetchPanel, fetchPleromaConfig } from 'mastodon/actions/pleroma';
|
||||
import TrendsContainer from './containers/trends_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -55,6 +56,8 @@ const makeMapStateToProps = () => {
|
|||
columns: state.getIn(['settings', 'columns']),
|
||||
unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
|
||||
unreadNotifications: state.getIn(['notifications', 'unread']),
|
||||
customPanelEnabled: state.getIn(['custom_panel', 'enabled']),
|
||||
customPanel: state.getIn(['custom_panel', 'panel']),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
|
@ -64,6 +67,8 @@ const mapDispatchToProps = dispatch => ({
|
|||
fetchFollowRequests: () => dispatch(fetchFollowRequests()),
|
||||
fetchLists: () => dispatch(fetchLists()),
|
||||
openSettings: () => dispatch(openModal('SETTINGS', {})),
|
||||
fetchPanel: () => dispatch(fetchPanel()),
|
||||
fetchPleromaConfig: () => dispatch(fetchPleromaConfig()),
|
||||
});
|
||||
|
||||
const badgeDisplay = (number, limit) => {
|
||||
|
@ -78,9 +83,9 @@ const badgeDisplay = (number, limit) => {
|
|||
|
||||
const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
||||
|
||||
export default @connect(makeMapStateToProps, mapDispatchToProps)
|
||||
export default @connect(makeMapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class GettingStarted extends ImmutablePureComponent {
|
||||
class GettingStarted extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object.isRequired,
|
||||
|
@ -97,6 +102,10 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
lists: ImmutablePropTypes.list,
|
||||
fetchLists: PropTypes.func.isRequired,
|
||||
openSettings: PropTypes.func.isRequired,
|
||||
fetchPanel: PropTypes.func.isRequired,
|
||||
fetchPleromaConfig: PropTypes.func.isRequired,
|
||||
customPanelEnabled: PropTypes.bool,
|
||||
customPanel: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -104,7 +113,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { fetchFollowRequests, multiColumn } = this.props;
|
||||
const { fetchFollowRequests, multiColumn, fetchPleromaConfig, fetchPanel } = this.props;
|
||||
|
||||
if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) {
|
||||
this.context.router.history.replace('/timelines/home');
|
||||
|
@ -112,10 +121,13 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
}
|
||||
|
||||
fetchFollowRequests();
|
||||
|
||||
fetchPleromaConfig();
|
||||
fetchPanel();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props;
|
||||
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings, customPanelEnabled, customPanel } = this.props;
|
||||
|
||||
const navItems = [];
|
||||
let listItems = [];
|
||||
|
@ -160,11 +172,13 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
<div key='9'>
|
||||
<ColumnLink key='10' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
|
||||
{lists.map(list =>
|
||||
<ColumnLink key={(11 + Number(list.get('id'))).toString()} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
|
||||
<ColumnLink key={(11 + Number(list.get('id'))).toString()} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
|
||||
)}
|
||||
</div>,
|
||||
]);
|
||||
|
||||
const instance_panel = (customPanelEnabled ? <div className='getting-started getting-started__panel' dangerouslySetInnerHTML={{ __html: customPanel }} /> : null);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
||||
<div className='scrollable optionally-scrollable'>
|
||||
|
@ -179,6 +193,8 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
|||
<ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} />
|
||||
</div>
|
||||
|
||||
{instance_panel}
|
||||
|
||||
<LinkFooter />
|
||||
</div>
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class LocalSettingsNavigation extends React.PureComponent {
|
|||
/>
|
||||
<LocalSettingsNavigationItem
|
||||
active={index === 6}
|
||||
href={ preferencesLink }
|
||||
href={preferencesLink}
|
||||
index={6}
|
||||
icon='cog'
|
||||
title={intl.formatMessage(messages.preferences)}
|
||||
|
|
|
@ -59,7 +59,7 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
onChange={onChange}
|
||||
>
|
||||
<FormattedMessage id='settings.hicolor_privacy_icons' defaultMessage='High color privacy icons' />
|
||||
<span className='hint'><FormattedMessage id='settings.hicolor_privacy_icons.hint' defaultMessage="Display privacy icons in bright and easily distinguishable colors" /></span>
|
||||
<span className='hint'><FormattedMessage id='settings.hicolor_privacy_icons.hint' defaultMessage='Display privacy icons in bright and easily distinguishable colors' /></span>
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
|
@ -76,7 +76,7 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
onChange={onChange}
|
||||
>
|
||||
<FormattedMessage id='settings.tag_misleading_links' defaultMessage='Tag misleading links' />
|
||||
<span className='hint'><FormattedMessage id='settings.tag_misleading_links.hint' defaultMessage="Add a visual indication with the link target host to every link not mentioning it explicitly" /></span>
|
||||
<span className='hint'><FormattedMessage id='settings.tag_misleading_links.hint' defaultMessage='Add a visual indication with the link target host to every link not mentioning it explicitly' /></span>
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
settings={settings}
|
||||
|
@ -99,7 +99,7 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
id='mastodon-settings--notifications-tab_badge'
|
||||
onChange={onChange}
|
||||
>
|
||||
<FormattedMessage id='settings.notifications.tab_badge' defaultMessage="Unread notifications badge" />
|
||||
<FormattedMessage id='settings.notifications.tab_badge' defaultMessage='Unread notifications badge' />
|
||||
<span className='hint'><FormattedMessage id='settings.notifications.tab_badge.hint' defaultMessage="Display a badge for unread notifications in the column icons when the notifications column isn't open" /></span>
|
||||
</LocalSettingsPageItem>
|
||||
<LocalSettingsPageItem
|
||||
|
@ -109,7 +109,7 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
onChange={onChange}
|
||||
>
|
||||
<FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Unread notifications favicon badge' />
|
||||
<span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage="Add a badge for unread notifications to the favicon" /></span>
|
||||
<span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage='Add a badge for unread notifications to the favicon' /></span>
|
||||
</LocalSettingsPageItem>
|
||||
</section>
|
||||
<section>
|
||||
|
@ -272,7 +272,7 @@ class LocalSettingsPage extends React.PureComponent {
|
|||
{ value: 'drop', message: intl.formatMessage(messages.filters_drop) },
|
||||
{ value: 'upstream', message: intl.formatMessage(messages.filters_upstream) },
|
||||
{ value: 'hide', message: intl.formatMessage(messages.filters_hide) },
|
||||
{ value: 'content_warning', message: intl.formatMessage(messages.filters_cw) }
|
||||
{ value: 'content_warning', message: intl.formatMessage(messages.filters_cw) },
|
||||
]}
|
||||
>
|
||||
<FormattedMessage id='settings.filtering_behavior' defaultMessage='Filtering behavior' />
|
||||
|
|
|
@ -53,13 +53,14 @@ export default class LocalSettingsPageItem extends React.PureComponent {
|
|||
let optionId = `${id}--${opt.value}`;
|
||||
return (
|
||||
<label htmlFor={optionId}>
|
||||
<input type='radio'
|
||||
<input
|
||||
type='radio'
|
||||
name={id}
|
||||
id={optionId}
|
||||
value={opt.value}
|
||||
onBlur={handleChange}
|
||||
onChange={handleChange}
|
||||
checked={ currentValue === opt.value }
|
||||
checked={currentValue === opt.value}
|
||||
disabled={!enabled}
|
||||
/>
|
||||
{opt.message}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { injectIntl } from 'react-intl';
|
|||
import {
|
||||
fetchPinnedAccountsSuggestions,
|
||||
clearPinnedAccountsSuggestions,
|
||||
changePinnedAccountsSuggestions
|
||||
changePinnedAccountsSuggestions,
|
||||
} from '../../../actions/accounts';
|
||||
import Search from 'flavours/glitch/features/list_editor/components/search';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import ColumnSettings from '../components/column_settings';
|
||||
import { changeSetting } from 'flavours/glitch/actions/settings';
|
||||
import { changeColumnParams } from 'flavours/glitch/actions/columns';
|
||||
|
||||
|
||||
const mapStateToProps = (state, { columnId }) => {
|
||||
const uuid = columnId;
|
||||
const columns = state.getIn(['settings', 'columns']);
|
||||
|
|
|
@ -46,7 +46,7 @@ class PublicTimeline extends React.PureComponent {
|
|||
|
||||
dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
|
||||
}
|
||||
|
||||
|
||||
handleLoadMore = () => {
|
||||
const { dispatch, statusIds, local } = this.props;
|
||||
const maxId = statusIds.last();
|
||||
|
|
|
@ -163,7 +163,6 @@ class ActionBar extends React.PureComponent {
|
|||
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
|
||||
menu.push(null);
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick });
|
||||
|
|
|
@ -47,7 +47,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
|
||||
e.preventDefault();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
|||
parseClick = (e, destination) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
|
||||
e.preventDefault();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(destination, state);
|
||||
}
|
||||
|
|
|
@ -402,7 +402,7 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
|
||||
}
|
||||
|
@ -90,9 +90,9 @@ class BoostModal extends ImmutablePureComponent {
|
|||
<div className='boost-modal__action-bar'>
|
||||
<div>
|
||||
{ missingMediaDescription ?
|
||||
<FormattedMessage id='boost_modal.missing_description' defaultMessage='This toot contains some media without description' />
|
||||
<FormattedMessage id='boost_modal.missing_description' defaultMessage='This toot contains some media without description' />
|
||||
:
|
||||
<FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
|
||||
<FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
|
||||
}
|
||||
</div>
|
||||
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
|
||||
|
|
|
@ -27,7 +27,7 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return onClick(e);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<a href='#' onClick={onClick && handleOnClick} className='column-link' tabIndex='0'>
|
||||
<Icon id={icon} fixedWidth className='column-link__icon' />
|
||||
|
|
|
@ -575,7 +575,7 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
<div>
|
||||
<select aria-label='Canvas size' onInput={this.changeSize} defaultValue={this.size}>
|
||||
{ Object.values(mapValues(DOODLE_SIZES, (val, k) =>
|
||||
<option key={k} value={k}>{val[2]}</option>
|
||||
<option key={k} value={k}>{val[2]}</option>,
|
||||
)) }
|
||||
</select>
|
||||
</div>
|
||||
|
@ -602,7 +602,7 @@ class DoodleModal extends ImmutablePureComponent {
|
|||
'foreground': this.fg === c[0],
|
||||
'background': this.bg === c[0],
|
||||
})}
|
||||
/>
|
||||
/>,
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@ class FavouriteModal extends ImmutablePureComponent {
|
|||
if (e.button === 0) {
|
||||
e.preventDefault();
|
||||
this.props.onClose();
|
||||
let state = {...this.context.router.history.location.state};
|
||||
let state = { ...this.context.router.history.location.state };
|
||||
state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
|
||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`, state);
|
||||
}
|
||||
|
|
|
@ -1,67 +1,28 @@
|
|||
import { connect } from 'react-redux';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { invitesEnabled, version, repository, source_url } from 'flavours/glitch/util/initial_state';
|
||||
import { signOutLink, securityLink } from 'flavours/glitch/util/backend_links';
|
||||
import { logOut } from 'flavours/glitch/util/log_out';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' },
|
||||
logoutConfirm: { id: 'confirmations.logout.confirm', defaultMessage: 'Log out' },
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
onLogout () {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.logoutMessage),
|
||||
confirm: intl.formatMessage(messages.logoutConfirm),
|
||||
onConfirm: () => logOut(),
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
@connect(null, mapDispatchToProps)
|
||||
class LinkFooter extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleLogoutClick = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.props.onLogout();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='getting-started__footer'>
|
||||
<ul>
|
||||
{invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
|
||||
{!!securityLink && <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>}
|
||||
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
|
||||
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
|
||||
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
|
||||
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
|
||||
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
|
||||
<li><a href={signOutLink} onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='getting_started.open_source_notice'
|
||||
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
|
||||
id='getting_started.mastofe_notice'
|
||||
defaultMessage='Mastofe is a libre distribution of the frontend from {glitchsoc}, a friendly fork of {mastodon}. You can contribute or report issues at {mastofe}.'
|
||||
values={{
|
||||
github: <span><a href='https://github.com/glitch-soc/mastodon' rel='noopener noreferrer' target='_blank'>glitch-soc/mastodon</a> (v{version})</span>,
|
||||
Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener noreferrer' target='_blank'>Mastodon</a> }}
|
||||
mastofe: <a href='https://git.pleroma.social/pleroma/mastofe' rel='noopener noreferrer' target='_blank'>Mastofe</a>,
|
||||
glitchsoc: <a href='https://github.com/glitch-soc/mastodon' rel='noopener noreferrer' target='_blank'>glitch-soc</a>,
|
||||
mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener noreferrer' target='_blank'>Mastodon</a>,
|
||||
pleroma: <a href='https://pleroma.social' rel='noopener noreferrer' target='_blank'>Pleroma</a>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -49,7 +49,7 @@ const PageTwo = ({ intl, myAccount }) => (
|
|||
privacy='public'
|
||||
text='Awoo! #introductions'
|
||||
spoilerText=''
|
||||
suggestions={ [] }
|
||||
suggestions={[]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -386,7 +386,7 @@ class UI extends React.Component {
|
|||
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
||||
}
|
||||
|
||||
this.favicon = new Favico({ animation:"none" });
|
||||
this.favicon = new Favico({ animation:'none' });
|
||||
|
||||
this.props.dispatch(fetchMarkers());
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
|
@ -596,7 +596,7 @@ class UI extends React.Component {
|
|||
<PermaLink href={moved.get('url')} to={`/accounts/${moved.get('id')}`}>
|
||||
@{moved.get('acct')}
|
||||
</PermaLink>
|
||||
)}}
|
||||
) }}
|
||||
/>
|
||||
</div>)}
|
||||
<SwitchingColumnsArea location={location} layout={layout} navbarUnder={navbarUnder} onLayoutChange={this.handleLayoutChange}>
|
||||
|
|
|
@ -429,7 +429,7 @@ class Video extends React.PureComponent {
|
|||
|
||||
playerStyle.height = height;
|
||||
} else if (inline) {
|
||||
return (<div className={computedClass} ref={this.setPlayerRef} tabindex={0}></div>);
|
||||
return (<div className={computedClass} ref={this.setPlayerRef} tabindex={0} />);
|
||||
}
|
||||
|
||||
let warning;
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
Binary file not shown.
Before Width: | Height: | Size: 43 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
Binary file not shown.
Before Width: | Height: | Size: 32 KiB |
|
@ -84,7 +84,7 @@ const messages = {
|
|||
|
||||
'navigation_bar.direct': 'ダイレクトメッセージ',
|
||||
'navigation_bar.bookmarks': 'ブックマーク',
|
||||
'column.bookmarks': 'ブックマーク'
|
||||
'column.bookmarks': 'ブックマーク',
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
|
@ -67,13 +67,13 @@ const messages = {
|
|||
'advanced_options.threaded_mode.short': 'Tryb wątków',
|
||||
'advanced_options.threaded_mode.long': 'Przechodzi do tworzenia odpowiedzi po publikacji wpisu',
|
||||
'advanced_options.threaded_mode.tooltip': 'Włączono tryb wątków',
|
||||
|
||||
|
||||
'column.bookmarks': 'Zakładki',
|
||||
'compose_form.sensitive': 'Oznacz zawartość multimedialną jako wrażliwą',
|
||||
'compose_form.spoiler': 'Ukryj tekst za ostrzeżeniem',
|
||||
'favourite_modal.combo': 'Możesz nacisnąć {combo}, aby pominąć to następnym razem',
|
||||
'tabs_bar.compose': 'Napisz',
|
||||
|
||||
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
||||
|
|
|
@ -128,16 +128,16 @@ function apiStatusToTextMentions (state, status) {
|
|||
}
|
||||
|
||||
return set.union(status.mentions.filter(
|
||||
mention => mention.id !== me
|
||||
mention => mention.id !== me,
|
||||
).map(
|
||||
mention => `@${mention.acct} `
|
||||
mention => `@${mention.acct} `,
|
||||
)).join('');
|
||||
}
|
||||
|
||||
function apiStatusToTextHashtags (state, status) {
|
||||
const text = unescapeHTML(status.content);
|
||||
return ImmutableOrderedSet([]).union(recoverHashtags(status.tags, text).map(
|
||||
(name) => `#${name} `
|
||||
(name) => `#${name} `,
|
||||
)).join('');
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,7 @@ function clearAll(state) {
|
|||
map.set('in_reply_to', null);
|
||||
map.update(
|
||||
'advanced_options',
|
||||
map => map.mergeWith(overwrite, state.get('default_advanced_options'))
|
||||
map => map.mergeWith(overwrite, state.get('default_advanced_options')),
|
||||
);
|
||||
map.set('privacy', state.get('default_privacy'));
|
||||
map.set('sensitive', false);
|
||||
|
@ -178,7 +178,7 @@ function continueThread (state, status) {
|
|||
map.set('in_reply_to', status.id);
|
||||
map.update(
|
||||
'advanced_options',
|
||||
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(status.content) }))
|
||||
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(status.content) })),
|
||||
);
|
||||
map.set('privacy', status.visibility);
|
||||
map.set('sensitive', false);
|
||||
|
@ -295,7 +295,7 @@ const expiresInFromExpiresAt = expires_at => {
|
|||
const mergeLocalHashtagResults = (suggestions, prefix, tagHistory) => {
|
||||
prefix = prefix.toLowerCase();
|
||||
if (suggestions.length < 4) {
|
||||
const localTags = tagHistory.filter(tag => tag.toLowerCase().startsWith(prefix) && !suggestions.some(suggestion => suggestion.type === 'hashtag' && suggestion.name.toLowerCase() === tag.toLowerCase()));
|
||||
const localTags = tagHistory.filter(tag => tag && tag.toLowerCase().startsWith(prefix) && !suggestions.some(suggestion => suggestion.type === 'hashtag' && suggestion.name.toLowerCase() === tag.toLowerCase()));
|
||||
return suggestions.concat(localTags.slice(0, 4 - suggestions.length).toJS().map(tag => ({ type: 'hashtag', name: tag })));
|
||||
} else {
|
||||
return suggestions;
|
||||
|
@ -377,7 +377,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
|
||||
map.update(
|
||||
'advanced_options',
|
||||
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(action.status.get('content')) }))
|
||||
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(action.status.get('content')) })),
|
||||
);
|
||||
map.set('focusDate', new Date());
|
||||
map.set('caretPosition', null);
|
||||
|
@ -409,7 +409,7 @@ export default function compose(state = initialState, action) {
|
|||
map.set('poll', null);
|
||||
map.update(
|
||||
'advanced_options',
|
||||
map => map.mergeWith(overwrite, state.get('default_advanced_options'))
|
||||
map => map.mergeWith(overwrite, state.get('default_advanced_options')),
|
||||
);
|
||||
map.set('idempotencyKey', uuid());
|
||||
});
|
||||
|
|
|
@ -67,7 +67,7 @@ const deleteFromContexts = (immutableState, ids) => immutableState.withMutations
|
|||
|
||||
const filterContexts = (state, relationship, statuses) => {
|
||||
const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id)
|
||||
.map(status => status.get('id'));
|
||||
.map(status => status.get('id'));
|
||||
|
||||
return deleteFromContexts(state, ownedStatusIds);
|
||||
};
|
||||
|
|
|
@ -34,6 +34,7 @@ import suggestions from './suggestions';
|
|||
import pinnedAccountsEditor from './pinned_accounts_editor';
|
||||
import polls from './polls';
|
||||
import identity_proofs from './identity_proofs';
|
||||
import custom_panel from './pleroma';
|
||||
import trends from './trends';
|
||||
import announcements from './announcements';
|
||||
|
||||
|
@ -74,6 +75,7 @@ const reducers = {
|
|||
suggestions,
|
||||
pinnedAccountsEditor,
|
||||
polls,
|
||||
custom_panel,
|
||||
trends,
|
||||
};
|
||||
|
||||
|
|
|
@ -54,10 +54,10 @@ export default function listEditorReducer(state = initialState, action) {
|
|||
});
|
||||
case LIST_CREATE_REQUEST:
|
||||
case LIST_UPDATE_REQUEST:
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', true);
|
||||
map.set('isChanged', false);
|
||||
});
|
||||
return state.withMutations(map => {
|
||||
map.set('isSubmitting', true);
|
||||
map.set('isChanged', false);
|
||||
});
|
||||
case LIST_CREATE_FAIL:
|
||||
case LIST_UPDATE_FAIL:
|
||||
return state.set('isSubmitting', false);
|
||||
|
|
|
@ -21,7 +21,7 @@ const initialState = ImmutableMap({
|
|||
preselect_on_reply: true,
|
||||
inline_preview_cards: true,
|
||||
hicolor_privacy_icons: false,
|
||||
show_content_type_choice: false,
|
||||
show_content_type_choice: true,
|
||||
filtering_behavior: 'hide',
|
||||
tag_misleading_links: true,
|
||||
rewrite_mentions: 'no',
|
||||
|
|
|
@ -127,7 +127,7 @@ const clearUnread = (state) => {
|
|||
state = state.set('unread', state.get('pendingItems').size);
|
||||
const lastNotification = state.get('items').find(item => item !== null);
|
||||
return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0');
|
||||
}
|
||||
};
|
||||
|
||||
const updateTop = (state, top) => {
|
||||
state = state.set('top', top);
|
||||
|
@ -210,7 +210,7 @@ const recountUnread = (state, last_read_id) => {
|
|||
mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), last_read_id) > 0));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default function notifications(state = initialState, action) {
|
||||
let st;
|
||||
|
|
18
app/javascript/flavours/glitch/reducers/pleroma.js
Normal file
18
app/javascript/flavours/glitch/reducers/pleroma.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { PANEL_FETCH_SUCCESS, PLEROMA_CONFIG_FETCH_SUCCESS } from 'mastodon/actions/pleroma';
|
||||
|
||||
const initialPanel = ImmutableMap({
|
||||
enabled: false,
|
||||
panel: '',
|
||||
});
|
||||
|
||||
export default function custom_panel(state = initialPanel, action) {
|
||||
switch (action.type) {
|
||||
case PANEL_FETCH_SUCCESS:
|
||||
return state.set('panel', action.panel); break;
|
||||
case PLEROMA_CONFIG_FETCH_SUCCESS:
|
||||
return state.set('enabled', (action.config || {}).showInstanceSpecificPanel);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
|
@ -245,21 +245,3 @@
|
|||
height: 100%;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
}
|
||||
|
||||
@for $i from 0 through 3 {
|
||||
.mbstobon-#{$i} .drawer__inner__mastodon {
|
||||
@if $i == 3 {
|
||||
background: url('~flavours/glitch/images/wave-drawer.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
|
||||
} @else {
|
||||
background: url('~flavours/glitch/images/wave-drawer-glitched.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
|
||||
}
|
||||
|
||||
& > .mastodon {
|
||||
background: url("~flavours/glitch/images/mbstobon-ui-#{$i}.png") no-repeat left bottom / contain;
|
||||
|
||||
@if $i != 3 {
|
||||
filter: contrast(50%) brightness(50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -894,10 +894,15 @@
|
|||
color: $dark-text-color;
|
||||
}
|
||||
|
||||
&__panel {
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
&__panel,
|
||||
&__footer {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
flex: 0 1 auto;
|
||||
|
||||
ul {
|
||||
margin-bottom: 10px;
|
||||
|
@ -910,7 +915,6 @@
|
|||
p {
|
||||
color: $dark-text-color;
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
a {
|
||||
color: $dark-text-color;
|
||||
|
@ -1095,10 +1099,6 @@
|
|||
|
||||
.missing-indicator {
|
||||
padding-top: 20px + 48px;
|
||||
|
||||
.regeneration-indicator__figure {
|
||||
background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.scrollable > div > :first-child .notification__dismiss-overlay > .wrappy {
|
||||
|
|
|
@ -69,6 +69,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
.media-gallery__audio {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
text-align: center;
|
||||
color: $darker-text-color;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
audio {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery {
|
||||
box-sizing: border-box;
|
||||
margin-top: 8px;
|
||||
|
|
|
@ -255,7 +255,6 @@
|
|||
}
|
||||
|
||||
.onboarding-modal__page__wrapper-0 {
|
||||
background: url('~images/elephant_ui_greeting.svg') no-repeat left bottom / auto 250px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,12 @@
|
|||
margin: -3px 0 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
p, pre, blockquote {
|
||||
margin-bottom: 20px;
|
||||
white-space: pre-wrap;
|
||||
|
@ -149,7 +155,7 @@
|
|||
}
|
||||
|
||||
a {
|
||||
color: $secondary-text-color;
|
||||
color: $pleroma-links;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
@import 'mixins';
|
||||
@import 'variables';
|
||||
@import 'styles/fonts/roboto';
|
||||
@import 'styles/fonts/roboto-mono';
|
||||
@import 'styles/fonts/montserrat';
|
||||
|
||||
@import 'reset';
|
||||
@import 'basics';
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
> * {
|
||||
flex: 1;
|
||||
max-height: 235px;
|
||||
background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,17 @@ $gold-star: #ca8f04; // Dark Goldenrod
|
|||
|
||||
$red-bookmark: $warning-red;
|
||||
|
||||
// Pleroma-Dark colors
|
||||
$pleroma-bg: #121a24;
|
||||
$pleroma-fg: #182230;
|
||||
$pleroma-text: #b9b9ba;
|
||||
$pleroma-links: #d8a070;
|
||||
|
||||
// Values from the classic Mastodon UI
|
||||
$classic-base-color: #282c37; // Midnight Express
|
||||
$classic-primary-color: #9baec8; // Echo Blue
|
||||
$classic-secondary-color: #d9e1e8; // Pattens Blue
|
||||
$classic-highlight-color: #2b90d9; // Summer Sky
|
||||
$classic-base-color: $pleroma-bg;
|
||||
$classic-primary-color: #9baec8;
|
||||
$classic-secondary-color: #d9e1e8;
|
||||
$classic-highlight-color: #d8a070;
|
||||
|
||||
// Variables for defaults in UI
|
||||
$base-shadow-color: $black !default;
|
||||
|
@ -51,9 +57,9 @@ $media-modal-media-max-height: 80%;
|
|||
|
||||
$no-gap-breakpoint: 415px;
|
||||
|
||||
$font-sans-serif: 'mastodon-font-sans-serif' !default;
|
||||
$font-display: 'mastodon-font-display' !default;
|
||||
$font-monospace: 'mastodon-font-monospace' !default;
|
||||
$font-sans-serif: sans-serif !default;
|
||||
$font-display: sans-serif !default;
|
||||
$font-monospace: monospace !default;
|
||||
|
||||
// Avatar border size (8% default, 100% for rounded avatars)
|
||||
$ui-avatar-border-size: 8%;
|
||||
|
|
|
@ -27,10 +27,6 @@ pack:
|
|||
# language tags and whose default exports are a messages object.
|
||||
locales: locales
|
||||
|
||||
# (OPTIONAL) A file to use as the preview screenshot for the flavour,
|
||||
# or an array thereof. These are the full path from `app/javascript/`.
|
||||
screenshot: flavours/glitch/images/glitch-preview.jpg
|
||||
|
||||
# (OPTIONAL) The directory which contains the pack files.
|
||||
# Defaults to the theme directory (`app/javascript/themes/[theme]`),
|
||||
# which should be sufficient for like 99% of use-cases lol.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import axios from 'axios';
|
||||
import ready from './ready';
|
||||
import LinkHeader from 'http-link-header';
|
||||
|
||||
export const getLinks = response => {
|
||||
|
@ -12,21 +11,10 @@ export const getLinks = response => {
|
|||
return LinkHeader.parse(value);
|
||||
};
|
||||
|
||||
let csrfHeader = {};
|
||||
|
||||
function setCSRFHeader() {
|
||||
const csrfToken = document.querySelector('meta[name=csrf-token]');
|
||||
if (csrfToken) {
|
||||
csrfHeader['X-CSRF-Token'] = csrfToken.content;
|
||||
}
|
||||
}
|
||||
|
||||
ready(setCSRFHeader);
|
||||
|
||||
export default getState => axios.create({
|
||||
headers: Object.assign(csrfHeader, getState ? {
|
||||
headers: getState ? {
|
||||
'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`,
|
||||
} : {}),
|
||||
} : {},
|
||||
|
||||
transformResponse: [function (data) {
|
||||
try {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
export const preferencesLink = '/settings/preferences';
|
||||
export const profileLink = '/settings/profile';
|
||||
export const preferencesLink = '/settings';
|
||||
export const profileLink = '/user-settings';
|
||||
export const signOutLink = '/auth/sign_out';
|
||||
export const termsLink = '/terms';
|
||||
export const accountAdminLink = (id) => `/admin/accounts/${id}`;
|
||||
export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses/${status_id}`;
|
||||
export const filterEditLink = (id) => `/filters/${id}/edit`;
|
||||
export const relationshipsLink = '/relationships';
|
||||
export const securityLink = '/auth/edit';
|
||||
export const accountAdminLink = (id) => `/pleroma/admin/#/users/${id}/`;
|
||||
export const statusAdminLink = (account_id, status_id) => `/pleroma/admin/#/users/${account_id}/`;
|
||||
export const filterEditLink = undefined; // (id) => `/filters/${id}/edit`;
|
||||
export const relationshipsLink = undefined; // = '/relationships';
|
||||
export const securityLink = undefined ; // = '/auth/edit';
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { urlRegex } from './url_regex';
|
||||
|
||||
const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx';
|
||||
|
||||
export function countableText(inputText) {
|
||||
return inputText
|
||||
.replace(urlRegex, urlPlaceholder)
|
||||
.replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3');
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
export function recoverHashtags (recognizedTags, text) {
|
||||
return recognizedTags.map(tag => {
|
||||
const re = new RegExp(`(?:^|[^\/\)\w])#(${tag.name})`, 'i');
|
||||
const matched_hashtag = text.match(re);
|
||||
return matched_hashtag ? matched_hashtag[1] : null;
|
||||
}
|
||||
const re = new RegExp(`(?:^|[^\/\)\w])#(${tag.name})`, 'i');
|
||||
const matched_hashtag = text.match(re);
|
||||
return matched_hashtag ? matched_hashtag[1] : null;
|
||||
},
|
||||
).filter(x => x !== null);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
import { get, set } from 'lodash';
|
||||
|
||||
const maybeSetNotificationsSettings = result => {
|
||||
const me = get(result, ['meta', 'me']);
|
||||
if(!me) return;
|
||||
|
||||
const showTypes = get(result, ['settings', 'notifications', 'shows'], {});
|
||||
const excludeTypes = get(result, ['accounts', me, 'pleroma', 'notification_settings', 'exclude_types'], []);
|
||||
|
||||
excludeTypes.forEach(x => showTypes[x] = false);
|
||||
set(result, ['settings', 'notifications', 'shows'], showTypes);
|
||||
};
|
||||
|
||||
const element = document.getElementById('initial-state');
|
||||
const initialState = element && function () {
|
||||
const result = JSON.parse(element.textContent);
|
||||
|
@ -6,10 +19,12 @@ const initialState = element && function () {
|
|||
} catch (e) {
|
||||
result.local_settings = {};
|
||||
}
|
||||
maybeSetNotificationsSettings(result);
|
||||
return result;
|
||||
}();
|
||||
|
||||
const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop];
|
||||
const getRight = (prop) => initialState && initialState.rights && initialState.rights[prop];
|
||||
|
||||
export const reduceMotion = getMeta('reduce_motion');
|
||||
export const autoPlayGif = getMeta('auto_play_gif');
|
||||
|
@ -21,7 +36,7 @@ export const favouriteModal = getMeta('favourite_modal');
|
|||
export const deleteModal = getMeta('delete_modal');
|
||||
export const me = getMeta('me');
|
||||
export const searchEnabled = getMeta('search_enabled');
|
||||
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
|
||||
export const maxChars = getMeta('max_toot_chars') || 500;
|
||||
export const pollLimits = (initialState && initialState.poll_limits);
|
||||
export const invitesEnabled = getMeta('invites_enabled');
|
||||
export const version = getMeta('version');
|
||||
|
@ -35,4 +50,6 @@ export const usePendingItems = getMeta('use_pending_items');
|
|||
export const useSystemEmojiFont = getMeta('system_emoji_font');
|
||||
export const showTrends = getMeta('trends');
|
||||
|
||||
export const deleteOthersNotice = getRight('delete_others_notice');
|
||||
|
||||
export default initialState;
|
||||
|
|
|
@ -7,7 +7,7 @@ export function assignHandlers (target, handlers) {
|
|||
// We just bind each handler to the `target`.
|
||||
const handle = target.handlers = {};
|
||||
Object.keys(handlers).forEach(
|
||||
key => handle[key] = handlers[key].bind(target)
|
||||
key => handle[key] = handlers[key].bind(target),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,10 @@ export default function getStream(streamingAPIBaseURL, accessToken, stream, { co
|
|||
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
|
||||
|
||||
ws.onopen = connected;
|
||||
ws.onmessage = e => received(JSON.parse(e.data));
|
||||
ws.onmessage = e => {
|
||||
if (e.data !== '')
|
||||
received(JSON.parse(e.data));
|
||||
};
|
||||
ws.onclose = disconnected;
|
||||
ws.onreconnect = reconnected;
|
||||
|
||||
|
|
|
@ -25,10 +25,6 @@ pack:
|
|||
# the flavour, relative to this directory.
|
||||
locales: ../../mastodon/locales
|
||||
|
||||
# (OPTIONAL) A file to use as the preview screenshot for the flavour,
|
||||
# or an array thereof. These are the full path from `app/javascript/`.
|
||||
screenshot: images/screenshot.jpg
|
||||
|
||||
# (OPTIONAL) The directory which contains the pack files.
|
||||
# Defaults to this directory (`app/javascript/flavour/[flavour]`),
|
||||
# but in the case of the vanilla Mastodon flavour the pack files are
|
||||
|
|
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue