]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/reducers/compose.js
fix #6523 (#6524)
[mastodon.git] / app / javascript / mastodon / reducers / compose.js
1 import {
2 COMPOSE_MOUNT,
3 COMPOSE_UNMOUNT,
4 COMPOSE_CHANGE,
5 COMPOSE_REPLY,
6 COMPOSE_REPLY_CANCEL,
7 COMPOSE_MENTION,
8 COMPOSE_SUBMIT_REQUEST,
9 COMPOSE_SUBMIT_SUCCESS,
10 COMPOSE_SUBMIT_FAIL,
11 COMPOSE_UPLOAD_REQUEST,
12 COMPOSE_UPLOAD_SUCCESS,
13 COMPOSE_UPLOAD_FAIL,
14 COMPOSE_UPLOAD_UNDO,
15 COMPOSE_UPLOAD_PROGRESS,
16 COMPOSE_SUGGESTIONS_CLEAR,
17 COMPOSE_SUGGESTIONS_READY,
18 COMPOSE_SUGGESTION_SELECT,
19 COMPOSE_SENSITIVITY_CHANGE,
20 COMPOSE_SPOILERNESS_CHANGE,
21 COMPOSE_SPOILER_TEXT_CHANGE,
22 COMPOSE_VISIBILITY_CHANGE,
23 COMPOSE_COMPOSING_CHANGE,
24 COMPOSE_EMOJI_INSERT,
25 COMPOSE_UPLOAD_CHANGE_REQUEST,
26 COMPOSE_UPLOAD_CHANGE_SUCCESS,
27 COMPOSE_UPLOAD_CHANGE_FAIL,
28 COMPOSE_RESET,
29 } from '../actions/compose';
30 import { TIMELINE_DELETE } from '../actions/timelines';
31 import { STORE_HYDRATE } from '../actions/store';
32 import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
33 import uuid from '../uuid';
34 import { me } from '../initial_state';
35
36 const initialState = ImmutableMap({
37 mounted: 0,
38 sensitive: false,
39 spoiler: false,
40 spoiler_text: '',
41 privacy: null,
42 text: '',
43 focusDate: null,
44 preselectDate: null,
45 in_reply_to: null,
46 is_composing: false,
47 is_submitting: false,
48 is_uploading: false,
49 progress: 0,
50 media_attachments: ImmutableList(),
51 suggestion_token: null,
52 suggestions: ImmutableList(),
53 default_privacy: 'public',
54 default_sensitive: false,
55 resetFileKey: Math.floor((Math.random() * 0x10000)),
56 idempotencyKey: null,
57 });
58
59 function statusToTextMentions(state, status) {
60 let set = ImmutableOrderedSet([]);
61
62 if (status.getIn(['account', 'id']) !== me) {
63 set = set.add(`@${status.getIn(['account', 'acct'])} `);
64 }
65
66 return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
67 };
68
69 function clearAll(state) {
70 return state.withMutations(map => {
71 map.set('text', '');
72 map.set('spoiler', false);
73 map.set('spoiler_text', '');
74 map.set('is_submitting', false);
75 map.set('in_reply_to', null);
76 map.set('privacy', state.get('default_privacy'));
77 map.set('sensitive', false);
78 map.update('media_attachments', list => list.clear());
79 map.set('idempotencyKey', uuid());
80 });
81 };
82
83 function appendMedia(state, media) {
84 const prevSize = state.get('media_attachments').size;
85
86 return state.withMutations(map => {
87 map.update('media_attachments', list => list.push(media));
88 map.set('is_uploading', false);
89 map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
90 map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
91 map.set('focusDate', new Date());
92 map.set('idempotencyKey', uuid());
93
94 if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
95 map.set('sensitive', true);
96 }
97 });
98 };
99
100 function removeMedia(state, mediaId) {
101 const media = state.get('media_attachments').find(item => item.get('id') === mediaId);
102 const prevSize = state.get('media_attachments').size;
103
104 return state.withMutations(map => {
105 map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
106 map.update('text', text => text.replace(media.get('text_url'), '').trim());
107 map.set('idempotencyKey', uuid());
108
109 if (prevSize === 1) {
110 map.set('sensitive', false);
111 }
112 });
113 };
114
115 const insertSuggestion = (state, position, token, completion) => {
116 return state.withMutations(map => {
117 map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
118 map.set('suggestion_token', null);
119 map.update('suggestions', ImmutableList(), list => list.clear());
120 map.set('focusDate', new Date());
121 map.set('idempotencyKey', uuid());
122 });
123 };
124
125 const insertEmoji = (state, position, emojiData) => {
126 const emoji = emojiData.native;
127
128 return state.withMutations(map => {
129 map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
130 map.set('focusDate', new Date());
131 map.set('idempotencyKey', uuid());
132 });
133 };
134
135 const privacyPreference = (a, b) => {
136 if (a === 'direct' || b === 'direct') {
137 return 'direct';
138 } else if (a === 'private' || b === 'private') {
139 return 'private';
140 } else if (a === 'unlisted' || b === 'unlisted') {
141 return 'unlisted';
142 } else {
143 return 'public';
144 }
145 };
146
147 const hydrate = (state, hydratedState) => {
148 state = clearAll(state.merge(hydratedState));
149
150 if (hydratedState.has('text')) {
151 state = state.set('text', hydratedState.get('text'));
152 }
153
154 return state;
155 };
156
157 export default function compose(state = initialState, action) {
158 switch(action.type) {
159 case STORE_HYDRATE:
160 return hydrate(state, action.state.get('compose'));
161 case COMPOSE_MOUNT:
162 return state.set('mounted', state.get('mounted') + 1);
163 case COMPOSE_UNMOUNT:
164 return state
165 .set('mounted', Math.max(state.get('mounted') - 1, 0))
166 .set('is_composing', false);
167 case COMPOSE_SENSITIVITY_CHANGE:
168 return state.withMutations(map => {
169 if (!state.get('spoiler')) {
170 map.set('sensitive', !state.get('sensitive'));
171 }
172
173 map.set('idempotencyKey', uuid());
174 });
175 case COMPOSE_SPOILERNESS_CHANGE:
176 return state.withMutations(map => {
177 map.set('spoiler_text', '');
178 map.set('spoiler', !state.get('spoiler'));
179 map.set('idempotencyKey', uuid());
180
181 if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
182 map.set('sensitive', true);
183 }
184 });
185 case COMPOSE_SPOILER_TEXT_CHANGE:
186 return state
187 .set('spoiler_text', action.text)
188 .set('idempotencyKey', uuid());
189 case COMPOSE_VISIBILITY_CHANGE:
190 return state
191 .set('privacy', action.value)
192 .set('idempotencyKey', uuid());
193 case COMPOSE_CHANGE:
194 return state
195 .set('text', action.text)
196 .set('idempotencyKey', uuid());
197 case COMPOSE_COMPOSING_CHANGE:
198 return state.set('is_composing', action.value);
199 case COMPOSE_REPLY:
200 return state.withMutations(map => {
201 map.set('in_reply_to', action.status.get('id'));
202 map.set('text', statusToTextMentions(state, action.status));
203 map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
204 map.set('focusDate', new Date());
205 map.set('preselectDate', new Date());
206 map.set('idempotencyKey', uuid());
207
208 if (action.status.get('spoiler_text').length > 0) {
209 map.set('spoiler', true);
210 map.set('spoiler_text', action.status.get('spoiler_text'));
211 } else {
212 map.set('spoiler', false);
213 map.set('spoiler_text', '');
214 }
215 });
216 case COMPOSE_REPLY_CANCEL:
217 case COMPOSE_RESET:
218 return state.withMutations(map => {
219 map.set('in_reply_to', null);
220 map.set('text', '');
221 map.set('spoiler', false);
222 map.set('spoiler_text', '');
223 map.set('privacy', state.get('default_privacy'));
224 map.set('idempotencyKey', uuid());
225 });
226 case COMPOSE_SUBMIT_REQUEST:
227 case COMPOSE_UPLOAD_CHANGE_REQUEST:
228 return state.set('is_submitting', true);
229 case COMPOSE_SUBMIT_SUCCESS:
230 return clearAll(state);
231 case COMPOSE_SUBMIT_FAIL:
232 case COMPOSE_UPLOAD_CHANGE_FAIL:
233 return state.set('is_submitting', false);
234 case COMPOSE_UPLOAD_REQUEST:
235 return state.set('is_uploading', true);
236 case COMPOSE_UPLOAD_SUCCESS:
237 return appendMedia(state, fromJS(action.media));
238 case COMPOSE_UPLOAD_FAIL:
239 return state.set('is_uploading', false);
240 case COMPOSE_UPLOAD_UNDO:
241 return removeMedia(state, action.media_id);
242 case COMPOSE_UPLOAD_PROGRESS:
243 return state.set('progress', Math.round((action.loaded / action.total) * 100));
244 case COMPOSE_MENTION:
245 return state
246 .update('text', text => `${text}@${action.account.get('acct')} `)
247 .set('focusDate', new Date())
248 .set('idempotencyKey', uuid());
249 case COMPOSE_SUGGESTIONS_CLEAR:
250 return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
251 case COMPOSE_SUGGESTIONS_READY:
252 return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
253 case COMPOSE_SUGGESTION_SELECT:
254 return insertSuggestion(state, action.position, action.token, action.completion);
255 case TIMELINE_DELETE:
256 if (action.id === state.get('in_reply_to')) {
257 return state.set('in_reply_to', null);
258 } else {
259 return state;
260 }
261 case COMPOSE_EMOJI_INSERT:
262 return insertEmoji(state, action.position, action.emoji);
263 case COMPOSE_UPLOAD_CHANGE_SUCCESS:
264 return state
265 .set('is_submitting', false)
266 .update('media_attachments', list => list.map(item => {
267 if (item.get('id') === action.media.id) {
268 return item.set('description', action.media.description);
269 }
270
271 return item;
272 }));
273 default:
274 return state;
275 }
276 };
This page took 0.269081 seconds and 5 git commands to generate.