diff options
author | Starfall <us@starfall.systems> | 2022-11-10 08:50:11 -0600 |
---|---|---|
committer | Starfall <us@starfall.systems> | 2022-11-10 08:50:11 -0600 |
commit | 67d1a0476d77e2ed0ca15dd2981c54c2b90b0742 (patch) | |
tree | 152f8c13a341d76738e8e2c09b24711936e6af68 /app/javascript/flavours | |
parent | b581e6b6d4a5ba9ed4ae17427b7f2d5d158be4e5 (diff) | |
parent | ee7e49d1b1323618e16026bc8db8ab7f9459cc2d (diff) |
Merge remote-tracking branch 'glitch/main'
- Remove Helm charts - Lots of conflicts with our removal of recommended settings and custom icons
Diffstat (limited to 'app/javascript/flavours')
331 files changed, 7005 insertions, 4786 deletions
diff --git a/app/javascript/flavours/glitch/actions/account_notes.js b/app/javascript/flavours/glitch/actions/account_notes.js index c1cce3193..059ed9e80 100644 --- a/app/javascript/flavours/glitch/actions/account_notes.js +++ b/app/javascript/flavours/glitch/actions/account_notes.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; export const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST'; export const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS'; diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js index f5871beb3..dc670e50a 100644 --- a/app/javascript/flavours/glitch/actions/accounts.js +++ b/app/javascript/flavours/glitch/actions/accounts.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; @@ -553,10 +553,12 @@ export function expandFollowingFail(id, error) { export function fetchRelationships(accountIds) { return (dispatch, getState) => { - const loadedRelationships = getState().get('relationships'); + const state = getState(); + const loadedRelationships = state.get('relationships'); const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null); + const signedIn = !!state.getIn(['meta', 'me']); - if (newAccountIds.length === 0) { + if (!signedIn || newAccountIds.length === 0) { return; } diff --git a/app/javascript/flavours/glitch/actions/announcements.js b/app/javascript/flavours/glitch/actions/announcements.js index 871409d43..1bdea909f 100644 --- a/app/javascript/flavours/glitch/actions/announcements.js +++ b/app/javascript/flavours/glitch/actions/announcements.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { normalizeAnnouncement } from './importer/normalizer'; export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/app.js b/app/javascript/flavours/glitch/actions/app.js new file mode 100644 index 000000000..de2d93e29 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/app.js @@ -0,0 +1,6 @@ +export const APP_LAYOUT_CHANGE = 'APP_LAYOUT_CHANGE'; + +export const changeLayout = layout => ({ + type: APP_LAYOUT_CHANGE, + layout, +}); diff --git a/app/javascript/flavours/glitch/actions/blocks.js b/app/javascript/flavours/glitch/actions/blocks.js index adae9d83c..fd9881302 100644 --- a/app/javascript/flavours/glitch/actions/blocks.js +++ b/app/javascript/flavours/glitch/actions/blocks.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; import { openModal } from './modal'; diff --git a/app/javascript/flavours/glitch/actions/bookmarks.js b/app/javascript/flavours/glitch/actions/bookmarks.js index 83dbf5407..544ed2ff2 100644 --- a/app/javascript/flavours/glitch/actions/bookmarks.js +++ b/app/javascript/flavours/glitch/actions/bookmarks.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import { importFetchedStatuses } from './importer'; export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index ab74fb303..54909b56e 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -1,19 +1,21 @@ -import api from 'flavours/glitch/util/api'; -import { CancelToken, isCancel } from 'axios'; +import axios from 'axios'; import { throttle } from 'lodash'; -import { search as emojiSearch } from 'flavours/glitch/util/emoji/emoji_mart_search_light'; +import { defineMessages } from 'react-intl'; +import api from 'flavours/glitch/api'; +import { search as emojiSearch } from 'flavours/glitch/features/emoji/emoji_mart_search_light'; +import { tagHistory } from 'flavours/glitch/settings'; +import { recoverHashtags } from 'flavours/glitch/utils/hashtag'; +import resizeImage from 'flavours/glitch/utils/resize_image'; +import { showAlert, showAlertForError } from './alerts'; 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'; -import { showAlert } from './alerts'; +import { importFetchedAccounts, importFetchedStatus } from './importer'; import { openModal } from './modal'; -import { defineMessages } from 'react-intl'; +import { updateTimeline } from './timelines'; -let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags; +/** @type {AbortController | undefined} */ +let fetchComposeSuggestionsAccountsController; +/** @type {AbortController | undefined} */ +let fetchComposeSuggestionsTagsController; export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; export const COMPOSE_CYCLE_ELEFRIEND = 'COMPOSE_CYCLE_ELEFRIEND'; @@ -25,11 +27,13 @@ export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; export const COMPOSE_DIRECT = 'COMPOSE_DIRECT'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_RESET = 'COMPOSE_RESET'; -export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; -export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; -export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; -export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; -export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; + +export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; +export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; +export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; +export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; +export const COMPOSE_UPLOAD_PROCESSING = 'COMPOSE_UPLOAD_PROCESSING'; +export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; export const THUMBNAIL_UPLOAD_REQUEST = 'THUMBNAIL_UPLOAD_REQUEST'; export const THUMBNAIL_UPLOAD_SUCCESS = 'THUMBNAIL_UPLOAD_SUCCESS'; @@ -83,10 +87,8 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }); -const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); - export const ensureComposeIsVisible = (getState, routerHistory) => { - if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { + if (!getState().getIn(['compose', 'mounted'])) { routerHistory.push('/publish'); } }; @@ -221,6 +223,10 @@ export function submitCompose(routerHistory) { } }; + if (statusId) { + dispatch(importFetchedStatus({ ...response.data })); + } + if (statusId === null) { insertIfOnline('home'); } @@ -307,13 +313,16 @@ export function uploadCompose(files) { if (status === 200) { dispatch(uploadComposeSuccess(data, f)); } else if (status === 202) { + dispatch(uploadComposeProcessing()); + let tryCount = 1; + 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) { - let retryAfter = (Math.log2(tryCount) || 1) * 1000; + const retryAfter = (Math.log2(tryCount) || 1) * 1000; tryCount += 1; setTimeout(() => poll(), retryAfter); } @@ -328,6 +337,10 @@ export function uploadCompose(files) { }; }; +export const uploadComposeProcessing = () => ({ + type: COMPOSE_UPLOAD_PROCESSING, +}); + export const uploadThumbnail = (id, file) => (dispatch, getState) => { dispatch(uploadThumbnailRequest()); @@ -472,8 +485,8 @@ export function undoUploadCompose(media_id) { }; export function clearComposeSuggestions() { - if (cancelFetchComposeSuggestionsAccounts) { - cancelFetchComposeSuggestionsAccounts(); + if (fetchComposeSuggestionsAccountsController) { + fetchComposeSuggestionsAccountsController.abort(); } return { type: COMPOSE_SUGGESTIONS_CLEAR, @@ -481,14 +494,14 @@ export function clearComposeSuggestions() { }; const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => { - if (cancelFetchComposeSuggestionsAccounts) { - cancelFetchComposeSuggestionsAccounts(); + if (fetchComposeSuggestionsAccountsController) { + fetchComposeSuggestionsAccountsController.abort(); } + fetchComposeSuggestionsAccountsController = new AbortController(); + api(getState).get('/api/v1/accounts/search', { - cancelToken: new CancelToken(cancel => { - cancelFetchComposeSuggestionsAccounts = cancel; - }), + signal: fetchComposeSuggestionsAccountsController.signal, params: { q: token.slice(1), @@ -499,9 +512,11 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => dispatch(importFetchedAccounts(response.data)); dispatch(readyComposeSuggestionsAccounts(token, response.data)); }).catch(error => { - if (!isCancel(error)) { + if (!axios.isCancel(error)) { dispatch(showAlertForError(error)); } + }).finally(() => { + fetchComposeSuggestionsAccountsController = undefined; }); }, 200, { leading: true, trailing: true }); @@ -511,16 +526,16 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => { }; const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => { - if (cancelFetchComposeSuggestionsTags) { - cancelFetchComposeSuggestionsTags(); + if (fetchComposeSuggestionsTagsController) { + fetchComposeSuggestionsTagsController.abort(); } dispatch(updateSuggestionTags(token)); + fetchComposeSuggestionsTagsController = new AbortController(); + api(getState).get('/api/v2/search', { - cancelToken: new CancelToken(cancel => { - cancelFetchComposeSuggestionsTags = cancel; - }), + signal: fetchComposeSuggestionsTagsController.signal, params: { type: 'hashtags', @@ -531,9 +546,11 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => { }).then(({ data }) => { dispatch(readyComposeSuggestionsTags(token, data.hashtags)); }).catch(error => { - if (!isCancel(error)) { + if (!axios.isCancel(error)) { dispatch(showAlertForError(error)); } + }).finally(() => { + fetchComposeSuggestionsTagsController = undefined; }); }, 200, { leading: true, trailing: true }); diff --git a/app/javascript/flavours/glitch/actions/conversations.js b/app/javascript/flavours/glitch/actions/conversations.js index e5c85c65d..4ef654b1f 100644 --- a/app/javascript/flavours/glitch/actions/conversations.js +++ b/app/javascript/flavours/glitch/actions/conversations.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import { importFetchedAccounts, importFetchedStatuses, diff --git a/app/javascript/flavours/glitch/actions/custom_emojis.js b/app/javascript/flavours/glitch/actions/custom_emojis.js index 29ae72edb..7b7d0091b 100644 --- a/app/javascript/flavours/glitch/actions/custom_emojis.js +++ b/app/javascript/flavours/glitch/actions/custom_emojis.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; export const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST'; export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS'; diff --git a/app/javascript/flavours/glitch/actions/directory.js b/app/javascript/flavours/glitch/actions/directory.js index 9fbfb7f5b..4b2b6dd56 100644 --- a/app/javascript/flavours/glitch/actions/directory.js +++ b/app/javascript/flavours/glitch/actions/directory.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedAccounts } from './importer'; import { fetchRelationships } from './accounts'; diff --git a/app/javascript/flavours/glitch/actions/domain_blocks.js b/app/javascript/flavours/glitch/actions/domain_blocks.js index 6d3f471fa..34a33a654 100644 --- a/app/javascript/flavours/glitch/actions/domain_blocks.js +++ b/app/javascript/flavours/glitch/actions/domain_blocks.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST'; export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS'; diff --git a/app/javascript/flavours/glitch/actions/favourites.js b/app/javascript/flavours/glitch/actions/favourites.js index 0d8bfb14d..9448b1efe 100644 --- a/app/javascript/flavours/glitch/actions/favourites.js +++ b/app/javascript/flavours/glitch/actions/favourites.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import { importFetchedStatuses } from './importer'; export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/featured_tags.js b/app/javascript/flavours/glitch/actions/featured_tags.js new file mode 100644 index 000000000..18bb61539 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/featured_tags.js @@ -0,0 +1,34 @@ +import api from '../api'; + +export const FEATURED_TAGS_FETCH_REQUEST = 'FEATURED_TAGS_FETCH_REQUEST'; +export const FEATURED_TAGS_FETCH_SUCCESS = 'FEATURED_TAGS_FETCH_SUCCESS'; +export const FEATURED_TAGS_FETCH_FAIL = 'FEATURED_TAGS_FETCH_FAIL'; + +export const fetchFeaturedTags = (id) => (dispatch, getState) => { + if (getState().getIn(['user_lists', 'featured_tags', id, 'items'])) { + return; + } + + dispatch(fetchFeaturedTagsRequest(id)); + + api(getState).get(`/api/v1/accounts/${id}/featured_tags`) + .then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data))) + .catch(err => dispatch(fetchFeaturedTagsFail(id, err))); +}; + +export const fetchFeaturedTagsRequest = (id) => ({ + type: FEATURED_TAGS_FETCH_REQUEST, + id, +}); + +export const fetchFeaturedTagsSuccess = (id, tags) => ({ + type: FEATURED_TAGS_FETCH_SUCCESS, + id, + tags, +}); + +export const fetchFeaturedTagsFail = (id, error) => ({ + type: FEATURED_TAGS_FETCH_FAIL, + id, + error, +}); diff --git a/app/javascript/flavours/glitch/actions/filters.js b/app/javascript/flavours/glitch/actions/filters.js index 9aa31028a..76326802e 100644 --- a/app/javascript/flavours/glitch/actions/filters.js +++ b/app/javascript/flavours/glitch/actions/filters.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { openModal } from './modal'; export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/history.js b/app/javascript/flavours/glitch/actions/history.js index c47057261..c142aaf61 100644 --- a/app/javascript/flavours/glitch/actions/history.js +++ b/app/javascript/flavours/glitch/actions/history.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedAccounts } from './importer'; export const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/identity_proofs.js b/app/javascript/flavours/glitch/actions/identity_proofs.js index 18e679aec..103983956 100644 --- a/app/javascript/flavours/glitch/actions/identity_proofs.js +++ b/app/javascript/flavours/glitch/actions/identity_proofs.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; export const IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST = 'IDENTITY_PROOFS_ACCOUNT_FETCH_REQUEST'; export const IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS = 'IDENTITY_PROOFS_ACCOUNT_FETCH_SUCCESS'; diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index 9950a720b..1c9f524e4 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -1,7 +1,7 @@ import escapeTextContentForBrowser from 'escape-html'; -import emojify from 'flavours/glitch/util/emoji'; -import { unescapeHTML } from 'flavours/glitch/util/html'; -import { autoHideCW } from 'flavours/glitch/util/content_warning'; +import emojify from 'flavours/glitch/features/emoji/emoji'; +import { unescapeHTML } from 'flavours/glitch/utils/html'; +import { autoHideCW } from 'flavours/glitch/utils/content_warning'; const domParser = new DOMParser(); diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js index 336c8fa90..225ee7eb2 100644 --- a/app/javascript/flavours/glitch/actions/interactions.js +++ b/app/javascript/flavours/glitch/actions/interactions.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedAccounts, importFetchedStatus } from './importer'; export const REBLOG_REQUEST = 'REBLOG_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/lists.js b/app/javascript/flavours/glitch/actions/lists.js index c2309b8c2..5ab922436 100644 --- a/app/javascript/flavours/glitch/actions/lists.js +++ b/app/javascript/flavours/glitch/actions/lists.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedAccounts } from './importer'; import { showAlertForError } from './alerts'; diff --git a/app/javascript/flavours/glitch/actions/local_settings.js b/app/javascript/flavours/glitch/actions/local_settings.js index 856674eb3..a4a928611 100644 --- a/app/javascript/flavours/glitch/actions/local_settings.js +++ b/app/javascript/flavours/glitch/actions/local_settings.js @@ -1,4 +1,4 @@ -import { expandSpoilers, disableSwiping } from 'flavours/glitch/util/initial_state'; +import { expandSpoilers, disableSwiping } from 'flavours/glitch/initial_state'; import { openModal } from './modal'; export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE'; diff --git a/app/javascript/flavours/glitch/actions/markers.js b/app/javascript/flavours/glitch/actions/markers.js index a086def97..3b6a76bc4 100644 --- a/app/javascript/flavours/glitch/actions/markers.js +++ b/app/javascript/flavours/glitch/actions/markers.js @@ -1,6 +1,7 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { debounce } from 'lodash'; -import compareId from 'flavours/glitch/util/compare_id'; +import compareId from '../compare_id'; +import { List as ImmutableList } from 'immutable'; export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST'; export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS'; @@ -11,7 +12,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => { const accessToken = getState().getIn(['meta', 'access_token'], ''); const params = _buildParams(getState()); - if (Object.keys(params).length === 0) { + if (Object.keys(params).length === 0 || accessToken === '') { return; } @@ -63,7 +64,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => { const _buildParams = (state) => { const params = {}; - const lastHomeId = state.getIn(['timelines', 'home', 'items']).find(item => item !== null); + const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null); const lastNotificationId = state.getIn(['notifications', 'lastReadId']); if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) { @@ -82,9 +83,10 @@ const _buildParams = (state) => { }; const debouncedSubmitMarkers = debounce((dispatch, getState) => { - const params = _buildParams(getState()); + const accessToken = getState().getIn(['meta', 'access_token'], ''); + const params = _buildParams(getState()); - if (Object.keys(params).length === 0) { + if (Object.keys(params).length === 0 || accessToken === '') { return; } diff --git a/app/javascript/flavours/glitch/actions/mutes.js b/app/javascript/flavours/glitch/actions/mutes.js index 2bacfadb7..1ccf9592f 100644 --- a/app/javascript/flavours/glitch/actions/mutes.js +++ b/app/javascript/flavours/glitch/actions/mutes.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; import { openModal } from 'flavours/glitch/actions/modal'; diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 4581ebc36..158a5b7e4 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -1,4 +1,4 @@ -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; import IntlMessageFormat from 'intl-messageformat'; import { fetchFollowRequests, fetchRelationships } from './accounts'; import { @@ -11,10 +11,10 @@ import { submitMarkers } from './markers'; import { saveSettings } from './settings'; import { defineMessages } from 'react-intl'; import { List as ImmutableList } from 'immutable'; -import { unescapeHTML } from 'flavours/glitch/util/html'; -import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state'; -import compareId from 'flavours/glitch/util/compare_id'; -import { requestNotificationPermission } from 'flavours/glitch/util/notifications'; +import { unescapeHTML } from 'flavours/glitch/utils/html'; +import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state'; +import compareId from 'flavours/glitch/compare_id'; +import { requestNotificationPermission } from 'flavours/glitch/utils/notifications'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; diff --git a/app/javascript/flavours/glitch/actions/pin_statuses.js b/app/javascript/flavours/glitch/actions/pin_statuses.js index 77dfb9c7f..0926978ac 100644 --- a/app/javascript/flavours/glitch/actions/pin_statuses.js +++ b/app/javascript/flavours/glitch/actions/pin_statuses.js @@ -1,11 +1,11 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedStatuses } from './importer'; export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; export function fetchPinnedStatuses() { return (dispatch, getState) => { diff --git a/app/javascript/flavours/glitch/actions/polls.js b/app/javascript/flavours/glitch/actions/polls.js index ca94a095f..8e8b82df5 100644 --- a/app/javascript/flavours/glitch/actions/polls.js +++ b/app/javascript/flavours/glitch/actions/polls.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedPoll } from './importer'; export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/push_notifications/index.js b/app/javascript/flavours/glitch/actions/push_notifications/index.js index 2ffec500a..9dcc4bd4b 100644 --- a/app/javascript/flavours/glitch/actions/push_notifications/index.js +++ b/app/javascript/flavours/glitch/actions/push_notifications/index.js @@ -1,19 +1,5 @@ -import { - SET_BROWSER_SUPPORT, - SET_SUBSCRIPTION, - CLEAR_SUBSCRIPTION, - SET_ALERTS, - setAlerts, -} from './setter'; -import { register, saveSettings } from './registerer'; - -export { - SET_BROWSER_SUPPORT, - SET_SUBSCRIPTION, - CLEAR_SUBSCRIPTION, - SET_ALERTS, - register, -}; +import { setAlerts } from './setter'; +import { saveSettings } from './registerer'; export function changeAlerts(path, value) { return dispatch => { @@ -21,3 +7,11 @@ export function changeAlerts(path, value) { dispatch(saveSettings()); }; } + +export { + CLEAR_SUBSCRIPTION, + SET_BROWSER_SUPPORT, + SET_SUBSCRIPTION, + SET_ALERTS, +} from './setter'; +export { register } from './registerer'; diff --git a/app/javascript/flavours/glitch/actions/push_notifications/registerer.js b/app/javascript/flavours/glitch/actions/push_notifications/registerer.js index 8fdb239f7..762fe260c 100644 --- a/app/javascript/flavours/glitch/actions/push_notifications/registerer.js +++ b/app/javascript/flavours/glitch/actions/push_notifications/registerer.js @@ -1,5 +1,5 @@ -import api from 'flavours/glitch/util/api'; -import { pushNotificationsSetting } from 'flavours/glitch/util/settings'; +import api from '../../api'; +import { pushNotificationsSetting } from '../../settings'; import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; // Taken from https://www.npmjs.com/package/web-push diff --git a/app/javascript/flavours/glitch/actions/reports.js b/app/javascript/flavours/glitch/actions/reports.js index 333bc71f4..fbe5b3791 100644 --- a/app/javascript/flavours/glitch/actions/reports.js +++ b/app/javascript/flavours/glitch/actions/reports.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { openModal } from './modal'; export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; diff --git a/app/javascript/flavours/glitch/actions/rules.js b/app/javascript/flavours/glitch/actions/rules.js deleted file mode 100644 index b95045e81..000000000 --- a/app/javascript/flavours/glitch/actions/rules.js +++ /dev/null @@ -1,27 +0,0 @@ -import api from 'flavours/glitch/util/api'; - -export const RULES_FETCH_REQUEST = 'RULES_FETCH_REQUEST'; -export const RULES_FETCH_SUCCESS = 'RULES_FETCH_SUCCESS'; -export const RULES_FETCH_FAIL = 'RULES_FETCH_FAIL'; - -export const fetchRules = () => (dispatch, getState) => { - dispatch(fetchRulesRequest()); - - api(getState) - .get('/api/v1/instance').then(({ data }) => dispatch(fetchRulesSuccess(data.rules))) - .catch(err => dispatch(fetchRulesFail(err))); -}; - -const fetchRulesRequest = () => ({ - type: RULES_FETCH_REQUEST, -}); - -const fetchRulesSuccess = rules => ({ - type: RULES_FETCH_SUCCESS, - rules, -}); - -const fetchRulesFail = error => ({ - type: RULES_FETCH_FAIL, - error, -}); diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js index b4aee4525..f21c0058b 100644 --- a/app/javascript/flavours/glitch/actions/search.js +++ b/app/javascript/flavours/glitch/actions/search.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatuses } from './importer'; @@ -29,7 +29,8 @@ export function clearSearch() { export function submitSearch() { return (dispatch, getState) => { - const value = getState().getIn(['search', 'value']); + const value = getState().getIn(['search', 'value']); + const signedIn = !!getState().getIn(['meta', 'me']); if (value.length === 0) { dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, '')); @@ -41,7 +42,7 @@ export function submitSearch() { api(getState).get('/api/v2/search', { params: { q: value, - resolve: true, + resolve: signedIn, limit: 10, }, }).then(response => { diff --git a/app/javascript/flavours/glitch/actions/server.js b/app/javascript/flavours/glitch/actions/server.js new file mode 100644 index 000000000..31d4aea10 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/server.js @@ -0,0 +1,91 @@ +import api from '../api'; +import { importFetchedAccount } from './importer'; + +export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST'; +export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS'; +export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL'; + +export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST'; +export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS'; +export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL'; + +export const SERVER_DOMAIN_BLOCKS_FETCH_REQUEST = 'SERVER_DOMAIN_BLOCKS_FETCH_REQUEST'; +export const SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS = 'SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS'; +export const SERVER_DOMAIN_BLOCKS_FETCH_FAIL = 'SERVER_DOMAIN_BLOCKS_FETCH_FAIL'; + +export const fetchServer = () => (dispatch, getState) => { + dispatch(fetchServerRequest()); + + api(getState) + .get('/api/v2/instance').then(({ data }) => { + if (data.contact.account) dispatch(importFetchedAccount(data.contact.account)); + dispatch(fetchServerSuccess(data)); + }).catch(err => dispatch(fetchServerFail(err))); +}; + +const fetchServerRequest = () => ({ + type: SERVER_FETCH_REQUEST, +}); + +const fetchServerSuccess = server => ({ + type: SERVER_FETCH_SUCCESS, + server, +}); + +const fetchServerFail = error => ({ + type: SERVER_FETCH_FAIL, + error, +}); + +export const fetchExtendedDescription = () => (dispatch, getState) => { + dispatch(fetchExtendedDescriptionRequest()); + + api(getState) + .get('/api/v1/instance/extended_description') + .then(({ data }) => dispatch(fetchExtendedDescriptionSuccess(data))) + .catch(err => dispatch(fetchExtendedDescriptionFail(err))); +}; + +const fetchExtendedDescriptionRequest = () => ({ + type: EXTENDED_DESCRIPTION_REQUEST, +}); + +const fetchExtendedDescriptionSuccess = description => ({ + type: EXTENDED_DESCRIPTION_SUCCESS, + description, +}); + +const fetchExtendedDescriptionFail = error => ({ + type: EXTENDED_DESCRIPTION_FAIL, + error, +}); + +export const fetchDomainBlocks = () => (dispatch, getState) => { + dispatch(fetchDomainBlocksRequest()); + + api(getState) + .get('/api/v1/instance/domain_blocks') + .then(({ data }) => dispatch(fetchDomainBlocksSuccess(true, data))) + .catch(err => { + if (err.response.status === 404) { + dispatch(fetchDomainBlocksSuccess(false, [])); + } else { + dispatch(fetchDomainBlocksFail(err)); + } + }); +}; + +const fetchDomainBlocksRequest = () => ({ + type: SERVER_DOMAIN_BLOCKS_FETCH_REQUEST, +}); + +const fetchDomainBlocksSuccess = (isAvailable, blocks) => ({ + type: SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS, + isAvailable, + blocks, +}); + +const fetchDomainBlocksFail = error => ({ + type: SERVER_DOMAIN_BLOCKS_FETCH_FAIL, + error, +}); diff --git a/app/javascript/flavours/glitch/actions/settings.js b/app/javascript/flavours/glitch/actions/settings.js index fb0bcc09c..5634a11ef 100644 --- a/app/javascript/flavours/glitch/actions/settings.js +++ b/app/javascript/flavours/glitch/actions/settings.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { debounce } from 'lodash'; import { showAlertForError } from './alerts'; diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js index 58c1d44a6..5930b7a16 100644 --- a/app/javascript/flavours/glitch/actions/statuses.js +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { deleteFromTimelines } from './timelines'; import { importFetchedStatus, importFetchedStatuses } from './importer'; diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index 375728cb5..ffac1b258 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -1,6 +1,6 @@ // @ts-check -import { connectStream } from 'flavours/glitch/util/stream'; +import { connectStream } from '../stream'; import { updateTimeline, deleteFromTimelines, diff --git a/app/javascript/flavours/glitch/actions/suggestions.js b/app/javascript/flavours/glitch/actions/suggestions.js index 7070250e3..1f1116e75 100644 --- a/app/javascript/flavours/glitch/actions/suggestions.js +++ b/app/javascript/flavours/glitch/actions/suggestions.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; import { importFetchedAccounts } from './importer'; import { fetchRelationships } from './accounts'; diff --git a/app/javascript/flavours/glitch/actions/tags.js b/app/javascript/flavours/glitch/actions/tags.js index 4016cf96f..37e79d4cb 100644 --- a/app/javascript/flavours/glitch/actions/tags.js +++ b/app/javascript/flavours/glitch/actions/tags.js @@ -1,4 +1,4 @@ -import api from 'flavours/glitch/util/api'; +import api from '../api'; export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST'; export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS'; diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 0d6f844b3..a1c4dd43a 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -1,10 +1,10 @@ import { importFetchedStatus, importFetchedStatuses } from './importer'; import { submitMarkers } from './markers'; -import api, { getLinks } from 'flavours/glitch/util/api'; +import api, { getLinks } from 'flavours/glitch/api'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import compareId from 'flavours/glitch/util/compare_id'; -import { me, usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state'; -import { toServerSideType } from 'flavours/glitch/util/filters'; +import compareId from 'flavours/glitch/compare_id'; +import { me, usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state'; +import { toServerSideType } from 'flavours/glitch/utils/filters'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; @@ -156,8 +156,8 @@ export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => ex export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote, allowLocalOnly } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, allow_local_only: !!allowLocalOnly, max_id: maxId, only_media: !!onlyMedia }, done); export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done); -export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); -export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); +export const expandAccountTimeline = (accountId, { maxId, withReplies, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, tagged, max_id: maxId }); +export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => { diff --git a/app/javascript/flavours/glitch/actions/trends.js b/app/javascript/flavours/glitch/actions/trends.js index 1b0ce2b5b..edda0b5b5 100644 --- a/app/javascript/flavours/glitch/actions/trends.js +++ b/app/javascript/flavours/glitch/actions/trends.js @@ -1,32 +1,139 @@ -import api from 'flavours/glitch/util/api'; +import api, { getLinks } from '../api'; +import { importFetchedStatuses } from './importer'; -export const TRENDS_FETCH_REQUEST = 'TRENDS_FETCH_REQUEST'; -export const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS'; -export const TRENDS_FETCH_FAIL = 'TRENDS_FETCH_FAIL'; +export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST'; +export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS'; +export const TRENDS_TAGS_FETCH_FAIL = 'TRENDS_TAGS_FETCH_FAIL'; -export const fetchTrends = () => (dispatch, getState) => { - dispatch(fetchTrendsRequest()); +export const TRENDS_LINKS_FETCH_REQUEST = 'TRENDS_LINKS_FETCH_REQUEST'; +export const TRENDS_LINKS_FETCH_SUCCESS = 'TRENDS_LINKS_FETCH_SUCCESS'; +export const TRENDS_LINKS_FETCH_FAIL = 'TRENDS_LINKS_FETCH_FAIL'; + +export const TRENDS_STATUSES_FETCH_REQUEST = 'TRENDS_STATUSES_FETCH_REQUEST'; +export const TRENDS_STATUSES_FETCH_SUCCESS = 'TRENDS_STATUSES_FETCH_SUCCESS'; +export const TRENDS_STATUSES_FETCH_FAIL = 'TRENDS_STATUSES_FETCH_FAIL'; + +export const TRENDS_STATUSES_EXPAND_REQUEST = 'TRENDS_STATUSES_EXPAND_REQUEST'; +export const TRENDS_STATUSES_EXPAND_SUCCESS = 'TRENDS_STATUSES_EXPAND_SUCCESS'; +export const TRENDS_STATUSES_EXPAND_FAIL = 'TRENDS_STATUSES_EXPAND_FAIL'; + +export const fetchTrendingHashtags = () => (dispatch, getState) => { + dispatch(fetchTrendingHashtagsRequest()); + + api(getState) + .get('/api/v1/trends/tags') + .then(({ data }) => dispatch(fetchTrendingHashtagsSuccess(data))) + .catch(err => dispatch(fetchTrendingHashtagsFail(err))); +}; + +export const fetchTrendingHashtagsRequest = () => ({ + type: TRENDS_TAGS_FETCH_REQUEST, + skipLoading: true, +}); + +export const fetchTrendingHashtagsSuccess = trends => ({ + type: TRENDS_TAGS_FETCH_SUCCESS, + trends, + skipLoading: true, +}); + +export const fetchTrendingHashtagsFail = error => ({ + type: TRENDS_TAGS_FETCH_FAIL, + error, + skipLoading: true, + skipAlert: true, +}); + +export const fetchTrendingLinks = () => (dispatch, getState) => { + dispatch(fetchTrendingLinksRequest()); api(getState) - .get('/api/v1/trends') - .then(({ data }) => dispatch(fetchTrendsSuccess(data))) - .catch(err => dispatch(fetchTrendsFail(err))); + .get('/api/v1/trends/links') + .then(({ data }) => dispatch(fetchTrendingLinksSuccess(data))) + .catch(err => dispatch(fetchTrendingLinksFail(err))); }; -export const fetchTrendsRequest = () => ({ - type: TRENDS_FETCH_REQUEST, +export const fetchTrendingLinksRequest = () => ({ + type: TRENDS_LINKS_FETCH_REQUEST, skipLoading: true, }); -export const fetchTrendsSuccess = trends => ({ - type: TRENDS_FETCH_SUCCESS, +export const fetchTrendingLinksSuccess = trends => ({ + type: TRENDS_LINKS_FETCH_SUCCESS, trends, skipLoading: true, }); -export const fetchTrendsFail = error => ({ - type: TRENDS_FETCH_FAIL, +export const fetchTrendingLinksFail = error => ({ + type: TRENDS_LINKS_FETCH_FAIL, error, skipLoading: true, skipAlert: true, }); + +export const fetchTrendingStatuses = () => (dispatch, getState) => { + if (getState().getIn(['status_lists', 'trending', 'isLoading'])) { + return; + } + + dispatch(fetchTrendingStatusesRequest()); + + api(getState).get('/api/v1/trends/statuses').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); + dispatch(fetchTrendingStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(err => dispatch(fetchTrendingStatusesFail(err))); +}; + +export const fetchTrendingStatusesRequest = () => ({ + type: TRENDS_STATUSES_FETCH_REQUEST, + skipLoading: true, +}); + +export const fetchTrendingStatusesSuccess = (statuses, next) => ({ + type: TRENDS_STATUSES_FETCH_SUCCESS, + statuses, + next, + skipLoading: true, +}); + +export const fetchTrendingStatusesFail = error => ({ + type: TRENDS_STATUSES_FETCH_FAIL, + error, + skipLoading: true, + skipAlert: true, +}); + + +export const expandTrendingStatuses = () => (dispatch, getState) => { + const url = getState().getIn(['status_lists', 'trending', 'next'], null); + + if (url === null || getState().getIn(['status_lists', 'trending', 'isLoading'])) { + return; + } + + dispatch(expandTrendingStatusesRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(importFetchedStatuses(response.data)); + dispatch(expandTrendingStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandTrendingStatusesFail(error)); + }); +}; + +export const expandTrendingStatusesRequest = () => ({ + type: TRENDS_STATUSES_EXPAND_REQUEST, +}); + +export const expandTrendingStatusesSuccess = (statuses, next) => ({ + type: TRENDS_STATUSES_EXPAND_SUCCESS, + statuses, + next, +}); + +export const expandTrendingStatusesFail = error => ({ + type: TRENDS_STATUSES_EXPAND_FAIL, + error, +}); diff --git a/app/javascript/flavours/glitch/api.js b/app/javascript/flavours/glitch/api.js new file mode 100644 index 000000000..6bbddbef6 --- /dev/null +++ b/app/javascript/flavours/glitch/api.js @@ -0,0 +1,75 @@ +// @ts-check + +import axios from 'axios'; +import LinkHeader from 'http-link-header'; +import ready from './ready'; + +/** + * @param {import('axios').AxiosResponse} response + * @returns {LinkHeader} + */ +export const getLinks = response => { + const value = response.headers.link; + + if (!value) { + return new LinkHeader(); + } + + return LinkHeader.parse(value); +}; + +/** @type {import('axios').RawAxiosRequestHeaders} */ +const csrfHeader = {}; + +/** + * @returns {void} + */ +const setCSRFHeader = () => { + /** @type {HTMLMetaElement | null} */ + const csrfToken = document.querySelector('meta[name=csrf-token]'); + + if (csrfToken) { + csrfHeader['X-CSRF-Token'] = csrfToken.content; + } +}; + +ready(setCSRFHeader); + +/** + * @param {() => import('immutable').Map} getState + * @returns {import('axios').RawAxiosRequestHeaders} + */ +const authorizationHeaderFromState = getState => { + const accessToken = getState && getState().getIn(['meta', 'access_token'], ''); + + if (!accessToken) { + return {}; + } + + return { + 'Authorization': `Bearer ${accessToken}`, + }; +}; + +/** + * @param {() => import('immutable').Map} getState + * @returns {import('axios').AxiosInstance} + */ +export default function api(getState) { + return axios.create({ + headers: { + ...csrfHeader, + ...authorizationHeaderFromState(getState), + }, + + transformResponse: [ + function (data) { + try { + return JSON.parse(data); + } catch { + return data; + } + }, + ], + }); +} diff --git a/app/javascript/flavours/glitch/util/base_polyfills.js b/app/javascript/flavours/glitch/base_polyfills.js index 4b8123dba..12096d902 100644 --- a/app/javascript/flavours/glitch/util/base_polyfills.js +++ b/app/javascript/flavours/glitch/base_polyfills.js @@ -5,7 +5,7 @@ import includes from 'array-includes'; import assign from 'object-assign'; import values from 'object.values'; import isNaN from 'is-nan'; -import { decode as decodeBase64 } from './base64'; +import { decode as decodeBase64 } from './utils/base64'; import promiseFinally from 'promise.prototype.finally'; if (!Array.prototype.includes) { diff --git a/app/javascript/flavours/glitch/util/compare_id.js b/app/javascript/flavours/glitch/compare_id.js index 66cf51c4b..66cf51c4b 100644 --- a/app/javascript/flavours/glitch/util/compare_id.js +++ b/app/javascript/flavours/glitch/compare_id.js diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js index 489f60736..8e810ce5f 100644 --- a/app/javascript/flavours/glitch/components/account.js +++ b/app/javascript/flavours/glitch/components/account.js @@ -7,8 +7,9 @@ import Permalink from './permalink'; import IconButton from './icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; import RelativeTimestamp from './relative_timestamp'; +import Skeleton from 'flavours/glitch/components/skeleton'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -26,7 +27,8 @@ export default @injectIntl class Account extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map.isRequired, + size: PropTypes.number, + account: ImmutablePropTypes.map, onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -40,6 +42,10 @@ class Account extends ImmutablePureComponent { onActionClick: PropTypes.func, }; + static defaultProps = { + size: 36, + }; + handleFollow = () => { this.props.onFollow(this.props.account); } @@ -74,10 +80,20 @@ class Account extends ImmutablePureComponent { actionIcon, actionTitle, defaultAction, + size, } = this.props; if (!account) { - return <div />; + return ( + <div className='account'> + <div className='account__wrapper'> + <div className='account__display-name'> + <div className='account__avatar-wrapper'><Skeleton width={36} height={36} /></div> + <DisplayName /> + </div> + </div> + </div> + ); } if (hidden) { @@ -153,7 +169,7 @@ class Account extends ImmutablePureComponent { <div className='account'> <div className='account__wrapper'> <Permalink key={account.get('id')} className='account__display-name' title={account.get('acct')} href={account.get('url')} to={`/@${account.get('acct')}`}> - <div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> + <div className='account__avatar-wrapper'><Avatar account={account} size={size} /></div> {mute_expires_at} <DisplayName account={account} /> </Permalink> diff --git a/app/javascript/flavours/glitch/components/admin/Counter.js b/app/javascript/flavours/glitch/components/admin/Counter.js index a4d6cef41..5b6a19f8d 100644 --- a/app/javascript/flavours/glitch/components/admin/Counter.js +++ b/app/javascript/flavours/glitch/components/admin/Counter.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; import { FormattedNumber } from 'react-intl'; import { Sparklines, SparklinesCurve } from 'react-sparklines'; import classNames from 'classnames'; diff --git a/app/javascript/flavours/glitch/components/admin/Dimension.js b/app/javascript/flavours/glitch/components/admin/Dimension.js index a924d093c..3dac8c6c2 100644 --- a/app/javascript/flavours/glitch/components/admin/Dimension.js +++ b/app/javascript/flavours/glitch/components/admin/Dimension.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; import { FormattedNumber } from 'react-intl'; -import { roundTo10 } from 'flavours/glitch/util/numbers'; +import { roundTo10 } from 'flavours/glitch/utils/numbers'; import Skeleton from 'flavours/glitch/components/skeleton'; export default class Dimension extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js index 0f2a4fe36..771dbb452 100644 --- a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js +++ b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; import { injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; diff --git a/app/javascript/flavours/glitch/components/admin/Retention.js b/app/javascript/flavours/glitch/components/admin/Retention.js index 6d7e4b279..9cc39040b 100644 --- a/app/javascript/flavours/glitch/components/admin/Retention.js +++ b/app/javascript/flavours/glitch/components/admin/Retention.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl'; import classNames from 'classnames'; -import { roundTo10 } from 'flavours/glitch/util/numbers'; +import { roundTo10 } from 'flavours/glitch/utils/numbers'; const dateForCohort = cohort => { switch(cohort.frequency) { diff --git a/app/javascript/flavours/glitch/components/admin/Trends.js b/app/javascript/flavours/glitch/components/admin/Trends.js index 60e367f00..4c17b69a0 100644 --- a/app/javascript/flavours/glitch/components/admin/Trends.js +++ b/app/javascript/flavours/glitch/components/admin/Trends.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Hashtag from 'flavours/glitch/components/hashtag'; diff --git a/app/javascript/flavours/glitch/components/animated_number.js b/app/javascript/flavours/glitch/components/animated_number.js index 3cc5173dd..9431c96f7 100644 --- a/app/javascript/flavours/glitch/components/animated_number.js +++ b/app/javascript/flavours/glitch/components/animated_number.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { FormattedNumber } from 'react-intl'; +import ShortNumber from 'mastodon/components/short_number'; import TransitionMotion from 'react-motion/lib/TransitionMotion'; import spring from 'react-motion/lib/spring'; -import { reduceMotion } from 'flavours/glitch/util/initial_state'; +import { reduceMotion } from 'flavours/glitch/initial_state'; const obfuscatedCount = count => { if (count < 0) { @@ -51,7 +51,7 @@ export default class AnimatedNumber extends React.PureComponent { const { direction } = this.state; if (reduceMotion) { - return obfuscate ? obfuscatedCount(value) : <FormattedNumber value={value} />; + return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />; } const styles = [{ @@ -65,7 +65,7 @@ export default class AnimatedNumber extends React.PureComponent { {items => ( <span className='animated-number'> {items.map(({ key, data, style }) => ( - <span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <FormattedNumber value={data} />}</span> + <span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span> ))} </span> )} diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.js b/app/javascript/flavours/glitch/components/autosuggest_emoji.js index d04c1eb68..83fafbd10 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_emoji.js +++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light'; +import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; -import { assetHost } from 'flavours/glitch/util/config'; +import { assetHost } from 'flavours/glitch/utils/config'; export default class AutosuggestEmoji extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/avatar.js b/app/javascript/flavours/glitch/components/avatar.js index 6d53a5298..38fd99af5 100644 --- a/app/javascript/flavours/glitch/components/avatar.js +++ b/app/javascript/flavours/glitch/components/avatar.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; import classNames from 'classnames'; export default class Avatar extends React.PureComponent { @@ -70,6 +70,8 @@ export default class Avatar extends React.PureComponent { onMouseLeave={this.handleMouseLeave} style={style} data-avatar-of={account && `@${account.get('acct')}`} + role='img' + aria-label={account?.get('acct')} /> ); } diff --git a/app/javascript/flavours/glitch/components/avatar_composite.js b/app/javascript/flavours/glitch/components/avatar_composite.js index e30dfe68a..c0ce7761d 100644 --- a/app/javascript/flavours/glitch/components/avatar_composite.js +++ b/app/javascript/flavours/glitch/components/avatar_composite.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; export default class AvatarComposite extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/avatar_overlay.js b/app/javascript/flavours/glitch/components/avatar_overlay.js index 23db5182b..01dec587a 100644 --- a/app/javascript/flavours/glitch/components/avatar_overlay.js +++ b/app/javascript/flavours/glitch/components/avatar_overlay.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; export default class AvatarOverlay extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/column.js b/app/javascript/flavours/glitch/components/column.js index c9da7d329..cf0e6d5e4 100644 --- a/app/javascript/flavours/glitch/components/column.js +++ b/app/javascript/flavours/glitch/components/column.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { supportsPassiveEvents } from 'detect-passive-events'; -import { scrollTop } from 'flavours/glitch/util/scroll'; +import { scrollTop } from '../scroll'; export default class Column extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/column_header.js b/app/javascript/flavours/glitch/components/column_header.js index 500612093..0f89b3a97 100644 --- a/app/javascript/flavours/glitch/components/column_header.js +++ b/app/javascript/flavours/glitch/components/column_header.js @@ -17,6 +17,7 @@ class ColumnHeader extends React.PureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { @@ -62,7 +63,7 @@ class ColumnHeader extends React.PureComponent { } handleTitleClick = () => { - this.props.onClick(); + this.props.onClick?.(); } handleMoveLeft = () => { @@ -150,13 +151,12 @@ class ColumnHeader extends React.PureComponent { collapsedContent.push(moveButtons); } - if (children || (multiColumn && this.props.onPin)) { + if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) { collapseButton = ( <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} - aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick} > <i className='icon-with-badge'> diff --git a/app/javascript/flavours/glitch/components/dismissable_banner.js b/app/javascript/flavours/glitch/components/dismissable_banner.js new file mode 100644 index 000000000..ff52a619d --- /dev/null +++ b/app/javascript/flavours/glitch/components/dismissable_banner.js @@ -0,0 +1,51 @@ +import React from 'react'; +import IconButton from './icon_button'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages } from 'react-intl'; +import { bannerSettings } from 'flavours/glitch/settings'; + +const messages = defineMessages({ + dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' }, +}); + +export default @injectIntl +class DismissableBanner extends React.PureComponent { + + static propTypes = { + id: PropTypes.string.isRequired, + children: PropTypes.node, + intl: PropTypes.object.isRequired, + }; + + state = { + visible: !bannerSettings.get(this.props.id), + }; + + handleDismiss = () => { + const { id } = this.props; + this.setState({ visible: false }, () => bannerSettings.set(id, true)); + } + + render () { + const { visible } = this.state; + + if (!visible) { + return null; + } + + const { children, intl } = this.props; + + return ( + <div className='dismissable-banner'> + <div className='dismissable-banner__message'> + {children} + </div> + + <div className='dismissable-banner__action'> + <IconButton icon='times' title={intl.formatMessage(messages.dismiss)} onClick={this.handleDismiss} /> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/display_name.js b/app/javascript/flavours/glitch/components/display_name.js index 9c7da744e..1c2297578 100644 --- a/app/javascript/flavours/glitch/components/display_name.js +++ b/app/javascript/flavours/glitch/components/display_name.js @@ -2,7 +2,8 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; +import Skeleton from 'flavours/glitch/components/skeleton'; export default class DisplayName extends React.PureComponent { @@ -46,14 +47,15 @@ export default class DisplayName extends React.PureComponent { const computedClass = classNames('display-name', { inline }, className); - if (!account) return null; - let displayName, suffix; + let acct; - let acct = account.get('acct'); + if (account) { + acct = account.get('acct'); - if (acct.indexOf('@') === -1 && localDomain) { - acct = `${acct}@${localDomain}`; + if (acct.indexOf('@') === -1 && localDomain) { + acct = `${acct}@${localDomain}`; + } } if (others && others.size > 0) { @@ -80,9 +82,12 @@ export default class DisplayName extends React.PureComponent { <span className='display-name__account'>@{acct}</span> </a> ); - } else { + } else if (account) { displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>; suffix = <span className='display-name__account'>@{acct}</span>; + } else { + displayName = <bdi><strong className='display-name__html'><Skeleton width='10ch' /></strong></bdi>; + suffix = <span className='display-name__account'><Skeleton width='7ch' /></span>; } return ( diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js index e04af8074..036e0b909 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/components/dropdown_menu.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import IconButton from './icon_button'; import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { supportsPassiveEvents } from 'detect-passive-events'; import classNames from 'classnames'; diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js index 4537bde1d..e0ca3e2b0 100644 --- a/app/javascript/flavours/glitch/components/error_boundary.js +++ b/app/javascript/flavours/glitch/components/error_boundary.js @@ -1,9 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { source_url } from 'flavours/glitch/util/initial_state'; -import { preferencesLink } from 'flavours/glitch/util/backend_links'; +import { source_url } from 'flavours/glitch/initial_state'; +import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import StackTrace from 'stacktrace-js'; +import { Helmet } from 'react-helmet'; export default class ErrorBoundary extends React.PureComponent { @@ -122,6 +123,10 @@ export default class ErrorBoundary extends React.PureComponent { )} </ul> </div> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </div> ); } diff --git a/app/javascript/flavours/glitch/components/hashtag.js b/app/javascript/flavours/glitch/components/hashtag.js index 5bbf32c87..422b9a8fa 100644 --- a/app/javascript/flavours/glitch/components/hashtag.js +++ b/app/javascript/flavours/glitch/components/hashtag.js @@ -1,7 +1,7 @@ // @ts-check import React from 'react'; import { Sparklines, SparklinesCurve } from 'react-sparklines'; -import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Permalink from './permalink'; @@ -9,10 +9,6 @@ import ShortNumber from 'flavours/glitch/components/short_number'; import Skeleton from 'flavours/glitch/components/skeleton'; import classNames from 'classnames'; -const messages = defineMessages({ - totalVolume: { id: 'hashtag.total_volume', defaultMessage: 'Total volume in the last {days, plural, one {day} other {{days} days}}' }, -}); - class SilentErrorBoundary extends React.Component { static propTypes = { @@ -42,7 +38,7 @@ class SilentErrorBoundary extends React.Component { * * @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element} */ -const accountsCountRenderer = (displayNumber, pluralReady) => ( +export const accountsCountRenderer = (displayNumber, pluralReady) => ( <FormattedMessage id='trends.counter_by_accounts' defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}' @@ -60,7 +56,6 @@ export const ImmutableHashtag = ({ hashtag }) => ( href={hashtag.get('url')} to={`/tags/${hashtag.get('name')}`} people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1} - uses={hashtag.getIn(['history', 0, 'uses']) * 1 + hashtag.getIn(['history', 1, 'uses']) * 1} history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()} /> ); @@ -69,39 +64,52 @@ ImmutableHashtag.propTypes = { hashtag: ImmutablePropTypes.map.isRequired, }; -const Hashtag = injectIntl(({ name, href, to, people, uses, history, className, intl }) => ( +const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => ( <div className={classNames('trends__item', className)}> <div className='trends__item__name'> <Permalink href={href} to={to}> {name ? <React.Fragment>#<span>{name}</span></React.Fragment> : <Skeleton width={50} />} </Permalink> - {typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />} + {description ? ( + <span>{description}</span> + ) : ( + typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} /> + )} </div> - <abbr className='trends__item__current' title={intl.formatMessage(messages.totalVolume, { days: 2 })}> - {typeof uses !== 'undefined' ? <ShortNumber value={uses} /> : <Skeleton width={42} height={36} />} - <span className='trends__item__current__asterisk'>*</span> - </abbr> - - <div className='trends__item__sparkline'> - <SilentErrorBoundary> - <Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}> - <SparklinesCurve style={{ fill: 'none' }} /> - </Sparklines> - </SilentErrorBoundary> - </div> + {typeof uses !== 'undefined' && ( + <div className='trends__item__current'> + <ShortNumber value={uses} /> + </div> + )} + + {withGraph && ( + <div className='trends__item__sparkline'> + <SilentErrorBoundary> + <Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}> + <SparklinesCurve style={{ fill: 'none' }} /> + </Sparklines> + </SilentErrorBoundary> + </div> + )} </div> -)); +); Hashtag.propTypes = { name: PropTypes.string, href: PropTypes.string, to: PropTypes.string, people: PropTypes.number, + description: PropTypes.node, uses: PropTypes.number, history: PropTypes.arrayOf(PropTypes.number), className: PropTypes.string, + withGraph: PropTypes.bool, +}; + +Hashtag.defaultProps = { + withGraph: true, }; export default Hashtag; diff --git a/app/javascript/flavours/glitch/components/icon_button.js b/app/javascript/flavours/glitch/components/icon_button.js index 9ff745355..41a95e92f 100644 --- a/app/javascript/flavours/glitch/components/icon_button.js +++ b/app/javascript/flavours/glitch/components/icon_button.js @@ -1,5 +1,5 @@ import React from 'react'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import PropTypes from 'prop-types'; import classNames from 'classnames'; @@ -18,7 +18,6 @@ export default class IconButton extends React.PureComponent { onKeyPress: PropTypes.func, size: PropTypes.number, active: PropTypes.bool, - pressed: PropTypes.bool, expanded: PropTypes.bool, style: PropTypes.object, activeStyle: PropTypes.object, @@ -84,15 +83,21 @@ export default class IconButton extends React.PureComponent { } render () { + // Hack required for some icons which have an overriden size + let containerSize = '1.28571429em'; + if (this.props.style?.fontSize) { + containerSize = `${this.props.size * 1.28571429}px`; + } + let style = { fontSize: `${this.props.size}px`, - height: '1.28571429em', + height: containerSize, lineHeight: `${this.props.size}px`, ...this.props.style, ...(this.props.active ? this.props.activeStyle : {}), }; if (!this.props.label) { - style.width = '1.28571429em'; + style.width = containerSize; } else { style.textAlign = 'left'; } @@ -105,7 +110,6 @@ export default class IconButton extends React.PureComponent { icon, inverted, overlay, - pressed, tabIndex, title, counter, @@ -150,7 +154,6 @@ export default class IconButton extends React.PureComponent { return ( <button aria-label={title} - aria-pressed={pressed} aria-expanded={expanded} title={title} className={classes} diff --git a/app/javascript/flavours/glitch/components/image.js b/app/javascript/flavours/glitch/components/image.js new file mode 100644 index 000000000..6e81ddf08 --- /dev/null +++ b/app/javascript/flavours/glitch/components/image.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Blurhash from './blurhash'; +import classNames from 'classnames'; + +export default class Image extends React.PureComponent { + + static propTypes = { + src: PropTypes.string, + srcSet: PropTypes.string, + blurhash: PropTypes.string, + className: PropTypes.string, + }; + + state = { + loaded: false, + }; + + handleLoad = () => this.setState({ loaded: true }); + + render () { + const { src, srcSet, blurhash, className } = this.props; + const { loaded } = this.state; + + return ( + <div className={classNames('image', { loaded }, className)} role='presentation'> + {blurhash && <Blurhash hash={blurhash} className='image__preview' />} + <img src={src} srcSet={srcSet} alt='' onLoad={this.handleLoad} /> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js index 88f29892e..b28e44e4c 100644 --- a/app/javascript/flavours/glitch/components/intersection_observer_article.js +++ b/app/javascript/flavours/glitch/components/intersection_observer_article.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task'; -import getRectFromEntry from 'flavours/glitch/util/get_rect_from_entry'; +import scheduleIdleTask from '../features/ui/util/schedule_idle_task'; +import getRectFromEntry from '../features/ui/util/get_rect_from_entry'; // Diff these props in the "unrendered" state const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight']; @@ -94,7 +94,7 @@ export default class IntersectionObserverArticle extends React.Component { // When the browser gets a chance, test if we're still not intersecting, // and if so, set our isHidden to true to trigger an unrender. The point of // this is to save DOM nodes and avoid using up too much memory. - // See: https://github.com/tootsuite/mastodon/issues/2900 + // See: https://github.com/mastodon/mastodon/issues/2900 this.setState((prevState) => ({ isHidden: !prevState.isIntersecting })); } diff --git a/app/javascript/flavours/glitch/components/link.js b/app/javascript/flavours/glitch/components/link.js index de99f7d42..bbec121a8 100644 --- a/app/javascript/flavours/glitch/components/link.js +++ b/app/javascript/flavours/glitch/components/link.js @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import React from 'react'; // Utils. -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; +import { assignHandlers } from 'flavours/glitch/utils/react_helpers'; // Handlers. const handlers = { diff --git a/app/javascript/flavours/glitch/components/logo.js b/app/javascript/flavours/glitch/components/logo.js index d1c7f08a9..ee5c22496 100644 --- a/app/javascript/flavours/glitch/components/logo.js +++ b/app/javascript/flavours/glitch/components/logo.js @@ -1,8 +1,9 @@ import React from 'react'; const Logo = () => ( - <svg viewBox='0 0 216.4144 232.00976' className='logo'> - <use xlinkHref='#mastodon-svg-logo' /> + <svg viewBox='0 0 261 66' className='logo' role='img'> + <title>Mastodon</title> + <use xlinkHref='#logo-symbol-wordmark' /> </svg> ); diff --git a/app/javascript/flavours/glitch/components/media_attachments.js b/app/javascript/flavours/glitch/components/media_attachments.js index c8d133f09..a517fcf30 100644 --- a/app/javascript/flavours/glitch/components/media_attachments.js +++ b/app/javascript/flavours/glitch/components/media_attachments.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { MediaGallery, Video, Audio } from 'flavours/glitch/util/async-components'; +import { MediaGallery, Video, Audio } from 'flavours/glitch/features/ui/util/async-components'; import Bundle from 'flavours/glitch/features/ui/components/bundle'; import noop from 'lodash/noop'; diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index 68195ea80..5414b4858 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -4,9 +4,9 @@ import PropTypes from 'prop-types'; import { is } from 'immutable'; import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { isIOS } from 'flavours/glitch/util/is_mobile'; +import { isIOS } from '../is_mobile'; import classNames from 'classnames'; -import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import { debounce } from 'lodash'; import Blurhash from 'flavours/glitch/components/blurhash'; diff --git a/app/javascript/flavours/glitch/components/missing_indicator.js b/app/javascript/flavours/glitch/components/missing_indicator.js index ee5bf7c1e..08e39c236 100644 --- a/app/javascript/flavours/glitch/components/missing_indicator.js +++ b/app/javascript/flavours/glitch/components/missing_indicator.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import illustration from 'flavours/glitch/images/elephant_ui_disappointed.svg'; import classNames from 'classnames'; +import { Helmet } from 'react-helmet'; const MissingIndicator = ({ fullPage }) => ( <div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}> @@ -14,6 +15,10 @@ const MissingIndicator = ({ fullPage }) => ( <FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' /> <FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' /> </div> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </div> ); diff --git a/app/javascript/flavours/glitch/components/navigation_portal.js b/app/javascript/flavours/glitch/components/navigation_portal.js new file mode 100644 index 000000000..90afa1da0 --- /dev/null +++ b/app/javascript/flavours/glitch/components/navigation_portal.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { Switch, Route, withRouter } from 'react-router-dom'; +import { showTrends } from 'flavours/glitch/initial_state'; +import Trends from 'flavours/glitch/features/getting_started/containers/trends_container'; +import AccountNavigation from 'flavours/glitch/features/account/navigation'; + +const DefaultNavigation = () => ( + <> + {showTrends && ( + <> + <div className='flex-spacer' /> + <Trends /> + </> + )} + </> +); + +export default @withRouter +class NavigationPortal extends React.PureComponent { + + render () { + return ( + <Switch> + <Route path='/@:acct' exact component={AccountNavigation} /> + <Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} /> + <Route path='/@:acct/with_replies' exact component={AccountNavigation} /> + <Route path='/@:acct/followers' exact component={AccountNavigation} /> + <Route path='/@:acct/following' exact component={AccountNavigation} /> + <Route path='/@:acct/media' exact component={AccountNavigation} /> + <Route component={DefaultNavigation} /> + </Switch> + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/not_signed_in_indicator.js b/app/javascript/flavours/glitch/components/not_signed_in_indicator.js new file mode 100644 index 000000000..b440c6be2 --- /dev/null +++ b/app/javascript/flavours/glitch/components/not_signed_in_indicator.js @@ -0,0 +1,12 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +const NotSignedInIndicator = () => ( + <div className='scrollable scrollable--flex'> + <div className='empty-column-indicator'> + <FormattedMessage id='not_signed_in_indicator.not_signed_in' defaultMessage='You need to sign in to access this resource.' /> + </div> + </div> +); + +export default NotSignedInIndicator; diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js index 970b00705..da65cd241 100644 --- a/app/javascript/flavours/glitch/components/poll.js +++ b/app/javascript/flavours/glitch/components/poll.js @@ -4,10 +4,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from 'flavours/glitch/features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import escapeTextContentForBrowser from 'escape-html'; -import emojify from 'flavours/glitch/util/emoji'; +import emojify from 'flavours/glitch/features/emoji/emoji'; import RelativeTimestamp from './relative_timestamp'; import Icon from 'flavours/glitch/components/icon'; @@ -34,6 +34,10 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => { export default @injectIntl class Poll extends ImmutablePureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { poll: ImmutablePropTypes.map, intl: PropTypes.object.isRequired, @@ -217,7 +221,7 @@ class Poll extends ImmutablePureComponent { </ul> <div className='poll__footer'> - {!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>} + {!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>} {showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> ยท </span>} {votesCount} {poll.get('expires_at') && <span> ยท {timeRemaining}</span>} diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 50bfacc6a..8eb2b66d4 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -4,11 +4,11 @@ import PropTypes from 'prop-types'; import IntersectionObserverArticleContainer from 'flavours/glitch/containers/intersection_observer_article_container'; import LoadMore from './load_more'; import LoadPending from './load_pending'; -import IntersectionObserverWrapper from 'flavours/glitch/util/intersection_observer_wrapper'; +import IntersectionObserverWrapper from 'flavours/glitch/features/ui/util/intersection_observer_wrapper'; import { throttle } from 'lodash'; import { List as ImmutableList } from 'immutable'; import classNames from 'classnames'; -import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen'; +import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen'; import LoadingIndicator from './loading_indicator'; import { connect } from 'react-redux'; diff --git a/app/javascript/flavours/glitch/components/server_banner.js b/app/javascript/flavours/glitch/components/server_banner.js new file mode 100644 index 000000000..36e0ff238 --- /dev/null +++ b/app/javascript/flavours/glitch/components/server_banner.js @@ -0,0 +1,93 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import { fetchServer } from 'flavours/glitch/actions/server'; +import ShortNumber from 'flavours/glitch/components/short_number'; +import Skeleton from 'flavours/glitch/components/skeleton'; +import Account from 'flavours/glitch/containers/account_container'; +import { domain } from 'flavours/glitch/initial_state'; +import Image from 'flavours/glitch/components/image'; +import { Link } from 'react-router-dom'; + +const messages = defineMessages({ + aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' }, +}); + +const mapStateToProps = state => ({ + server: state.getIn(['server', 'server']), +}); + +export default @connect(mapStateToProps) +@injectIntl +class ServerBanner extends React.PureComponent { + + static propTypes = { + server: PropTypes.object, + dispatch: PropTypes.func, + intl: PropTypes.object, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchServer()); + } + + render () { + const { server, intl } = this.props; + const isLoading = server.get('isLoading'); + + return ( + <div className='server-banner'> + <div className='server-banner__introduction'> + <FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} /> + </div> + + <Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' /> + + <div className='server-banner__description'> + {isLoading ? ( + <> + <Skeleton width='100%' /> + <br /> + <Skeleton width='100%' /> + <br /> + <Skeleton width='70%' /> + </> + ) : server.get('description')} + </div> + + <div className='server-banner__meta'> + <div className='server-banner__meta__column'> + <h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4> + + <Account id={server.getIn(['contact', 'account', 'id'])} size={36} /> + </div> + + <div className='server-banner__meta__column'> + <h4><FormattedMessage id='server_banner.server_stats' defaultMessage='Server stats:' /></h4> + + {isLoading ? ( + <> + <strong className='server-banner__number'><Skeleton width='10ch' /></strong> + <br /> + <span className='server-banner__number-label'><Skeleton width='5ch' /></span> + </> + ) : ( + <> + <strong className='server-banner__number'><ShortNumber value={server.getIn(['usage', 'users', 'active_month'])} /></strong> + <br /> + <span className='server-banner__number-label' title={intl.formatMessage(messages.aboutActiveUsers)}><FormattedMessage id='server_banner.active_users' defaultMessage='active users' /></span> + </> + )} + </div> + </div> + + <hr className='spacer' /> + + <Link className='button button--block button-secondary' to='/about'><FormattedMessage id='server_banner.learn_more' defaultMessage='Learn more' /></Link> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/components/short_number.js b/app/javascript/flavours/glitch/components/short_number.js index e4ba09634..535c17727 100644 --- a/app/javascript/flavours/glitch/components/short_number.js +++ b/app/javascript/flavours/glitch/components/short_number.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../util/numbers'; +import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers'; import { FormattedMessage, FormattedNumber } from 'react-intl'; // @ts-check @@ -56,7 +56,7 @@ ShortNumber.propTypes = { /** * @typedef {object} ShortNumberCounterProps - * @property {import('../util/number').ShortNumber} value Short number + * @property {import('../utils/number').ShortNumber} value Short number */ /** diff --git a/app/javascript/flavours/glitch/components/skeleton.js b/app/javascript/flavours/glitch/components/skeleton.js index 09093e99c..6a17ffb26 100644 --- a/app/javascript/flavours/glitch/components/skeleton.js +++ b/app/javascript/flavours/glitch/components/skeleton.js @@ -4,8 +4,8 @@ import PropTypes from 'prop-types'; const Skeleton = ({ width, height }) => <span className='skeleton' style={{ width, height }}>‌</span>; Skeleton.propTypes = { - width: PropTypes.number, - height: PropTypes.number, + width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), + height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }; export default Skeleton; diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index e238456c5..366a98d82 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -10,13 +10,13 @@ import AttachmentList from './attachment_list'; import Card from '../features/status/components/card'; import { injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { MediaGallery, Video, Audio } from 'flavours/glitch/util/async-components'; +import { MediaGallery, Video, Audio } from '../features/ui/util/async-components'; import { HotKeys } from 'react-hotkeys'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; import classNames from 'classnames'; -import { autoUnfoldCW } from 'flavours/glitch/util/content_warning'; +import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import PollContainer from 'flavours/glitch/containers/poll_container'; -import { displayMedia } from 'flavours/glitch/util/initial_state'; +import { displayMedia } from 'flavours/glitch/initial_state'; import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder'; // We use the component (and not the container) since we do not want @@ -83,6 +83,7 @@ class Status extends ImmutablePureComponent { onEmbed: PropTypes.func, onHeightChange: PropTypes.func, onToggleHidden: PropTypes.func, + onInteractionModal: PropTypes.func, muted: PropTypes.bool, hidden: PropTypes.bool, unread: PropTypes.bool, @@ -99,8 +100,11 @@ class Status extends ImmutablePureComponent { onClick: PropTypes.func, scrollKey: PropTypes.string, deployPictureInPicture: PropTypes.func, - usingPiP: PropTypes.bool, settings: ImmutablePropTypes.map.isRequired, + pictureInPicture: PropTypes.shape({ + inUse: PropTypes.bool, + available: PropTypes.bool, + }), }; state = { @@ -126,7 +130,7 @@ class Status extends ImmutablePureComponent { 'hidden', 'expanded', 'unread', - 'usingPiP', + 'pictureInPicture', ] updateOnStates = [ @@ -495,7 +499,6 @@ class Status extends ImmutablePureComponent { settings, collapsed, muted, - prepend, intersectionObserverWrapper, onOpenVideo, onOpenMedia, @@ -503,7 +506,7 @@ class Status extends ImmutablePureComponent { hidden, unread, featured, - usingPiP, + pictureInPicture, ...other } = this.props; const { isCollapsed, forceFilter } = this.state; @@ -595,7 +598,7 @@ class Status extends ImmutablePureComponent { attachments = status.get('media_attachments'); - if (usingPiP) { + if (pictureInPicture.inUse) { media.push(<PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />); mediaIcons.push('video-camera'); } else if (attachments.size > 0) { @@ -623,7 +626,7 @@ class Status extends ImmutablePureComponent { width={this.props.cachedMediaWidth} height={110} cacheWidth={this.props.cacheMediaWidth} - deployPictureInPicture={this.handleDeployPictureInPicture} + deployPictureInPicture={pictureInPicture.available ? this.handleDeployPictureInPicture : undefined} sensitive={status.get('sensitive')} blurhash={attachment.get('blurhash')} visible={this.state.showMedia} @@ -652,7 +655,7 @@ class Status extends ImmutablePureComponent { onOpenVideo={this.handleOpenVideo} width={this.props.cachedMediaWidth} cacheWidth={this.props.cacheMediaWidth} - deployPictureInPicture={this.handleDeployPictureInPicture} + deployPictureInPicture={pictureInPicture.available ? this.handleDeployPictureInPicture : undefined} visible={this.state.showMedia} onToggleVisibility={this.handleToggleMediaVisibility} />)} @@ -709,20 +712,31 @@ class Status extends ImmutablePureComponent { 'data-status-by': `@${status.getIn(['account', 'acct'])}`, }; - if (prepend && account) { + let prepend; + + if (this.props.prepend && account) { const notifKind = { favourite: 'favourited', reblog: 'boosted', reblogged_by: 'boosted', status: 'posted', - }[prepend]; + }[this.props.prepend]; selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`; + + prepend = ( + <StatusPrepend + type={this.props.prepend} + account={account} + parseClick={parseClick} + notificationId={this.props.notificationId} + /> + ); } let rebloggedByText; - if (prepend === 'reblog') { + if (this.props.prepend === 'reblog') { rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') }); } @@ -745,16 +759,10 @@ class Status extends ImmutablePureComponent { data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))} > + {!muted && prepend} <header className='status__info'> <span> - {prepend && account ? ( - <StatusPrepend - type={prepend} - account={account} - parseClick={parseClick} - notificationId={this.props.notificationId} - /> - ) : null} + {muted && prepend} {!muted || !isCollapsed ? ( <StatusHeader status={status} diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index c0cd496ce..977c98ccb 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -5,9 +5,9 @@ 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 } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; import RelativeTimestamp from './relative_timestamp'; -import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links'; +import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import classNames from 'classnames'; import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions'; @@ -42,6 +42,7 @@ const messages = defineMessages({ hide: { id: 'status.hide', defaultMessage: 'Hide toot' }, edited: { id: 'status.edited', defaultMessage: 'Edited {date}' }, filter: { id: 'status.filter', defaultMessage: 'Filter this post' }, + openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, }); export default @injectIntl @@ -69,7 +70,9 @@ class StatusActionBar extends ImmutablePureComponent { onBookmark: PropTypes.func, onFilter: PropTypes.func, onAddFilter: PropTypes.func, + onInteractionModal: PropTypes.func, withDismiss: PropTypes.bool, + withCounters: PropTypes.bool, showReplyCount: PropTypes.bool, scrollKey: PropTypes.string, intl: PropTypes.object.isRequired, @@ -80,14 +83,17 @@ class StatusActionBar extends ImmutablePureComponent { updateOnProps = [ 'status', 'showReplyCount', + 'withCounters', 'withDismiss', ] handleReplyClick = () => { - if (me) { + const { signedIn } = this.context.identity; + + if (signedIn) { this.props.onReply(this.props.status, this.context.router.history); } else { - this._openInteractionDialog('reply'); + this.props.onInteractionModal('reply', this.props.status); } } @@ -99,28 +105,28 @@ class StatusActionBar extends ImmutablePureComponent { } handleFavouriteClick = (e) => { - if (me) { + const { signedIn } = this.context.identity; + + if (signedIn) { this.props.onFavourite(this.props.status, e); } else { - this._openInteractionDialog('favourite'); + this.props.onInteractionModal('favourite', this.props.status); } } - handleBookmarkClick = (e) => { - this.props.onBookmark(this.props.status, e); - } - handleReblogClick = e => { - if (me) { + const { signedIn } = this.context.identity; + + if (signedIn) { this.props.onReblog(this.props.status, e); } else { - this._openInteractionDialog('reblog'); + this.props.onInteractionModal('reblog', this.props.status); } } - _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'); - } + handleBookmarkClick = (e) => { + this.props.onBookmark(this.props.status, e); + } handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); @@ -177,22 +183,8 @@ class StatusActionBar extends ImmutablePureComponent { } handleCopy = () => { - const url = this.props.status.get('url'); - const textarea = document.createElement('textarea'); - - textarea.textContent = url; - textarea.style.position = 'fixed'; - - document.body.appendChild(textarea); - - try { - textarea.select(); - document.execCommand('copy'); - } catch (e) { - - } finally { - document.body.removeChild(textarea); - } + const url = this.props.status.get('url'); + navigator.clipboard.writeText(url); } handleHideClick = () => { @@ -204,13 +196,14 @@ class StatusActionBar extends ImmutablePureComponent { } render () { - const { status, intl, withDismiss, showReplyCount, scrollKey } = this.props; + const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; const anonymousAccess = !me; const mutingConversation = status.get('muted'); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); const writtenByMe = status.getIn(['account', 'id']) === me; + const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']); let menu = []; let reblogIcon = 'retweet'; @@ -220,6 +213,9 @@ class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); if (publicStatus) { + if (isRemote) { + menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') }); + } menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy }); menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); } @@ -237,7 +233,7 @@ class StatusActionBar extends ImmutablePureComponent { } if (writtenByMe) { - // menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); + menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); } else { @@ -283,27 +279,6 @@ class StatusActionBar extends ImmutablePureComponent { <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} /> ); - let replyButton = ( - <IconButton - className='status__action-bar-button' - title={replyTitle} - icon={replyIcon} - onClick={this.handleReplyClick} - /> - ); - if (showReplyCount) { - replyButton = ( - <IconButton - className='status__action-bar-button' - title={replyTitle} - icon={replyIcon} - onClick={this.handleReplyClick} - counter={status.get('replies_count')} - obfuscateCount - /> - ); - } - const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private'; let reblogTitle = ''; @@ -323,11 +298,18 @@ class StatusActionBar extends ImmutablePureComponent { return ( <div className='status__action-bar'> - {replyButton} - <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} /> - <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> + <IconButton + className='status__action-bar-button' + title={replyTitle} + icon={replyIcon} + onClick={this.handleReplyClick} + counter={showReplyCount ? status.get('replies_count') : undefined} + obfuscateCount + /> + <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon={reblogIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} /> + <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} /> {shareButton} - <IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /> + <IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /> {filterButton} diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js index 39891da4f..c618cedca 100644 --- a/app/javascript/flavours/glitch/components/status_content.js +++ b/app/javascript/flavours/glitch/components/status_content.js @@ -5,8 +5,8 @@ import { FormattedMessage } from 'react-intl'; import Permalink from './permalink'; import classnames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; -import { decode as decodeIDNA } from 'flavours/glitch/util/idna'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; +import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; const textMatchesTarget = (text, origin, host) => { return (text === origin || text === host @@ -124,6 +124,9 @@ export default class StatusContent extends React.PureComponent { link.setAttribute('title', link.href); link.classList.add('unhandled-link'); + link.setAttribute('target', '_blank'); + link.setAttribute('rel', 'noopener nofollow noreferrer'); + try { if (tagLinks && isLinkMisleading(link)) { // Add a tag besides the link to display its origin @@ -149,9 +152,6 @@ export default class StatusContent extends React.PureComponent { if (tagLinks && e instanceof TypeError) link.removeAttribute('href'); } } - - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener noreferrer'); } } @@ -332,7 +332,7 @@ export default class StatusContent extends React.PureComponent { > <span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={lang} /> {' '} - <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}> + <button type='button' className='status__content__spoiler-link' onClick={this.handleSpoilerClick} aria-expanded={!hidden}> {toggleText} </button> </p> diff --git a/app/javascript/flavours/glitch/components/status_icons.js b/app/javascript/flavours/glitch/components/status_icons.js index 2226aaef2..71ffb2e56 100644 --- a/app/javascript/flavours/glitch/components/status_icons.js +++ b/app/javascript/flavours/glitch/components/status_icons.js @@ -8,7 +8,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import IconButton from './icon_button'; import VisibilityIcon from './status_visibility_icon'; import Icon from 'flavours/glitch/components/icon'; -import { languages } from 'flavours/glitch/util/initial_state'; +import { languages } from 'flavours/glitch/initial_state'; // Messages for use with internationalization stuff. const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js index 9095e087e..0d843a27d 100644 --- a/app/javascript/flavours/glitch/components/status_list.js +++ b/app/javascript/flavours/glitch/components/status_list.js @@ -22,9 +22,11 @@ export default class StatusList extends ImmutablePureComponent { isPartial: PropTypes.bool, hasMore: PropTypes.bool, prepend: PropTypes.node, - alwaysPrepend: PropTypes.bool, emptyMessage: PropTypes.node, + alwaysPrepend: PropTypes.bool, + withCounters: PropTypes.bool, timelineId: PropTypes.string.isRequired, + regex: PropTypes.string, }; static defaultProps = { @@ -99,6 +101,7 @@ export default class StatusList extends ImmutablePureComponent { onMoveDown={this.handleMoveDown} contextType={timelineId} scrollKey={this.props.scrollKey} + withCounters={this.props.withCounters} /> )) ) : null; @@ -113,6 +116,7 @@ export default class StatusList extends ImmutablePureComponent { onMoveDown={this.handleMoveDown} contextType={timelineId} scrollKey={this.props.scrollKey} + withCounters={this.props.withCounters} /> )).concat(scrollableContent); } diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js index d85009362..f82533062 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.js +++ b/app/javascript/flavours/glitch/components/status_prepend.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; import Icon from 'flavours/glitch/components/icon'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; export default class StatusPrepend extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/containers/account_container.js b/app/javascript/flavours/glitch/containers/account_container.js index bc84d299b..5b57d730f 100644 --- a/app/javascript/flavours/glitch/containers/account_container.js +++ b/app/javascript/flavours/glitch/containers/account_container.js @@ -13,7 +13,7 @@ import { } from 'flavours/glitch/actions/accounts'; import { openModal } from 'flavours/glitch/actions/modal'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; -import { unfollowModal } from 'flavours/glitch/util/initial_state'; +import { unfollowModal } from 'flavours/glitch/initial_state'; const messages = defineMessages({ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, diff --git a/app/javascript/flavours/glitch/containers/compose_container.js b/app/javascript/flavours/glitch/containers/compose_container.js index 74c411b7c..1e49b89a0 100644 --- a/app/javascript/flavours/glitch/containers/compose_container.js +++ b/app/javascript/flavours/glitch/containers/compose_container.js @@ -6,7 +6,7 @@ import { hydrateStore } from 'flavours/glitch/actions/store'; import { IntlProvider, addLocaleData } from 'react-intl'; import { getLocale } from 'mastodon/locales'; import Compose from 'flavours/glitch/features/standalone/compose'; -import initialState from 'flavours/glitch/util/initial_state'; +import initialState from 'flavours/glitch/initial_state'; import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis'; const { localeData, messages } = getLocale(); diff --git a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js index 0c4a2b50f..b2dff63db 100644 --- a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js +++ b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js @@ -2,7 +2,7 @@ import { openDropdownMenu, closeDropdownMenu } from 'flavours/glitch/actions/dro import { openModal, closeModal } from 'flavours/glitch/actions/modal'; import { connect } from 'react-redux'; import DropdownMenu from 'flavours/glitch/components/dropdown_menu'; -import { isUserTouching } from 'flavours/glitch/util/is_mobile'; +import { isUserTouching } from '../is_mobile'; const mapStateToProps = state => ({ dropdownPlacement: state.getIn(['dropdown_menu', 'placement']), diff --git a/app/javascript/flavours/glitch/containers/mastodon.js b/app/javascript/flavours/glitch/containers/mastodon.js index d07b2b3d0..6c92376d8 100644 --- a/app/javascript/flavours/glitch/containers/mastodon.js +++ b/app/javascript/flavours/glitch/containers/mastodon.js @@ -1,22 +1,25 @@ -import React from 'react'; -import { Provider } from 'react-redux'; import PropTypes from 'prop-types'; -import configureStore from 'flavours/glitch/store/configureStore'; +import React from 'react'; +import { Helmet } from 'react-helmet'; +import { IntlProvider, addLocaleData } from 'react-intl'; +import { Provider as ReduxProvider } from 'react-redux'; import { BrowserRouter, Route } from 'react-router-dom'; import { ScrollContext } from 'react-router-scroll-4'; +import configureStore from 'flavours/glitch/store/configureStore'; import UI from 'flavours/glitch/features/ui'; import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis'; import { hydrateStore } from 'flavours/glitch/actions/store'; -import { connectUserStream } from 'flavours/glitch/actions/streaming'; import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_settings'; -import { IntlProvider, addLocaleData } from 'react-intl'; -import { getLocale } from 'locales'; -import initialState from 'flavours/glitch/util/initial_state'; +import { connectUserStream } from 'flavours/glitch/actions/streaming'; import ErrorBoundary from 'flavours/glitch/components/error_boundary'; +import initialState, { title as siteTitle } from 'flavours/glitch/initial_state'; +import { getLocale } from 'locales'; const { localeData, messages } = getLocale(); addLocaleData(localeData); +const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`; + export const store = configureStore(); const hydrateAction = hydrateStore(initialState); store.dispatch(hydrateAction); @@ -30,8 +33,9 @@ store.dispatch(fetchCustomEmojis()); const createIdentityContext = state => ({ signedIn: !!state.meta.me, accountId: state.meta.me, + disabledAccountId: state.meta.disabled_account_id, accessToken: state.meta.access_token, - permissions: state.role.permissions, + permissions: state.role ? state.role.permissions : 0, }); export default class Mastodon extends React.PureComponent { @@ -44,6 +48,7 @@ export default class Mastodon extends React.PureComponent { identity: PropTypes.shape({ signedIn: PropTypes.bool.isRequired, accountId: PropTypes.string, + disabledAccountId: PropTypes.string, accessToken: PropTypes.string, }).isRequired, }; @@ -78,15 +83,17 @@ export default class Mastodon extends React.PureComponent { return ( <IntlProvider locale={locale} messages={messages}> - <Provider store={store}> + <ReduxProvider store={store}> <ErrorBoundary> - <BrowserRouter basename='/web'> + <BrowserRouter> <ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}> <Route path='/' component={UI} /> </ScrollContext> </BrowserRouter> + + <Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} /> </ErrorBoundary> - </Provider> + </ReduxProvider> </IntlProvider> ); } diff --git a/app/javascript/flavours/glitch/containers/media_container.js b/app/javascript/flavours/glitch/containers/media_container.js index 11c15d7c6..f1e8534aa 100644 --- a/app/javascript/flavours/glitch/containers/media_container.js +++ b/app/javascript/flavours/glitch/containers/media_container.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { IntlProvider, addLocaleData } from 'react-intl'; import { fromJS } from 'immutable'; import { getLocale } from 'mastodon/locales'; -import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar'; +import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar'; import MediaGallery from 'flavours/glitch/components/media_gallery'; import Poll from 'flavours/glitch/components/poll'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 0ba2e712c..c12b2e614 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -36,8 +36,8 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { deployPictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state'; -import { filterEditLink } from 'flavours/glitch/util/backend_links'; +import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/initial_state'; +import { filterEditLink } from 'flavours/glitch/utils/backend_links'; import { showAlertForError } from '../actions/alerts'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Spoilers from '../components/spoilers'; @@ -76,12 +76,16 @@ const makeMapStateToProps = () => { } return { - containerId : props.containerId || props.id, // Should match reblogStatus's id for reblogs - status : status, - account : account || props.account, - settings : state.get('local_settings'), - prepend : prepend || props.prepend, - usingPiP : state.get('picture_in_picture').statusId === props.id, + containerId: props.containerId || props.id, // Should match reblogStatus's id for reblogs + status: status, + account: account || props.account, + settings: state.get('local_settings'), + prepend: prepend || props.prepend, + + pictureInPicture: { + inUse: state.getIn(['meta', 'layout']) !== 'mobile' && state.get('picture_in_picture').statusId === props.id, + available: state.getIn(['meta', 'layout']) !== 'mobile', + }, }; }; @@ -240,6 +244,14 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }); }, + onInteractionModal (type, status) { + dispatch(openModal('INTERACTION', { + type, + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); + }, + }); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status)); diff --git a/app/javascript/flavours/glitch/containers/timeline_container.js b/app/javascript/flavours/glitch/containers/timeline_container.js deleted file mode 100644 index b61dc8dd7..000000000 --- a/app/javascript/flavours/glitch/containers/timeline_container.js +++ /dev/null @@ -1,62 +0,0 @@ -import React, { Fragment } from 'react'; -import ReactDOM from 'react-dom'; -import { Provider } from 'react-redux'; -import PropTypes from 'prop-types'; -import configureStore from 'flavours/glitch/store/configureStore'; -import { hydrateStore } from 'flavours/glitch/actions/store'; -import { IntlProvider, addLocaleData } from 'react-intl'; -import { getLocale } from 'mastodon/locales'; -import PublicTimeline from 'flavours/glitch/features/standalone/public_timeline'; -import HashtagTimeline from 'flavours/glitch/features/standalone/hashtag_timeline'; -import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container'; -import initialState from 'flavours/glitch/util/initial_state'; - -const { localeData, messages } = getLocale(); -addLocaleData(localeData); - -const store = configureStore(); - -if (initialState) { - store.dispatch(hydrateStore(initialState)); -} - -export default class TimelineContainer extends React.PureComponent { - - static propTypes = { - locale: PropTypes.string.isRequired, - hashtag: PropTypes.string, - local: PropTypes.bool, - }; - - static defaultProps = { - local: !initialState.settings.known_fediverse, - }; - - render () { - const { locale, hashtag, local } = this.props; - - let timeline; - - if (hashtag) { - timeline = <HashtagTimeline hashtag={hashtag} local={local} />; - } else { - timeline = <PublicTimeline local={local} />; - } - - return ( - <IntlProvider locale={locale} messages={messages}> - <Provider store={store}> - <Fragment> - {timeline} - - {ReactDOM.createPortal( - <ModalContainer />, - document.getElementById('modal-container'), - )} - </Fragment> - </Provider> - </IntlProvider> - ); - } - -} diff --git a/app/javascript/flavours/glitch/util/extra_polyfills.js b/app/javascript/flavours/glitch/extra_polyfills.js index 3acc55abd..0d45c23b0 100644 --- a/app/javascript/flavours/glitch/util/extra_polyfills.js +++ b/app/javascript/flavours/glitch/extra_polyfills.js @@ -1,3 +1,4 @@ +import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'; import 'intersection-observer'; import 'requestidlecallback'; import objectFitImages from 'object-fit-images'; diff --git a/app/javascript/flavours/glitch/features/about/index.js b/app/javascript/flavours/glitch/features/about/index.js new file mode 100644 index 000000000..4129c8236 --- /dev/null +++ b/app/javascript/flavours/glitch/features/about/index.js @@ -0,0 +1,227 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import Column from 'flavours/glitch/components/column'; +import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; +import { Helmet } from 'react-helmet'; +import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; +import Account from 'flavours/glitch/containers/account_container'; +import Skeleton from 'flavours/glitch/components/skeleton'; +import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; +import Image from 'flavours/glitch/components/image'; + +const messages = defineMessages({ + title: { id: 'column.about', defaultMessage: 'About' }, + rules: { id: 'about.rules', defaultMessage: 'Server rules' }, + blocks: { id: 'about.blocks', defaultMessage: 'Moderated servers' }, + silenced: { id: 'about.domain_blocks.silenced.title', defaultMessage: 'Limited' }, + silencedExplanation: { id: 'about.domain_blocks.silenced.explanation', defaultMessage: 'You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.' }, + suspended: { id: 'about.domain_blocks.suspended.title', defaultMessage: 'Suspended' }, + suspendedExplanation: { id: 'about.domain_blocks.suspended.explanation', defaultMessage: 'No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.' }, +}); + +const severityMessages = { + silence: { + title: messages.silenced, + explanation: messages.silencedExplanation, + }, + + suspend: { + title: messages.suspended, + explanation: messages.suspendedExplanation, + }, +}; + +const mapStateToProps = state => ({ + server: state.getIn(['server', 'server']), + extendedDescription: state.getIn(['server', 'extendedDescription']), + domainBlocks: state.getIn(['server', 'domainBlocks']), +}); + +class Section extends React.PureComponent { + + static propTypes = { + title: PropTypes.string, + children: PropTypes.node, + open: PropTypes.bool, + onOpen: PropTypes.func, + }; + + state = { + collapsed: !this.props.open, + }; + + handleClick = () => { + const { onOpen } = this.props; + const { collapsed } = this.state; + + this.setState({ collapsed: !collapsed }, () => onOpen && onOpen()); + } + + render () { + const { title, children } = this.props; + const { collapsed } = this.state; + + return ( + <div className={classNames('about__section', { active: !collapsed })}> + <div className='about__section__title' role='button' tabIndex='0' onClick={this.handleClick}> + <Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title} + </div> + + {!collapsed && ( + <div className='about__section__body'>{children}</div> + )} + </div> + ); + } + +} + +export default @connect(mapStateToProps) +@injectIntl +class About extends React.PureComponent { + + static propTypes = { + server: ImmutablePropTypes.map, + extendedDescription: ImmutablePropTypes.map, + domainBlocks: ImmutablePropTypes.contains({ + isLoading: PropTypes.bool, + isAvailable: PropTypes.bool, + items: ImmutablePropTypes.list, + }), + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchServer()); + dispatch(fetchExtendedDescription()); + } + + handleDomainBlocksOpen = () => { + const { dispatch } = this.props; + dispatch(fetchDomainBlocks()); + } + + render () { + const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props; + const isLoading = server.get('isLoading'); + + return ( + <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> + <div className='scrollable about'> + <div className='about__header'> + <Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> + <h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1> + <p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</a> }} /></p> + </div> + + <div className='about__meta'> + <div className='about__meta__column'> + <h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4> + + <Account id={server.getIn(['contact', 'account', 'id'])} size={36} /> + </div> + + <hr className='about__meta__divider' /> + + <div className='about__meta__column'> + <h4><FormattedMessage id='about.contact' defaultMessage='Contact:' /></h4> + + {isLoading ? <Skeleton width='10ch' /> : <a className='about__mail' href={`mailto:${server.getIn(['contact', 'email'])}`}>{server.getIn(['contact', 'email'])}</a>} + </div> + </div> + + <Section open title={intl.formatMessage(messages.title)}> + {extendedDescription.get('isLoading') ? ( + <> + <Skeleton width='100%' /> + <br /> + <Skeleton width='100%' /> + <br /> + <Skeleton width='100%' /> + <br /> + <Skeleton width='70%' /> + </> + ) : (extendedDescription.get('content')?.length > 0 ? ( + <div + className='prose' + dangerouslySetInnerHTML={{ __html: extendedDescription.get('content') }} + /> + ) : ( + <p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p> + ))} + </Section> + + <Section title={intl.formatMessage(messages.rules)}> + {!isLoading && (server.get('rules').isEmpty() ? ( + <p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p> + ) : ( + <ol className='rules-list'> + {server.get('rules').map(rule => ( + <li key={rule.get('id')}> + <span className='rules-list__text'>{rule.get('text')}</span> + </li> + ))} + </ol> + ))} + </Section> + + <Section title={intl.formatMessage(messages.blocks)} onOpen={this.handleDomainBlocksOpen}> + {domainBlocks.get('isLoading') ? ( + <> + <Skeleton width='100%' /> + <br /> + <Skeleton width='70%' /> + </> + ) : (domainBlocks.get('isAvailable') ? ( + <> + <p><FormattedMessage id='about.domain_blocks.preamble' defaultMessage='Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.' /></p> + + <table className='about__domain-blocks'> + <thead> + <tr> + <th><FormattedMessage id='about.domain_blocks.domain' defaultMessage='Domain' /></th> + <th><FormattedMessage id='about.domain_blocks.severity' defaultMessage='Severity' /></th> + <th><FormattedMessage id='about.domain_blocks.comment' defaultMessage='Reason' /></th> + </tr> + </thead> + + <tbody> + {domainBlocks.get('items').map(block => ( + <tr key={block.get('domain')}> + <td><span title={`SHA-256: ${block.get('digest')}`}>{block.get('domain')}</span></td> + <td><span title={intl.formatMessage(severityMessages[block.get('severity')].explanation)}>{intl.formatMessage(severityMessages[block.get('severity')].title)}</span></td> + <td>{block.get('comment')}</td> + </tr> + ))} + </tbody> + </table> + </> + ) : ( + <p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p> + ))} + </Section> + + <LinkFooter /> + + <div className='about__footer'> + <p><FormattedMessage id='about.fork_disclaimer' defaultMessage='Glitch-soc is free open source software forked from Mastodon.' /></p> + <p><FormattedMessage id='about.disclaimer' defaultMessage='Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.' /></p> + </div> + </div> + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='all' /> + </Helmet> + </Column> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js index 23120d57e..ce0584124 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js @@ -4,8 +4,8 @@ import PropTypes from 'prop-types'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import { NavLink } from 'react-router-dom'; import { injectIntl, FormattedMessage, FormattedNumber } from 'react-intl'; -import { me, isStaff } from 'flavours/glitch/util/initial_state'; -import { profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links'; +import { me, isStaff } from 'flavours/glitch/initial_state'; +import { profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; import Icon from 'flavours/glitch/components/icon'; export default @injectIntl diff --git a/app/javascript/flavours/glitch/features/account/components/featured_tags.js b/app/javascript/flavours/glitch/features/account/components/featured_tags.js new file mode 100644 index 000000000..d646b08b2 --- /dev/null +++ b/app/javascript/flavours/glitch/features/account/components/featured_tags.js @@ -0,0 +1,53 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Hashtag from 'flavours/glitch/components/hashtag'; + +const messages = defineMessages({ + lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, + empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' }, +}); + +export default @injectIntl +class FeaturedTags extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + account: ImmutablePropTypes.map, + featuredTags: ImmutablePropTypes.list, + tagged: PropTypes.string, + intl: PropTypes.object.isRequired, + }; + + render () { + const { account, featuredTags, intl } = this.props; + + if (!account || account.get('suspended') || featuredTags.isEmpty()) { + return null; + } + + return ( + <div className='getting-started__trends'> + <h4><FormattedMessage id='account.featured_tags.title' defaultMessage="{name}'s featured hashtags" values={{ name: <bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /> }} /></h4> + + {featuredTags.take(3).map(featuredTag => ( + <Hashtag + key={featuredTag.get('name')} + name={featuredTag.get('name')} + href={featuredTag.get('url')} + to={`/@${account.get('acct')}/tagged/${featuredTag.get('name')}`} + uses={featuredTag.get('statuses_count')} + withGraph={false} + description={((featuredTag.get('statuses_count') * 1) > 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)} + /> + ))} + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 53170b7a6..93831b3e7 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -3,8 +3,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { autoPlayGif, me } from 'flavours/glitch/util/initial_state'; -import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/util/backend_links'; +import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state'; +import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; import IconButton from 'flavours/glitch/components/icon_button'; @@ -14,11 +14,12 @@ import { NavLink } from 'react-router-dom'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import AccountNoteContainer from '../containers/account_note_container'; import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, - cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' }, + cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, @@ -51,8 +52,18 @@ const messages = defineMessages({ add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' }, + languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' }, + openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, }); +const titleFromAccount = account => { + const displayName = account.get('display_name'); + const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct'); + const prefix = displayName.trim().length === 0 ? account.get('username') : displayName; + + return `${prefix} (@${acct})`; +}; + const dateFormatOptions = { month: 'short', day: 'numeric', @@ -85,6 +96,9 @@ class Header extends ImmutablePureComponent { onEndorseToggle: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired, onEditAccountNote: PropTypes.func.isRequired, + onChangeLanguages: PropTypes.func.isRequired, + onInteractionModal: PropTypes.func.isRequired, + onOpenAvatar: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, @@ -120,8 +134,16 @@ class Header extends ImmutablePureComponent { } } + handleAvatarClick = e => { + if (e.button === 0 && !(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + this.props.onOpenAvatar(); + } + } + render () { const { account, hidden, intl, domain } = this.props; + const { signedIn } = this.context.identity; if (!account) { return null; @@ -129,7 +151,9 @@ class Header extends ImmutablePureComponent { const accountNote = account.getIn(['relationship', 'note']); - const suspended = account.get('suspended'); + const suspended = account.get('suspended'); + const isRemote = account.get('acct') !== account.get('username'); + const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null; let info = []; let actionBtn = ''; @@ -155,12 +179,12 @@ class Header extends ImmutablePureComponent { } if (me !== account.get('id')) { - if (!account.get('relationship')) { // Wait until the relationship is loaded + if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ''; } else if (account.getIn(['relationship', 'requested'])) { actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; } else if (!account.getIn(['relationship', 'blocking'])) { - actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; + actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : this.props.onInteractionModal} />; } else if (account.getIn(['relationship', 'blocking'])) { actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; } @@ -180,12 +204,17 @@ class Header extends ImmutablePureComponent { lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />; } - if (account.get('id') !== me && !suspended) { + if (signedIn && account.get('id') !== me && !suspended) { menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); menu.push(null); } + if (isRemote) { + menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') }); + menu.push(null); + } + if ('share' in navigator && !suspended) { menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); menu.push(null); @@ -207,7 +236,7 @@ class Header extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' }); menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' }); menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }); - } else { + } else if (signedIn) { if (account.getIn(['relationship', 'following'])) { if (!account.getIn(['relationship', 'muting'])) { if (account.getIn(['relationship', 'showing_reblogs'])) { @@ -215,6 +244,9 @@ class Header extends ImmutablePureComponent { } else { menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); } + + menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages }); + menu.push(null); } menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); @@ -237,15 +269,13 @@ class Header extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); } - if (account.get('acct') !== account.get('username')) { - const domain = account.get('acct').split('@')[1]; - + if (signedIn && isRemote) { menu.push(null); if (account.getIn(['relationship', 'domain_blocking'])) { - menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain }); + menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain }); } else { - menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain }); + menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain }); } } @@ -257,7 +287,9 @@ class Header extends ImmutablePureComponent { const content = { __html: account.get('note_emojified') }; const displayNameHtml = { __html: account.get('display_name_html') }; const fields = account.get('fields'); - const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const isLocal = account.get('acct').indexOf('@') === -1; + const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const isIndexable = !account.get('noindex'); let badge; @@ -281,7 +313,7 @@ class Header extends ImmutablePureComponent { <div className='account__header__bar'> <div className='account__header__tabs'> - <a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'> + <a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}> <Avatar account={suspended || hidden ? undefined : account} size={90} /> </a> @@ -296,7 +328,7 @@ class Header extends ImmutablePureComponent { </React.Fragment> )} - <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' /> + <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' /> </div> )} </div> @@ -308,7 +340,7 @@ class Header extends ImmutablePureComponent { </h1> </div> - <AccountNoteContainer account={account} /> + {signedIn && <AccountNoteContainer account={account} />} {!(suspended || hidden) && ( <div className='account__header__extra'> @@ -334,6 +366,11 @@ class Header extends ImmutablePureComponent { </div> )} </div> + + <Helmet> + <title>{titleFromAccount(account)}</title> + <meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} /> + </Helmet> </div> ); } diff --git a/app/javascript/flavours/glitch/features/account/containers/featured_tags_container.js b/app/javascript/flavours/glitch/features/account/containers/featured_tags_container.js new file mode 100644 index 000000000..6f0b06941 --- /dev/null +++ b/app/javascript/flavours/glitch/features/account/containers/featured_tags_container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import FeaturedTags from '../components/featured_tags'; +import { makeGetAccount } from 'flavours/glitch/selectors'; +import { List as ImmutableList } from 'immutable'; + +const mapStateToProps = () => { + const getAccount = makeGetAccount(); + + return (state, { accountId }) => ({ + account: getAccount(state, accountId), + featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items'], ImmutableList()), + }); +}; + +export default connect(mapStateToProps)(FeaturedTags); diff --git a/app/javascript/flavours/glitch/features/account/navigation.js b/app/javascript/flavours/glitch/features/account/navigation.js new file mode 100644 index 000000000..edae38ce5 --- /dev/null +++ b/app/javascript/flavours/glitch/features/account/navigation.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import FeaturedTags from 'flavours/glitch/features/account/containers/featured_tags_container'; +import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; + +const mapStateToProps = (state, { match: { params: { acct } } }) => { + const accountId = state.getIn(['accounts_map', normalizeForLookup(acct)]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + isLoading: false, + }; +}; + +export default @connect(mapStateToProps) +class AccountNavigation extends React.PureComponent { + + static propTypes = { + match: PropTypes.shape({ + params: PropTypes.shape({ + acct: PropTypes.string, + tagged: PropTypes.string, + }).isRequired, + }).isRequired, + + accountId: PropTypes.string, + isLoading: PropTypes.bool, + }; + + render () { + const { accountId, isLoading, match: { params: { tagged } } } = this.props; + + if (isLoading) { + return null; + } + + return ( + <> + <div className='flex-spacer' /> + <FeaturedTags accountId={accountId} tagged={tagged} /> + </> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js index 7457980d2..a16ee4806 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js +++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js @@ -1,8 +1,8 @@ import Blurhash from 'flavours/glitch/components/blurhash'; import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; -import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; -import { isIOS } from 'flavours/glitch/util/is_mobile'; +import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; +import { isIOS } from 'flavours/glitch/is_mobile'; import PropTypes from 'prop-types'; import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js index 8df1bf4ca..638224bc0 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.js +++ b/app/javascript/flavours/glitch/features/account_gallery/index.js @@ -15,9 +15,10 @@ import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import LoadMore from 'flavours/glitch/components/load_more'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import { openModal } from 'flavours/glitch/actions/modal'; +import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; const mapStateToProps = (state, { params: { acct, id } }) => { - const accountId = id || state.getIn(['accounts_map', acct]); + const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); if (!accountId) { return { diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index 645ff29ea..90c4c9d51 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -23,6 +23,9 @@ export default class Header extends ImmutablePureComponent { onUnblockDomain: PropTypes.func.isRequired, onEndorseToggle: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired, + onChangeLanguages: PropTypes.func.isRequired, + onInteractionModal: PropTypes.func.isRequired, + onOpenAvatar: PropTypes.func.isRequired, hideTabs: PropTypes.bool, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, @@ -92,6 +95,18 @@ export default class Header extends ImmutablePureComponent { this.props.onEditAccountNote(this.props.account); } + handleChangeLanguages = () => { + this.props.onChangeLanguages(this.props.account); + } + + handleInteractionModal = () => { + this.props.onInteractionModal(this.props.account); + } + + handleOpenAvatar = () => { + this.props.onOpenAvatar(this.props.account); + } + render () { const { account, hidden, hideTabs } = this.props; @@ -118,6 +133,9 @@ export default class Header extends ImmutablePureComponent { onEndorseToggle={this.handleEndorseToggle} onAddToList={this.handleAddToList} onEditAccountNote={this.handleEditAccountNote} + onChangeLanguages={this.handleChangeLanguages} + onInteractionModal={this.handleInteractionModal} + onOpenAvatar={this.handleOpenAvatar} domain={this.props.domain} hidden={hidden} /> diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js b/app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js index e465c83b4..ca0e60672 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/limited_account_hint.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { revealAccount } from 'flavours/glitch/actions/accounts'; import { FormattedMessage } from 'react-intl'; import Button from 'flavours/glitch/components/button'; +import { domain } from 'flavours/glitch/initial_state'; const mapDispatchToProps = (dispatch, { accountId }) => ({ @@ -26,7 +27,7 @@ class LimitedAccountHint extends React.PureComponent { return ( <div className='limited-account-hint'> - <p><FormattedMessage id='limited_account_hint.title' defaultMessage='This profile has been hidden by the moderators of your server.' /></p> + <p><FormattedMessage id='limited_account_hint.title' defaultMessage='This profile has been hidden by the moderators of {domain}.' values={{ domain }} /></p> <Button onClick={reveal}><FormattedMessage id='limited_account_hint.action' defaultMessage='Show profile anyway' /></Button> </div> ); diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js index 3fa7c1448..25bcd0119 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js @@ -21,9 +21,10 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { blockDomain, unblockDomain } from 'flavours/glitch/actions/domain_blocks'; import { initEditAccountNote } from 'flavours/glitch/actions/account_notes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { unfollowModal } from 'flavours/glitch/util/initial_state'; +import { unfollowModal } from 'flavours/glitch/initial_state'; const messages = defineMessages({ + cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' }, }); @@ -43,7 +44,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { - if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { + if (account.getIn(['relationship', 'following'])) { if (unfollowModal) { dispatch(openModal('CONFIRM', { message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, @@ -53,11 +54,29 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } else { dispatch(unfollowAccount(account.get('id'))); } + } else if (account.getIn(['relationship', 'requested'])) { + if (unfollowModal) { + dispatch(openModal('CONFIRM', { + message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, + confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + })); + } else { + dispatch(unfollowAccount(account.get('id'))); + } } else { dispatch(followAccount(account.get('id'))); } }, + onInteractionModal (account) { + dispatch(openModal('INTERACTION', { + type: 'follow', + accountId: account.get('id'), + url: account.get('url'), + })); + }, + onBlock (account) { if (account.getIn(['relationship', 'blocking'])) { dispatch(unblockAccount(account.get('id'))); @@ -130,12 +149,25 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(unblockDomain(domain)); }, - onAddToList(account){ + onAddToList (account) { dispatch(openModal('LIST_ADDER', { accountId: account.get('id'), })); }, + onChangeLanguages (account) { + dispatch(openModal('SUBSCRIBED_LANGUAGES', { + accountId: account.get('id'), + })); + }, + + onOpenAvatar (account) { + dispatch(openModal('IMAGE', { + src: account.get('avatar'), + alt: account.get('acct'), + })); + }, + }); export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header)); diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 68d558e66..b735af0ac 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -17,19 +17,22 @@ import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import TimelineHint from 'flavours/glitch/components/timeline_hint'; import LimitedAccountHint from './components/limited_account_hint'; import { getAccountHidden } from 'flavours/glitch/selectors'; +import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; +import { fetchFeaturedTags } from '../../actions/featured_tags'; const emptyList = ImmutableList(); -const mapStateToProps = (state, { params: { acct, id }, withReplies = false }) => { - const accountId = id || state.getIn(['accounts_map', acct]); +const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => { + const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); if (!accountId) { return { isLoading: true, + statusIds: emptyList, }; } - const path = withReplies ? `${accountId}:with_replies` : accountId; + const path = withReplies ? `${accountId}:with_replies` : `${accountId}${tagged ? `:${tagged}` : ''}`; return { accountId, @@ -37,7 +40,7 @@ const mapStateToProps = (state, { params: { acct, id }, withReplies = false }) = remoteUrl: state.getIn(['accounts', accountId, 'url']), isAccount: !!state.getIn(['accounts', accountId]), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), - featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), + featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, 'items'], ImmutableList()), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), suspended: state.getIn(['accounts', accountId, 'suspended'], false), @@ -60,6 +63,7 @@ class AccountTimeline extends ImmutablePureComponent { params: PropTypes.shape({ acct: PropTypes.string, id: PropTypes.string, + tagged: PropTypes.string, }).isRequired, accountId: PropTypes.string, dispatch: PropTypes.func.isRequired, @@ -77,14 +81,16 @@ class AccountTimeline extends ImmutablePureComponent { }; _load () { - const { accountId, withReplies, dispatch } = this.props; + const { accountId, withReplies, params: { tagged }, dispatch } = this.props; dispatch(fetchAccount(accountId)); if (!withReplies) { - dispatch(expandAccountFeaturedTimeline(accountId)); + dispatch(expandAccountFeaturedTimeline(accountId, { tagged })); } - dispatch(expandAccountTimeline(accountId, { withReplies })); + + dispatch(fetchFeaturedTags(accountId)); + dispatch(expandAccountTimeline(accountId, { withReplies, tagged })); } componentDidMount () { @@ -98,12 +104,17 @@ class AccountTimeline extends ImmutablePureComponent { } componentDidUpdate (prevProps) { - const { params: { acct }, accountId, dispatch } = this.props; + const { params: { acct, tagged }, accountId, withReplies, dispatch } = this.props; if (prevProps.accountId !== accountId && accountId) { this._load(); } else if (prevProps.params.acct !== acct) { dispatch(lookupAccount(acct)); + } else if (prevProps.params.tagged !== tagged) { + if (!withReplies) { + dispatch(expandAccountFeaturedTimeline(accountId, { tagged })); + } + dispatch(expandAccountTimeline(accountId, { withReplies, tagged })); } } @@ -126,7 +137,7 @@ class AccountTimeline extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies })); + this.props.dispatch(expandAccountTimeline(this.props.accountId, { maxId, withReplies: this.props.withReplies, tagged: this.props.params.tagged })); } setRef = c => { @@ -136,19 +147,17 @@ class AccountTimeline extends ImmutablePureComponent { render () { const { accountId, statusIds, featuredStatusIds, isLoading, hasMore, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props; - if (!isAccount) { + if (isLoading && statusIds.isEmpty()) { return ( <Column> - <ColumnBackButton multiColumn={multiColumn} /> - <MissingIndicator /> + <LoadingIndicator /> </Column> ); - } - - if (!statusIds && isLoading) { + } else if (!isLoading && !isAccount) { return ( <Column> - <LoadingIndicator /> + <ColumnBackButton multiColumn={multiColumn} /> + <MissingIndicator /> </Column> ); } @@ -174,7 +183,7 @@ class AccountTimeline extends ImmutablePureComponent { <ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} /> <StatusList - prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} />} + prepend={<HeaderContainer accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />} alwaysPrepend append={remoteMessage} scrollKey='account_timeline' diff --git a/app/javascript/flavours/glitch/features/audio/index.js b/app/javascript/flavours/glitch/features/audio/index.js index 8fa64342e..014a0a213 100644 --- a/app/javascript/flavours/glitch/features/audio/index.js +++ b/app/javascript/flavours/glitch/features/audio/index.js @@ -8,7 +8,7 @@ import { throttle } from 'lodash'; import { getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video'; import { debounce } from 'lodash'; import Visualizer from './visualizer'; -import { displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; +import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import Blurhash from 'flavours/glitch/components/blurhash'; import { is } from 'immutable'; diff --git a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js index ada8fb068..8978ac5fc 100644 --- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js +++ b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.js @@ -1,15 +1,16 @@ -import React from 'react'; -import { connect } from 'react-redux'; +import { debounce } from 'lodash'; import PropTypes from 'prop-types'; +import React from 'react'; +import { Helmet } from 'react-helmet'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { debounce } from 'lodash'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { connect } from 'react-redux'; import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks'; -import Column from 'flavours/glitch/features/ui/components/column'; -import ColumnHeader from 'flavours/glitch/components/column_header'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; +import ColumnHeader from 'flavours/glitch/components/column_header'; import StatusList from 'flavours/glitch/components/status_list'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import Column from 'flavours/glitch/features/ui/components/column'; const messages = defineMessages({ heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, @@ -95,6 +96,11 @@ class Bookmarks extends ImmutablePureComponent { emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> + + <Helmet> + <title>{intl.formatMessage(messages.heading)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js b/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js new file mode 100644 index 000000000..cb91636cb --- /dev/null +++ b/app/javascript/flavours/glitch/features/closed_registrations_modal/index.js @@ -0,0 +1,75 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { domain } from 'flavours/glitch/initial_state'; +import { fetchServer } from 'flavours/glitch/actions/server'; + +const mapStateToProps = state => ({ + message: state.getIn(['server', 'server', 'registrations', 'message']), +}); + +export default @connect(mapStateToProps) +class ClosedRegistrationsModal extends ImmutablePureComponent { + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchServer()); + } + + render () { + let closedRegistrationsMessage; + + if (this.props.message) { + closedRegistrationsMessage = ( + <p + className='prose' + dangerouslySetInnerHTML={{ __html: this.props.message }} + /> + ); + } else { + closedRegistrationsMessage = ( + <p className='prose'> + <FormattedMessage + id='closed_registrations_modal.description' + defaultMessage='Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.' + values={{ domain: <strong>{domain}</strong> }} + /> + </p> + ); + } + + return ( + <div className='modal-root__modal interaction-modal'> + <div className='interaction-modal__lead'> + <h3><FormattedMessage id='closed_registrations_modal.title' defaultMessage='Signing up on Mastodon' /></h3> + <p> + <FormattedMessage + id='closed_registrations_modal.preamble' + defaultMessage='Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!' + /> + </p> + </div> + + <div className='interaction-modal__choices'> + <div className='interaction-modal__choices__choice'> + <h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3> + {closedRegistrationsMessage} + </div> + + <div className='interaction-modal__choices__choice'> + <h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3> + <p className='prose'> + <FormattedMessage + id='closed_registrations.other_server_instructions' + defaultMessage='Since Mastodon is decentralized, you can create an account on another server and still interact with this one.' + /> + </p> + <a href='https://joinmastodon.org/servers' className='button button--block'><FormattedMessage id='closed_registrations_modal.find_another_server' defaultMessage='Find another server' /></a> + </div> + </div> + </div> + ); + } + +}; diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js index 7341f9702..67bf54875 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.js +++ b/app/javascript/flavours/glitch/features/community_timeline/index.js @@ -9,6 +9,9 @@ import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectCommunityStream } from 'flavours/glitch/actions/streaming'; +import { Helmet } from 'react-helmet'; +import { domain } from 'flavours/glitch/initial_state'; +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; const messages = defineMessages({ title: { id: 'column.community', defaultMessage: 'Local timeline' }, @@ -19,11 +22,13 @@ const mapStateToProps = (state, { columnId }) => { const columns = state.getIn(['settings', 'columns']); const index = columns.findIndex(c => c.get('uuid') === uuid); const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']); + const regex = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'regex', 'body']) : state.getIn(['settings', 'community', 'regex', 'body']); const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]); return { hasUnread: !!timelineState && timelineState.get('unread') > 0, onlyMedia, + regex, }; }; @@ -37,6 +42,7 @@ class CommunityTimeline extends React.PureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { @@ -46,6 +52,7 @@ class CommunityTimeline extends React.PureComponent { hasUnread: PropTypes.bool, multiColumn: PropTypes.bool, onlyMedia: PropTypes.bool, + regex: PropTypes.string, }; handlePin = () => { @@ -69,18 +76,30 @@ class CommunityTimeline extends React.PureComponent { componentDidMount () { const { dispatch, onlyMedia } = this.props; + const { signedIn } = this.context.identity; dispatch(expandCommunityTimeline({ onlyMedia })); - this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); + + if (signedIn) { + this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); + } } componentDidUpdate (prevProps) { + const { signedIn } = this.context.identity; + if (prevProps.onlyMedia !== this.props.onlyMedia) { const { dispatch, onlyMedia } = this.props; - this.disconnect(); + if (this.disconnect) { + this.disconnect(); + } + dispatch(expandCommunityTimeline({ onlyMedia })); - this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); + + if (signedIn) { + this.disconnect = dispatch(connectCommunityStream({ onlyMedia })); + } } } @@ -120,6 +139,10 @@ class CommunityTimeline extends React.PureComponent { <ColumnSettingsContainer columnId={columnId} /> </ColumnHeader> + <DismissableBanner id='community_timeline'> + <FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} /> + </DismissableBanner> + <StatusListContainer trackScroll={!pinned} scrollKey={`community_timeline-${columnId}`} @@ -127,7 +150,13 @@ class CommunityTimeline extends React.PureComponent { onLoadMore={this.handleLoadMore} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} bindToDocument={!multiColumn} + regex={this.props.regex} /> + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index b03bc34b8..516648f4b 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -5,17 +5,17 @@ import ReplyIndicatorContainer from '../containers/reply_indicator_container'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import AutosuggestInput from '../../../components/autosuggest_input'; import { defineMessages, injectIntl } from 'react-intl'; -import EmojiPicker from 'flavours/glitch/features/emoji_picker'; +import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import PollFormContainer from '../containers/poll_form_container'; import UploadFormContainer from '../containers/upload_form_container'; import WarningContainer from '../containers/warning_container'; -import { isMobile } from 'flavours/glitch/util/is_mobile'; +import { isMobile } from 'flavours/glitch/is_mobile'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { countableText } from 'flavours/glitch/util/counter'; +import { countableText } from '../util/counter'; import OptionsContainer from '../containers/options_container'; import Publisher from './publisher'; import TextareaIcons from './textarea_icons'; -import { maxChars } from 'flavours/glitch/util/initial_state'; +import { maxChars } from 'flavours/glitch/initial_state'; import CharacterCounter from './character_counter'; import { length } from 'stringz'; @@ -143,7 +143,7 @@ class ComposeForm extends ImmutablePureComponent { }; // Inserts an emoji at the caret. - handleEmoji = (data) => { + handleEmojiPick = (data) => { const { textarea: { selectionStart } } = this; const { onPickEmoji } = this.props; if (onPickEmoji) { @@ -275,7 +275,7 @@ class ComposeForm extends ImmutablePureComponent { render () { const { - handleEmoji, + handleEmojiPick, handleSecondarySubmit, handleSelect, handleSubmit, @@ -305,12 +305,12 @@ class ComposeForm extends ImmutablePureComponent { const countText = this.getFulltextForCharacterCounting(); return ( - <div className='composer'> + <div className='compose-form'> <WarningContainer /> <ReplyIndicatorContainer /> - <div className={`composer--spoiler ${spoiler ? 'composer--spoiler--visible' : ''}`} ref={this.setRef}> + <div className={`spoiler-input ${spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef}> <AutosuggestInput placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={spoilerText} @@ -344,7 +344,7 @@ class ComposeForm extends ImmutablePureComponent { onPaste={onPaste} autoFocus={!showSearch && !isMobile(window.innerWidth, layout)} > - <EmojiPicker onPickEmoji={handleEmoji} /> + <EmojiPickerDropdown onPickEmoji={handleEmojiPick} /> <TextareaIcons advancedOptions={advancedOptions} /> <div className='compose-form__modifiers'> <UploadFormContainer /> @@ -352,7 +352,7 @@ class ComposeForm extends ImmutablePureComponent { </div> </AutosuggestTextarea> - <div className='composer--options-wrapper'> + <div className='compose-form__buttons-wrapper'> <OptionsContainer advancedOptions={advancedOptions} disabled={isSubmitting} @@ -364,7 +364,7 @@ class ComposeForm extends ImmutablePureComponent { sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)} spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} /> - <div className='compose--counter-wrapper'> + <div className='character-counter__wrapper'> <CharacterCounter text={countText} max={maxChars} /> </div> </div> diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 9f70d6b79..6b6d3de94 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -9,14 +9,13 @@ import IconButton from 'flavours/glitch/components/icon_button'; import DropdownMenu from './dropdown_menu'; // Utils. -import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; +import { isUserTouching } from 'flavours/glitch/is_mobile'; +import { assignHandlers } from 'flavours/glitch/utils/react_helpers'; // The component. export default class ComposerOptionsDropdown extends React.PureComponent { static propTypes = { - active: PropTypes.bool, disabled: PropTypes.bool, icon: PropTypes.string, items: PropTypes.arrayOf(PropTypes.shape({ @@ -162,7 +161,6 @@ export default class ComposerOptionsDropdown extends React.PureComponent { // Rendering. render () { const { - active, disabled, title, icon, @@ -174,35 +172,34 @@ export default class ComposerOptionsDropdown extends React.PureComponent { closeOnChange, } = this.props; const { open, placement } = this.state; - const computedClass = classNames('composer--options--dropdown', { - active, - open, - top: placement === 'top', - }); - // The result. + const active = value && items.findIndex(({ name }) => name === value) === (placement === 'bottom' ? 0 : (items.length - 1)); + return ( <div - className={computedClass} + className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown} > - <IconButton - active={open || active} - className='value' - disabled={disabled} - icon={icon} - inverted - onClick={this.handleToggle} - onMouseDown={this.handleMouseDown} - onKeyDown={this.handleButtonKeyDown} - onKeyPress={this.handleKeyPress} - size={18} - style={{ - height: null, - lineHeight: '27px', - }} - title={title} - /> + <div className={classNames('privacy-dropdown__value', { active })}> + <IconButton + active={open} + className='privacy-dropdown__value-icon' + disabled={disabled} + icon={icon} + inverted + onClick={this.handleToggle} + onMouseDown={this.handleMouseDown} + onKeyDown={this.handleButtonKeyDown} + onKeyPress={this.handleKeyPress} + size={18} + style={{ + height: null, + lineHeight: '27px', + }} + title={title} + /> + </div> + <Overlay containerPadding={20} placement={placement} diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js index 0649fe1ca..09e8fc35a 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js @@ -9,9 +9,9 @@ import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; // Utils. -import { withPassive } from 'flavours/glitch/util/dom_helpers'; -import Motion from 'flavours/glitch/util/optional_motion'; -import { assignHandlers } from 'flavours/glitch/util/react_helpers'; +import { withPassive } from 'flavours/glitch/utils/dom_helpers'; +import Motion from '../../ui/util/optional_motion'; +import { assignHandlers } from 'flavours/glitch/utils/react_helpers'; // The spring to use with our motion. const springMotion = spring(1, { @@ -156,7 +156,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent const active = (name === (this.props.value || this.state.value)); - const computedClass = classNames('composer--options--dropdown--content--item', { active }); + const computedClass = classNames('privacy-dropdown__option', { active }); let contents = this.props.renderItemContents && this.props.renderItemContents(item, i); @@ -165,7 +165,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent <React.Fragment> {icon && <Icon className='icon' fixedWidth id={icon} />} - <div className='content'> + <div className='privacy-dropdown__option__content'> <strong>{text}</strong> {meta} </div> @@ -218,7 +218,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent // size will be used to determine the coordinate of the menu by // react-overlays <div - className='composer--options--dropdown--content' + className='privacy-dropdown__dropdown' ref={this.handleRef} role='listbox' style={{ diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js index 5de9fe107..546d398a0 100644 --- a/app/javascript/flavours/glitch/features/emoji_picker/index.js +++ b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js @@ -1,19 +1,14 @@ -import { connect } from 'react-redux'; -import { changeSetting } from 'flavours/glitch/actions/settings'; -import { createSelector } from 'reselect'; -import { Map as ImmutableMap } from 'immutable'; -import { useEmoji } from 'flavours/glitch/actions/emojis'; import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components'; +import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; import Overlay from 'react-overlays/lib/Overlay'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { supportsPassiveEvents } from 'detect-passive-events'; -import { buildCustomEmojis, categoriesFromEmojis } from 'flavours/glitch/util/emoji'; -import { useSystemEmojiFont } from 'flavours/glitch/util/initial_state'; -import { assetHost } from 'flavours/glitch/util/config'; +import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji'; +import { useSystemEmojiFont } from 'flavours/glitch/initial_state'; +import { assetHost } from 'flavours/glitch/utils/config'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -31,80 +26,6 @@ const messages = defineMessages({ flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, }); -const perLine = 8; -const lines = 2; - -const DEFAULTS = [ - '+1', - 'grinning', - 'kissing_heart', - 'heart_eyes', - 'laughing', - 'stuck_out_tongue_winking_eye', - 'sweat_smile', - 'joy', - 'yum', - 'disappointed', - 'thinking_face', - 'weary', - 'sob', - 'sunglasses', - 'heart', - 'ok_hand', -]; - -const getFrequentlyUsedEmojis = createSelector([ - state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()), -], emojiCounters => { - let emojis = emojiCounters - .keySeq() - .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b)) - .reverse() - .slice(0, perLine * lines) - .toArray(); - - if (emojis.length < DEFAULTS.length) { - emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length)); - } - - return emojis; -}); - -const getCustomEmojis = createSelector([ - state => state.get('custom_emojis'), -], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => { - const aShort = a.get('shortcode').toLowerCase(); - const bShort = b.get('shortcode').toLowerCase(); - - if (aShort < bShort) { - return -1; - } else if (aShort > bShort ) { - return 1; - } else { - return 0; - } -})); - -const mapStateToProps = state => ({ - custom_emojis: getCustomEmojis(state), - skinTone: state.getIn(['settings', 'skinTone']), - frequentlyUsedEmojis: getFrequentlyUsedEmojis(state), -}); - -const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ - onSkinTone: skinTone => { - dispatch(changeSetting(['skinTone'], skinTone)); - }, - - onPickEmoji: emoji => { - dispatch(useEmoji(emoji)); - - if (onPickEmoji) { - onPickEmoji(emoji); - } - }, -}); - let EmojiPicker, Emoji; // load asynchronously const listenerOptions = supportsPassiveEvents ? { passive: true } : false; @@ -389,8 +310,7 @@ class EmojiPickerMenu extends React.PureComponent { } -export default @connect(mapStateToProps, mapDispatchToProps) -@injectIntl +export default @injectIntl class EmojiPickerDropdown extends React.PureComponent { static propTypes = { @@ -425,7 +345,7 @@ class EmojiPickerDropdown extends React.PureComponent { this.setState({ loading: false }); }).catch(() => { - this.setState({ loading: false }); + this.setState({ loading: false, active: false }); }); } diff --git a/app/javascript/flavours/glitch/features/compose/components/header.js b/app/javascript/flavours/glitch/features/compose/components/header.js index 95add2027..7ecb573ab 100644 --- a/app/javascript/flavours/glitch/features/compose/components/header.js +++ b/app/javascript/flavours/glitch/features/compose/components/header.js @@ -10,8 +10,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Icon from 'flavours/glitch/components/icon'; // Utils. -import { conditionalRender } from 'flavours/glitch/util/react_helpers'; -import { signOutLink } from 'flavours/glitch/util/backend_links'; +import { conditionalRender } from 'flavours/glitch/utils/react_helpers'; +import { signOutLink } from 'flavours/glitch/utils/backend_links'; // Messages. const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js index 035b0c0c3..a3256aa9b 100644 --- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.js @@ -3,12 +3,12 @@ import PropTypes from 'prop-types'; import { injectIntl, defineMessages } from 'react-intl'; import TextIconButton from './text_icon_button'; import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from 'flavours/glitch/features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { supportsPassiveEvents } from 'detect-passive-events'; import classNames from 'classnames'; -import { languages as preloadedLanguages } from 'flavours/glitch/util/initial_state'; -import { loupeIcon, deleteIcon } from 'flavours/glitch/util/icons'; +import { languages as preloadedLanguages } from 'flavours/glitch/initial_state'; +import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons'; import fuzzysort from 'fuzzysort'; const messages = defineMessages({ @@ -51,6 +51,15 @@ class LanguageDropdownMenu extends React.PureComponent { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); this.setState({ mounted: true }); + + // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need + // to wait for a frame before focusing + requestAnimationFrame(() => { + if (this.node) { + const element = this.node.querySelector('input[type="search"]'); + if (element) element.focus(); + } + }); } componentWillUnmount () { @@ -226,7 +235,7 @@ class LanguageDropdownMenu extends React.PureComponent { // react-overlays <div className={`language-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}> <div className='emoji-mart-search'> - <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} autoFocus /> + <input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} /> <button className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}>{!isSearching ? loupeIcon : deleteIcon}</button> </div> diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js index 595ca5512..ba73ed553 100644 --- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js +++ b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js @@ -4,7 +4,7 @@ import Avatar from 'flavours/glitch/components/avatar'; import Permalink from 'flavours/glitch/components/permalink'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { profileLink } from 'flavours/glitch/util/backend_links'; +import { profileLink } from 'flavours/glitch/utils/backend_links'; export default class NavigationBar extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js index f005dbdd1..47bd9b056 100644 --- a/app/javascript/flavours/glitch/features/compose/components/options.js +++ b/app/javascript/flavours/glitch/features/compose/components/options.js @@ -16,8 +16,8 @@ import LanguageDropdown from '../containers/language_dropdown_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; // Utils. -import Motion from 'flavours/glitch/util/optional_motion'; -import { pollLimits } from 'flavours/glitch/util/initial_state'; +import Motion from '../../ui/util/optional_motion'; +import { pollLimits } from 'flavours/glitch/initial_state'; // Messages. const messages = defineMessages({ @@ -228,7 +228,7 @@ class ComposerOptions extends ImmutablePureComponent { // The result. return ( - <div className='composer--options'> + <div className='compose-form__buttons'> <input accept={acceptContentTypes} disabled={disabled || !allowMedia} @@ -309,7 +309,6 @@ class ComposerOptions extends ImmutablePureComponent { )} <LanguageDropdown /> <Dropdown - active={advancedOptions && advancedOptions.some(value => !!value)} disabled={disabled || isEditing} icon='ellipsis-h' items={advancedOptions ? [ diff --git a/app/javascript/flavours/glitch/features/compose/components/poll_form.js b/app/javascript/flavours/glitch/features/compose/components/poll_form.js index e4b5104f3..d5edccff3 100644 --- a/app/javascript/flavours/glitch/features/compose/components/poll_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/poll_form.js @@ -7,7 +7,7 @@ import IconButton from 'flavours/glitch/components/icon_button'; import Icon from 'flavours/glitch/components/icon'; import AutosuggestInput from 'flavours/glitch/components/autosuggest_input'; import classNames from 'classnames'; -import { pollLimits } from 'flavours/glitch/util/initial_state'; +import { pollLimits } from 'flavours/glitch/initial_state'; const messages = defineMessages({ option_placeholder: { id: 'compose_form.poll.option_placeholder', defaultMessage: 'Choice {number}' }, diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.js b/app/javascript/flavours/glitch/features/compose/components/publisher.js index e2498bcad..9d53b7ee3 100644 --- a/app/javascript/flavours/glitch/features/compose/components/publisher.js +++ b/app/javascript/flavours/glitch/features/compose/components/publisher.js @@ -11,7 +11,7 @@ import Button from 'flavours/glitch/components/button'; import Icon from 'flavours/glitch/components/icon'; // Utils. -import { maxChars } from 'flavours/glitch/util/initial_state'; +import { maxChars } from 'flavours/glitch/initial_state'; // Messages. const messages = defineMessages({ @@ -48,7 +48,7 @@ class Publisher extends ImmutablePureComponent { const { countText, disabled, intl, onSecondarySubmit, privacy, sideArm, isEditing } = this.props; const diff = maxChars - length(countText || ''); - const computedClass = classNames('composer--publisher', { + const computedClass = classNames('compose-form__publish', { disabled: disabled, over: diff < 0, }); @@ -72,22 +72,26 @@ class Publisher extends ImmutablePureComponent { return ( <div className={computedClass}> {sideArm && !isEditing && sideArm !== 'none' ? ( + <div className='compose-form__publish-button-wrapper'> + <Button + className='side_arm' + disabled={disabled} + onClick={onSecondarySubmit} + style={{ padding: null }} + text={<Icon id={privacyIcons[sideArm]} />} + title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`} + /> + </div> + ) : null} + <div className='compose-form__publish-button-wrapper'> <Button - className='side_arm' + className='primary' + text={publishText} + title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`} + onClick={this.handleSubmit} disabled={disabled} - onClick={onSecondarySubmit} - style={{ padding: null }} - text={<Icon id={privacyIcons[sideArm]} />} - title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${sideArm}.short` })}`} /> - ) : null} - <Button - className='primary' - text={publishText} - title={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${privacy}.short` })}`} - onClick={this.handleSubmit} - disabled={disabled} - /> + </div> </div> ); }; diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js index 37ae9cab9..7ad9e2b64 100644 --- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js +++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js @@ -49,10 +49,10 @@ class ReplyIndicator extends ImmutablePureComponent { // The result. return ( - <article className='composer--reply'> - <header> + <article className='reply-indicator'> + <header className='reply-indicator__header'> <IconButton - className='cancel' + className='reply-indicator__cancel' icon='times' onClick={this.handleClick} title={intl.formatMessage(messages.cancel)} @@ -66,7 +66,7 @@ class ReplyIndicator extends ImmutablePureComponent { )} </header> <div - className='content translate' + className='reply-indicator__content translate' dangerouslySetInnerHTML={{ __html: content || '' }} /> {attachments.size > 0 && ( diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js index 12d839637..326fe5b70 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.js +++ b/app/javascript/flavours/glitch/features/compose/components/search.js @@ -15,12 +15,13 @@ import Overlay from 'react-overlays/lib/Overlay'; import Icon from 'flavours/glitch/components/icon'; // Utils. -import { focusRoot } from 'flavours/glitch/util/dom_helpers'; -import { searchEnabled } from 'flavours/glitch/util/initial_state'; -import Motion from 'flavours/glitch/util/optional_motion'; +import { focusRoot } from 'flavours/glitch/utils/dom_helpers'; +import { searchEnabled } from 'flavours/glitch/initial_state'; +import Motion from '../../ui/util/optional_motion'; const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, + placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' }, }); class SearchPopout extends React.PureComponent { @@ -62,6 +63,7 @@ class Search extends React.PureComponent { static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, }; static propTypes = { @@ -137,6 +139,7 @@ class Search extends React.PureComponent { render () { const { intl, value, submitted } = this.props; const { expanded } = this.state; + const { signedIn } = this.context.identity; const hasValue = value.length > 0 || submitted; return ( @@ -147,7 +150,7 @@ class Search extends React.PureComponent { ref={this.setRef} className='search__input' type='text' - placeholder={intl.formatMessage(messages.placeholder)} + placeholder={intl.formatMessage(signedIn ? messages.placeholderSignedIn : messages.placeholder)} value={value || ''} onChange={this.handleChange} onKeyUp={this.handleKeyUp} diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js index e82ee2ca2..c2178702c 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.js +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.js @@ -7,7 +7,7 @@ import StatusContainer from 'flavours/glitch/containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; import Icon from 'flavours/glitch/components/icon'; -import { searchEnabled } from 'flavours/glitch/util/initial_state'; +import { searchEnabled } from 'flavours/glitch/initial_state'; import LoadMore from 'flavours/glitch/components/load_more'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js index b875fb15e..25c2443b1 100644 --- a/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js +++ b/app/javascript/flavours/glitch/features/compose/components/textarea_icons.js @@ -38,7 +38,7 @@ class TextareaIcons extends ImmutablePureComponent { render () { const { advancedOptions, intl } = this.props; return ( - <div className='composer--textarea--icons'> + <div className='compose-form__textarea-icons'> {advancedOptions ? iconMap.map( ([key, icon, message]) => advancedOptions.get(key) ? ( <span diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js index 963b95c87..94ac6c499 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload.js @@ -1,12 +1,12 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; import Icon from 'flavours/glitch/components/icon'; -import { isUserTouching } from 'flavours/glitch/util/is_mobile'; +import { isUserTouching } from 'flavours/glitch/is_mobile'; export default class Upload extends ImmutablePureComponent { @@ -18,7 +18,7 @@ export default class Upload extends ImmutablePureComponent { media: ImmutablePropTypes.map.isRequired, onUndo: PropTypes.func.isRequired, onOpenFocalPoint: PropTypes.func.isRequired, - isEditingStatus: PropTypes.func.isRequired, + isEditingStatus: PropTypes.bool.isRequired, }; handleUndoClick = e => { @@ -39,17 +39,17 @@ export default class Upload extends ImmutablePureComponent { const y = ((focusY / -2) + .5) * 100; return ( - <div className='composer--upload_form--item' tabIndex='0' role='button'> + <div className='compose-form__upload' tabIndex='0' role='button'> <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='composer--upload_form--actions'> + <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}> + <div className='compose-form__upload__actions'> <button className='icon-button' onClick={this.handleUndoClick}><Icon id='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button> {!isEditingStatus && (<button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button>)} </div> {(media.get('description') || '').length === 0 && ( - <div className='composer--upload_form--item__warning'> + <div className='compose-form__upload__warning'> <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='info-circle' /> <FormattedMessage id='upload_form.description_missing' defaultMessage='No description added' /></button> </div> )} diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js index 43039c674..7ebbac963 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js @@ -4,7 +4,6 @@ import UploadProgressContainer from '../containers/upload_progress_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import UploadContainer from '../containers/upload_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; -import { FormattedMessage } from 'react-intl'; export default class UploadForm extends ImmutablePureComponent { static propTypes = { @@ -15,11 +14,11 @@ export default class UploadForm extends ImmutablePureComponent { const { mediaIds } = this.props; return ( - <div className='composer--upload_form'> - <UploadProgressContainer icon='upload' message={<FormattedMessage id='upload_progress.label' defaultMessage='Uploadingโฆ' />} /> + <div className='compose-form__upload-wrapper'> + <UploadProgressContainer /> {mediaIds.size > 0 && ( - <div className='content'> + <div className='compose-form__uploads-wrapper'> {mediaIds.map(id => ( <UploadContainer id={id} key={id} /> ))} diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js index 493bb9ca5..39ac31053 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js @@ -1,37 +1,46 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import Icon from 'flavours/glitch/components/icon'; +import { FormattedMessage } from 'react-intl'; export default class UploadProgress extends React.PureComponent { static propTypes = { active: PropTypes.bool, progress: PropTypes.number, - icon: PropTypes.string.isRequired, - message: PropTypes.node.isRequired, + isProcessing: PropTypes.bool, }; render () { - const { active, progress, icon, message } = this.props; + const { active, progress, isProcessing } = this.props; if (!active) { return null; } + let message; + + if (isProcessing) { + message = <FormattedMessage id='upload_progress.processing' defaultMessage='Processingโฆ' />; + } else { + message = <FormattedMessage id='upload_progress.label' defaultMessage='Uploadingโฆ' />; + } + return ( - <div className='composer--upload_form--progress'> - <Icon id={icon} /> + <div className='upload-progress'> + <div className='upload-progress__icon'> + <Icon id='upload' /> + </div> - <div className='message'> + <div className='upload-progress__message'> {message} - <div className='backdrop'> + <div className='upload-progress__backdrop'> <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}> {({ width }) => - (<div className='tracker' style={{ width: `${width}%` }} - />) + <div className='upload-progress__tracker' style={{ width: `${width}%` }} /> } </Motion> </div> diff --git a/app/javascript/flavours/glitch/features/compose/components/warning.js b/app/javascript/flavours/glitch/features/compose/components/warning.js index 6ee3640bc..803b7f86a 100644 --- a/app/javascript/flavours/glitch/features/compose/components/warning.js +++ b/app/javascript/flavours/glitch/features/compose/components/warning.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; export default class Warning extends React.PureComponent { @@ -15,7 +15,7 @@ export default class Warning extends React.PureComponent { return ( <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> {({ opacity, scaleX, scaleY }) => ( - <div className='composer--warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}> + <div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}> {message} </div> )} diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js index a037bbbcc..d12c98c01 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js @@ -18,7 +18,7 @@ import { } from 'flavours/glitch/actions/modal'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; -import { privacyPreference } from 'flavours/glitch/util/privacy_preference'; +import { privacyPreference } from 'flavours/glitch/utils/privacy_preference'; const messages = defineMessages({ missingDescriptionMessage: { id: 'confirmations.missing_media_description.message', diff --git a/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js new file mode 100644 index 000000000..66d51947a --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js @@ -0,0 +1,83 @@ +import { connect } from 'react-redux'; +import EmojiPickerDropdown from '../components/emoji_picker_dropdown'; +import { changeSetting } from 'flavours/glitch/actions/settings'; +import { createSelector } from 'reselect'; +import { Map as ImmutableMap } from 'immutable'; +import { useEmoji } from 'flavours/glitch/actions/emojis'; + +const perLine = 8; +const lines = 2; + +const DEFAULTS = [ + '+1', + 'grinning', + 'kissing_heart', + 'heart_eyes', + 'laughing', + 'stuck_out_tongue_winking_eye', + 'sweat_smile', + 'joy', + 'yum', + 'disappointed', + 'thinking_face', + 'weary', + 'sob', + 'sunglasses', + 'heart', + 'ok_hand', +]; + +const getFrequentlyUsedEmojis = createSelector([ + state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()), +], emojiCounters => { + let emojis = emojiCounters + .keySeq() + .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b)) + .reverse() + .slice(0, perLine * lines) + .toArray(); + + if (emojis.length < DEFAULTS.length) { + let uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji)); + emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length)); + } + + return emojis; +}); + +const getCustomEmojis = createSelector([ + state => state.get('custom_emojis'), +], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => { + const aShort = a.get('shortcode').toLowerCase(); + const bShort = b.get('shortcode').toLowerCase(); + + if (aShort < bShort) { + return -1; + } else if (aShort > bShort ) { + return 1; + } else { + return 0; + } +})); + +const mapStateToProps = state => ({ + custom_emojis: getCustomEmojis(state), + skinTone: state.getIn(['settings', 'skinTone']), + frequentlyUsedEmojis: getFrequentlyUsedEmojis(state), +}); + +const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ + onSkinTone: skinTone => { + dispatch(changeSetting(['skinTone'], skinTone)); + }, + + onPickEmoji: emoji => { + dispatch(useEmoji(emoji)); + + if (onPickEmoji) { + onPickEmoji(emoji); + } + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(EmojiPickerDropdown); diff --git a/app/javascript/flavours/glitch/features/compose/containers/header_container.js b/app/javascript/flavours/glitch/features/compose/containers/header_container.js index 2f0da48c8..e1ce19fb0 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/header_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/header_container.js @@ -2,7 +2,7 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { connect } from 'react-redux'; import { defineMessages, injectIntl } from 'react-intl'; import Header from '../components/header'; -import { logOut } from 'flavours/glitch/util/log_out'; +import { logOut } from 'flavours/glitch/utils/log_out'; const messages = defineMessages({ logoutMessage: { id: 'confirmations.logout.message', defaultMessage: 'Are you sure you want to log out?' }, diff --git a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js index eb630ffbb..0e1400261 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import NavigationBar from '../components/navigation_bar'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; const mapStateToProps = state => { return { diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js index 0cfee96da..b18c76a43 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js @@ -4,6 +4,7 @@ import UploadProgress from '../components/upload_progress'; const mapStateToProps = state => ({ active: state.getIn(['compose', 'is_uploading']), progress: state.getIn(['compose', 'progress']), + isProcessing: state.getIn(['compose', 'is_processing']), }); export default connect(mapStateToProps)(UploadProgress); diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js index 5fccaa442..b2ed40b82 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js @@ -3,8 +3,8 @@ import { connect } from 'react-redux'; import Warning from '../components/warning'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { me } from 'flavours/glitch/util/initial_state'; -import { profileLink, termsLink } from 'flavours/glitch/util/backend_links'; +import { me } from 'flavours/glitch/initial_state'; +import { profileLink, termsLink } from 'flavours/glitch/utils/backend_links'; const buildHashtagRE = () => { try { diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js index b9a8e0245..8ca378672 100644 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ b/app/javascript/flavours/glitch/features/compose/index.js @@ -8,12 +8,14 @@ import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose'; import { injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; import SearchContainer from './containers/search_container'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../ui/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 { me, mascot } from 'flavours/glitch/initial_state'; import { cycleElefriendCompose } from 'flavours/glitch/actions/compose'; import HeaderContainer from './containers/header_container'; +import Column from 'flavours/glitch/components/column'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' }, @@ -21,7 +23,7 @@ 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, + showSearch: ownProps.multiColumn ? state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) : false, }); const mapDispatchToProps = (dispatch, { intl }) => ({ @@ -44,7 +46,6 @@ 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, @@ -53,19 +54,11 @@ class Compose extends React.PureComponent { }; componentDidMount () { - const { isSearchPage } = this.props; - - if (!isSearchPage) { - this.props.onMount(); - } + this.props.onMount(); } componentWillUnmount () { - const { isSearchPage } = this.props; - - if (!isSearchPage) { - this.props.onUnmount(); - } + this.props.onUnmount(); } render () { @@ -74,37 +67,49 @@ class Compose extends React.PureComponent { intl, multiColumn, onClickElefriend, - isSearchPage, showSearch, } = this.props; const computedClass = classNames('drawer', `mbstobon-${elefriend}`); - return ( - <div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}> - {multiColumn && <HeaderContainer />} + if (multiColumn) { + return ( + <div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}> + <HeaderContainer /> - {(multiColumn || isSearchPage) && <SearchContainer />} + {multiColumn && <SearchContainer />} - <div className='drawer__pager'> - {!isSearchPage && <div className='drawer__inner'> - <NavigationContainer /> + <div className='drawer__pager'> + <div className='drawer__inner'> + <NavigationContainer /> - <ComposeFormContainer /> + <ComposeFormContainer /> - <div className='drawer__inner__mastodon'> - {mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />} + <div className='drawer__inner__mastodon'> + {mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />} + </div> </div> - </div>} - <Motion defaultStyle={{ x: isSearchPage ? 0 : -100 }} style={{ x: spring(showSearch || isSearchPage ? 0 : -100, { stiffness: 210, damping: 20 }) }}> - {({ x }) => ( - <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> - <SearchResultsContainer /> - </div> - )} - </Motion> + <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> + {({ x }) => ( + <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> + <SearchResultsContainer /> + </div> + )} + </Motion> + </div> </div> - </div> + ); + } + + return ( + <Column> + <NavigationContainer /> + <ComposeFormContainer /> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> + </Column> ); } diff --git a/app/javascript/flavours/glitch/util/counter.js b/app/javascript/flavours/glitch/features/compose/util/counter.js index 7aa9e87b1..7aa9e87b1 100644 --- a/app/javascript/flavours/glitch/util/counter.js +++ b/app/javascript/flavours/glitch/features/compose/util/counter.js diff --git a/app/javascript/flavours/glitch/util/url_regex.js b/app/javascript/flavours/glitch/features/compose/util/url_regex.js index 9c2005c53..9c2005c53 100644 --- a/app/javascript/flavours/glitch/util/url_regex.js +++ b/app/javascript/flavours/glitch/features/compose/util/url_regex.js diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js index 7107c9db3..00d9fdcd0 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js @@ -11,7 +11,7 @@ import Permalink from 'flavours/glitch/components/permalink'; import IconButton from 'flavours/glitch/components/icon_button'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import { HotKeys } from 'react-hotkeys'; -import { autoPlayGif } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; import classNames from 'classnames'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/direct_timeline/index.js b/app/javascript/flavours/glitch/features/direct_timeline/index.js index 75ca19d17..d55c63c2b 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/index.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/index.js @@ -1,15 +1,16 @@ +import PropTypes from 'prop-types'; import React from 'react'; +import { Helmet } from 'react-helmet'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; +import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; +import { mountConversations, unmountConversations, expandConversations } from 'flavours/glitch/actions/conversations'; +import { connectDirectStream } from 'flavours/glitch/actions/streaming'; +import { expandDirectTimeline } from 'flavours/glitch/actions/timelines'; import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; -import { expandDirectTimeline } from 'flavours/glitch/actions/timelines'; -import { mountConversations, unmountConversations, expandConversations } from 'flavours/glitch/actions/conversations'; -import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; import ColumnSettingsContainer from './containers/column_settings_container'; -import { connectDirectStream } from 'flavours/glitch/actions/streaming'; import ConversationsListContainer from './containers/conversations_list_container'; const messages = defineMessages({ @@ -143,6 +144,11 @@ class DirectTimeline extends React.PureComponent { </ColumnHeader> {contents} + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js index 6c554336c..ccc3dd3d2 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.js +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js @@ -7,9 +7,10 @@ import { makeGetAccount } from 'flavours/glitch/selectors'; import Avatar from 'flavours/glitch/components/avatar'; import DisplayName from 'flavours/glitch/components/display_name'; import Permalink from 'flavours/glitch/components/permalink'; +import IconButton from 'flavours/glitch/components/icon_button'; import Button from 'flavours/glitch/components/button'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; -import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/initial_state'; import ShortNumber from 'flavours/glitch/components/short_number'; import { followAccount, @@ -23,12 +24,14 @@ import classNames from 'classnames'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, - cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Cancel follow request' }, + cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' }, + cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' }, unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, + dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, }); const makeMapStateToProps = () => { @@ -43,10 +46,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow(account) { - if ( - account.getIn(['relationship', 'following']) || - account.getIn(['relationship', 'requested']) - ) { + if (account.getIn(['relationship', 'following'])) { if (unfollowModal) { dispatch( openModal('CONFIRM', { @@ -64,6 +64,16 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } else { dispatch(unfollowAccount(account.get('id'))); } + } else if (account.getIn(['relationship', 'requested'])) { + if (unfollowModal) { + dispatch(openModal('CONFIRM', { + message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, + confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), + onConfirm: () => dispatch(unfollowAccount(account.get('id'))), + })); + } else { + dispatch(unfollowAccount(account.get('id'))); + } } else { dispatch(followAccount(account.get('id'))); } @@ -94,6 +104,7 @@ class AccountCard extends ImmutablePureComponent { onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, + onDismiss: PropTypes.func, }; handleMouseEnter = ({ currentTarget }) => { @@ -138,6 +149,14 @@ class AccountCard extends ImmutablePureComponent { window.open('/settings/profile', '_blank'); } + handleDismiss = (e) => { + const { account, onDismiss } = this.props; + onDismiss(account.get('id')); + + e.preventDefault(); + e.stopPropagation(); + } + render() { const { account, intl } = this.props; @@ -163,6 +182,8 @@ class AccountCard extends ImmutablePureComponent { <div className='account-card'> <Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='account-card__permalink'> <div className='account-card__header'> + {this.props.onDismiss && <IconButton className='media-modal__close' title={intl.formatMessage(messages.dismissSuggestion)} icon='times' onClick={this.handleDismiss} size={20} />} + <img src={ autoPlayGif ? account.get('header') : account.get('header_static') diff --git a/app/javascript/flavours/glitch/features/directory/index.js b/app/javascript/flavours/glitch/features/directory/index.js index 87d9b3625..94bcd578c 100644 --- a/app/javascript/flavours/glitch/features/directory/index.js +++ b/app/javascript/flavours/glitch/features/directory/index.js @@ -13,6 +13,7 @@ import RadioButton from 'flavours/glitch/components/radio_button'; import LoadMore from 'flavours/glitch/components/load_more'; import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, @@ -165,6 +166,11 @@ class Directory extends React.PureComponent { /> {multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea} + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.js b/app/javascript/flavours/glitch/features/domain_blocks/index.js index acce87d5a..cb0b55c63 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.js +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.js @@ -11,6 +11,7 @@ import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_bloc import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' }, @@ -59,6 +60,7 @@ class Blocks extends ImmutablePureComponent { return ( <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <ColumnBackButtonSlim /> + <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore} @@ -70,6 +72,10 @@ class Blocks extends ImmutablePureComponent { <DomainContainer key={domain} domain={domain} />, )} </ScrollableList> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/util/emoji/index.js b/app/javascript/flavours/glitch/features/emoji/emoji.js index 162946bbb..c4e2c26f2 100644 --- a/app/javascript/flavours/glitch/util/emoji/index.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji.js @@ -1,6 +1,6 @@ -import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, useSystemEmojiFont } from 'flavours/glitch/initial_state'; import unicodeMapping from './emoji_unicode_mapping_light'; -import { assetHost } from 'flavours/glitch/util/config'; +import { assetHost } from 'flavours/glitch/utils/config'; import Trie from 'substring-trie'; const trie = new Trie(Object.keys(unicodeMapping)); diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_compressed.js b/app/javascript/flavours/glitch/features/emoji/emoji_compressed.js index 74b53ce5c..74b53ce5c 100644 --- a/app/javascript/flavours/glitch/util/emoji/emoji_compressed.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_compressed.js diff --git a/app/javascript/flavours/glitch/features/emoji/emoji_map.json b/app/javascript/flavours/glitch/features/emoji/emoji_map.json new file mode 100644 index 000000000..64f6615b7 --- /dev/null +++ b/app/javascript/flavours/glitch/features/emoji/emoji_map.json @@ -0,0 +1 @@ +{"๐":"1f600","๐":"1f603","๐":"1f604","๐":"1f601","๐":"1f606","๐ ":"1f605","๐คฃ":"1f923","๐":"1f602","๐":"1f642","๐":"1f643","๐ซ ":"1fae0","๐":"1f609","๐":"1f60a","๐":"1f607","๐ฅฐ":"1f970","๐":"1f60d","๐คฉ":"1f929","๐":"1f618","๐":"1f617","โบ":"263a","๐":"1f61a","๐":"1f619","๐ฅฒ":"1f972","๐":"1f60b","๐":"1f61b","๐":"1f61c","๐คช":"1f92a","๐":"1f61d","๐ค":"1f911","๐ค":"1f917","๐คญ":"1f92d","๐ซข":"1fae2","๐ซฃ":"1fae3","๐คซ":"1f92b","๐ค":"1f914","๐ซก":"1fae1","๐ค":"1f910","๐คจ":"1f928","๐":"1f610","๐":"1f611","๐ถ":"1f636","๐ซฅ":"1fae5","๐":"1f60f","๐":"1f612","๐":"1f644","๐ฌ":"1f62c","๐คฅ":"1f925","๐":"1f60c","๐":"1f614","๐ช":"1f62a","๐คค":"1f924","๐ด":"1f634","๐ท":"1f637","๐ค":"1f912","๐ค":"1f915","๐คข":"1f922","๐คฎ":"1f92e","๐คง":"1f927","๐ฅต":"1f975","๐ฅถ":"1f976","๐ฅด":"1f974","๐ต":"1f635","๐คฏ":"1f92f","๐ค ":"1f920","๐ฅณ":"1f973","๐ฅธ":"1f978","๐":"1f60e","๐ค":"1f913","๐ง":"1f9d0","๐":"1f615","๐ซค":"1fae4","๐":"1f61f","๐":"1f641","โน":"2639","๐ฎ":"1f62e","๐ฏ":"1f62f","๐ฒ":"1f632","๐ณ":"1f633","๐ฅบ":"1f97a","๐ฅน":"1f979","๐ฆ":"1f626","๐ง":"1f627","๐จ":"1f628","๐ฐ":"1f630","๐ฅ":"1f625","๐ข":"1f622","๐ญ":"1f62d","๐ฑ":"1f631","๐":"1f616","๐ฃ":"1f623","๐":"1f61e","๐":"1f613","๐ฉ":"1f629","๐ซ":"1f62b","๐ฅฑ":"1f971","๐ค":"1f624","๐ก":"1f621","๐ ":"1f620","๐คฌ":"1f92c","๐":"1f608","๐ฟ":"1f47f","๐":"1f480","โ ":"2620","๐ฉ":"1f4a9","๐คก":"1f921","๐น":"1f479","๐บ":"1f47a","๐ป":"1f47b","๐ฝ":"1f47d","๐พ":"1f47e","๐ค":"1f916","๐บ":"1f63a","๐ธ":"1f638","๐น":"1f639","๐ป":"1f63b","๐ผ":"1f63c","๐ฝ":"1f63d","๐":"1f640","๐ฟ":"1f63f","๐พ":"1f63e","๐":"1f648","๐":"1f649","๐":"1f64a","๐":"1f48b","๐":"1f48c","๐":"1f498","๐":"1f49d","๐":"1f496","๐":"1f497","๐":"1f493","๐":"1f49e","๐":"1f495","๐":"1f49f","โฃ":"2763","๐":"1f494","โค":"2764","๐งก":"1f9e1","๐":"1f49b","๐":"1f49a","๐":"1f499","๐":"1f49c","๐ค":"1f90e","๐ค":"1f5a4","๐ค":"1f90d","๐ฏ":"1f4af","๐ข":"1f4a2","๐ฅ":"1f4a5","๐ซ":"1f4ab","๐ฆ":"1f4a6","๐จ":"1f4a8","๐ณ":"1f573","๐ฃ":"1f4a3","๐ฌ":"1f4ac","๐จ":"1f5e8","๐ฏ":"1f5ef","๐ญ":"1f4ad","๐ค":"1f4a4","๐":"1f44b","๐ค":"1f91a","๐":"1f590","โ":"270b","๐":"1f596","๐ซฑ":"1faf1","๐ซฒ":"1faf2","๐ซณ":"1faf3","๐ซด":"1faf4","๐":"1f44c","๐ค":"1f90c","๐ค":"1f90f","โ":"270c","๐ค":"1f91e","๐ซฐ":"1faf0","๐ค":"1f91f","๐ค":"1f918","๐ค":"1f919","๐":"1f448","๐":"1f449","๐":"1f446","๐":"1f595","๐":"1f447","โ":"261d","๐ซต":"1faf5","๐":"1f44d","๐":"1f44e","โ":"270a","๐":"1f44a","๐ค":"1f91b","๐ค":"1f91c","๐":"1f44f","๐":"1f64c","๐ซถ":"1faf6","๐":"1f450","๐คฒ":"1f932","๐ค":"1f91d","๐":"1f64f","โ":"270d","๐ ":"1f485","๐คณ":"1f933","๐ช":"1f4aa","๐ฆพ":"1f9be","๐ฆฟ":"1f9bf","๐ฆต":"1f9b5","๐ฆถ":"1f9b6","๐":"1f442","๐ฆป":"1f9bb","๐":"1f443","๐ง ":"1f9e0","๐ซ":"1fac0","๐ซ":"1fac1","๐ฆท":"1f9b7","๐ฆด":"1f9b4","๐":"1f440","๐":"1f441","๐ ":"1f445","๐":"1f444","๐ซฆ":"1fae6","๐ถ":"1f476","๐ง":"1f9d2","๐ฆ":"1f466","๐ง":"1f467","๐ง":"1f9d1","๐ฑ":"1f471","๐จ":"1f468","๐ง":"1f9d4","๐ฉ":"1f469","๐ง":"1f9d3","๐ด":"1f474","๐ต":"1f475","๐":"1f64d","๐":"1f64e","๐ ":"1f645","๐":"1f646","๐":"1f481","๐":"1f64b","๐ง":"1f9cf","๐":"1f647","๐คฆ":"1f926","๐คท":"1f937","๐ฎ":"1f46e","๐ต":"1f575","๐":"1f482","๐ฅท":"1f977","๐ท":"1f477","๐ซ ":"1fac5","๐คด":"1f934","๐ธ":"1f478","๐ณ":"1f473","๐ฒ":"1f472","๐ง":"1f9d5","๐คต":"1f935","๐ฐ":"1f470","๐คฐ":"1f930","๐ซ":"1fac3","๐ซ":"1fac4","๐คฑ":"1f931","๐ผ":"1f47c","๐ ":"1f385","๐คถ":"1f936","๐ฆธ":"1f9b8","๐ฆน":"1f9b9","๐ง":"1f9d9","๐ง":"1f9da","๐ง":"1f9db","๐ง":"1f9dc","๐ง":"1f9dd","๐ง":"1f9de","๐ง":"1f9df","๐ง":"1f9cc","๐":"1f486","๐":"1f487","๐ถ":"1f6b6","๐ง":"1f9cd","๐ง":"1f9ce","๐":"1f3c3","๐":"1f483","๐บ":"1f57a","๐ด":"1f574","๐ฏ":"1f46f","๐ง":"1f9d6","๐ง":"1f9d7","๐คบ":"1f93a","๐":"1f3c7","โท":"26f7","๐":"1f3c2","๐":"1f3cc","๐":"1f3c4","๐ฃ":"1f6a3","๐":"1f3ca","โน":"26f9","๐":"1f3cb","๐ด":"1f6b4","๐ต":"1f6b5","๐คธ":"1f938","๐คผ":"1f93c","๐คฝ":"1f93d","๐คพ":"1f93e","๐คน":"1f939","๐ง":"1f9d8","๐":"1f6c0","๐":"1f6cc","๐ญ":"1f46d","๐ซ":"1f46b","๐ฌ":"1f46c","๐":"1f48f","๐":"1f491","๐ช":"1f46a","๐ฃ":"1f5e3","๐ค":"1f464","๐ฅ":"1f465","๐ซ":"1fac2","๐ฃ":"1f463","๐ป":"1f463","๐ผ":"1f463","๐ฝ":"1f463","๐พ":"1f463","๐ฟ":"1f463","๐ฆฐ":"1f463","๐ฆฑ":"1f463","๐ฆณ":"1f463","๐ฆฒ":"1f463","๐ต":"1f435","๐":"1f412","๐ฆ":"1f98d","๐ฆง":"1f9a7","๐ถ":"1f436","๐":"1f415","๐ฆฎ":"1f9ae","๐ฉ":"1f429","๐บ":"1f43a","๐ฆ":"1f98a","๐ฆ":"1f99d","๐ฑ":"1f431","๐":"1f408","๐ฆ":"1f981","๐ฏ":"1f42f","๐ ":"1f405","๐":"1f406","๐ด":"1f434","๐":"1f40e","๐ฆ":"1f984","๐ฆ":"1f993","๐ฆ":"1f98c","๐ฆฌ":"1f9ac","๐ฎ":"1f42e","๐":"1f402","๐":"1f403","๐":"1f404","๐ท":"1f437","๐":"1f416","๐":"1f417","๐ฝ":"1f43d","๐":"1f40f","๐":"1f411","๐":"1f410","๐ช":"1f42a","๐ซ":"1f42b","๐ฆ":"1f999","๐ฆ":"1f992","๐":"1f418","๐ฆฃ":"1f9a3","๐ฆ":"1f98f","๐ฆ":"1f99b","๐ญ":"1f42d","๐":"1f401","๐":"1f400","๐น":"1f439","๐ฐ":"1f430","๐":"1f407","๐ฟ":"1f43f","๐ฆซ":"1f9ab","๐ฆ":"1f994","๐ฆ":"1f987","๐ป":"1f43b","๐จ":"1f428","๐ผ":"1f43c","๐ฆฅ":"1f9a5","๐ฆฆ":"1f9a6","๐ฆจ":"1f9a8","๐ฆ":"1f998","๐ฆก":"1f9a1","๐พ":"1f43e","๐ฆ":"1f983","๐":"1f414","๐":"1f413","๐ฃ":"1f423","๐ค":"1f424","๐ฅ":"1f425","๐ฆ":"1f426","๐ง":"1f427","๐":"1f54a","๐ฆ ":"1f985","๐ฆ":"1f986","๐ฆข":"1f9a2","๐ฆ":"1f989","๐ฆค":"1f9a4","๐ชถ":"1fab6","๐ฆฉ":"1f9a9","๐ฆ":"1f99a","๐ฆ":"1f99c","๐ธ":"1f438","๐":"1f40a","๐ข":"1f422","๐ฆ":"1f98e","๐":"1f40d","๐ฒ":"1f432","๐":"1f409","๐ฆ":"1f995","๐ฆ":"1f996","๐ณ":"1f433","๐":"1f40b","๐ฌ":"1f42c","๐ฆญ":"1f9ad","๐":"1f41f","๐ ":"1f420","๐ก":"1f421","๐ฆ":"1f988","๐":"1f419","๐":"1f41a","๐ชธ":"1fab8","๐":"1f40c","๐ฆ":"1f98b","๐":"1f41b","๐":"1f41c","๐":"1f41d","๐ชฒ":"1fab2","๐":"1f41e","๐ฆ":"1f997","๐ชณ":"1fab3","๐ท":"1f577","๐ธ":"1f578","๐ฆ":"1f982","๐ฆ":"1f99f","๐ชฐ":"1fab0","๐ชฑ":"1fab1","๐ฆ ":"1f9a0","๐":"1f490","๐ธ":"1f338","๐ฎ":"1f4ae","๐ชท":"1fab7","๐ต":"1f3f5","๐น":"1f339","๐ฅ":"1f940","๐บ":"1f33a","๐ป":"1f33b","๐ผ":"1f33c","๐ท":"1f337","๐ฑ":"1f331","๐ชด":"1fab4","๐ฒ":"1f332","๐ณ":"1f333","๐ด":"1f334","๐ต":"1f335","๐พ":"1f33e","๐ฟ":"1f33f","โ":"2618","๐":"1f340","๐":"1f341","๐":"1f342","๐":"1f343","๐ชน":"1fab9","๐ชบ":"1faba","๐":"1f347","๐":"1f348","๐":"1f349","๐":"1f34a","๐":"1f34b","๐":"1f34c","๐":"1f34d","๐ฅญ":"1f96d","๐":"1f34e","๐":"1f34f","๐":"1f350","๐":"1f351","๐":"1f352","๐":"1f353","๐ซ":"1fad0","๐ฅ":"1f95d","๐ ":"1f345","๐ซ":"1fad2","๐ฅฅ":"1f965","๐ฅ":"1f951","๐":"1f346","๐ฅ":"1f954","๐ฅ":"1f955","๐ฝ":"1f33d","๐ถ":"1f336","๐ซ":"1fad1","๐ฅ":"1f952","๐ฅฌ":"1f96c","๐ฅฆ":"1f966","๐ง":"1f9c4","๐ง ":"1f9c5","๐":"1f344","๐ฅ":"1f95c","๐ซ":"1fad8","๐ฐ":"1f330","๐":"1f35e","๐ฅ":"1f950","๐ฅ":"1f956","๐ซ":"1fad3","๐ฅจ":"1f968","๐ฅฏ":"1f96f","๐ฅ":"1f95e","๐ง":"1f9c7","๐ง":"1f9c0","๐":"1f356","๐":"1f357","๐ฅฉ":"1f969","๐ฅ":"1f953","๐":"1f354","๐":"1f35f","๐":"1f355","๐ญ":"1f32d","๐ฅช":"1f96a","๐ฎ":"1f32e","๐ฏ":"1f32f","๐ซ":"1fad4","๐ฅ":"1f959","๐ง":"1f9c6","๐ฅ":"1f95a","๐ณ":"1f373","๐ฅ":"1f958","๐ฒ":"1f372","๐ซ":"1fad5","๐ฅฃ":"1f963","๐ฅ":"1f957","๐ฟ":"1f37f","๐ง":"1f9c8","๐ง":"1f9c2","๐ฅซ":"1f96b","๐ฑ":"1f371","๐":"1f358","๐":"1f359","๐":"1f35a","๐":"1f35b","๐":"1f35c","๐":"1f35d","๐ ":"1f360","๐ข":"1f362","๐ฃ":"1f363","๐ค":"1f364","๐ฅ":"1f365","๐ฅฎ":"1f96e","๐ก":"1f361","๐ฅ":"1f95f","๐ฅ ":"1f960","๐ฅก":"1f961","๐ฆ":"1f980","๐ฆ":"1f99e","๐ฆ":"1f990","๐ฆ":"1f991","๐ฆช":"1f9aa","๐ฆ":"1f366","๐ง":"1f367","๐จ":"1f368","๐ฉ":"1f369","๐ช":"1f36a","๐":"1f382","๐ฐ":"1f370","๐ง":"1f9c1","๐ฅง":"1f967","๐ซ":"1f36b","๐ฌ":"1f36c","๐ญ":"1f36d","๐ฎ":"1f36e","๐ฏ":"1f36f","๐ผ":"1f37c","๐ฅ":"1f95b","โ":"2615","๐ซ":"1fad6","๐ต":"1f375","๐ถ":"1f376","๐พ":"1f37e","๐ท":"1f377","๐ธ":"1f378","๐น":"1f379","๐บ":"1f37a","๐ป":"1f37b","๐ฅ":"1f942","๐ฅ":"1f943","๐ซ":"1fad7","๐ฅค":"1f964","๐ง":"1f9cb","๐ง":"1f9c3","๐ง":"1f9c9","๐ง":"1f9ca","๐ฅข":"1f962","๐ฝ":"1f37d","๐ด":"1f374","๐ฅ":"1f944","๐ช":"1f52a","๐ซ":"1fad9","๐บ":"1f3fa","๐":"1f30d","๐":"1f30e","๐":"1f30f","๐":"1f310","๐บ":"1f5fa","๐พ":"1f5fe","๐งญ":"1f9ed","๐":"1f3d4","โฐ":"26f0","๐":"1f30b","๐ป":"1f5fb","๐":"1f3d5","๐":"1f3d6","๐":"1f3dc","๐":"1f3dd","๐":"1f3de","๐":"1f3df","๐":"1f3db","๐":"1f3d7","๐งฑ":"1f9f1","๐ชจ":"1faa8","๐ชต":"1fab5","๐":"1f6d6","๐":"1f3d8","๐":"1f3da","๐ ":"1f3e0","๐ก":"1f3e1","๐ข":"1f3e2","๐ฃ":"1f3e3","๐ค":"1f3e4","๐ฅ":"1f3e5","๐ฆ":"1f3e6","๐จ":"1f3e8","๐ฉ":"1f3e9","๐ช":"1f3ea","๐ซ":"1f3eb","๐ฌ":"1f3ec","๐ญ":"1f3ed","๐ฏ":"1f3ef","๐ฐ":"1f3f0","๐":"1f492","๐ผ":"1f5fc","๐ฝ":"1f5fd","โช":"26ea","๐":"1f54c","๐":"1f6d5","๐":"1f54d","โฉ":"26e9","๐":"1f54b","โฒ":"26f2","โบ":"26fa","๐":"1f301","๐":"1f303","๐":"1f3d9","๐":"1f304","๐ ":"1f305","๐":"1f306","๐":"1f307","๐":"1f309","โจ":"2668","๐ ":"1f3a0","๐":"1f6dd","๐ก":"1f3a1","๐ข":"1f3a2","๐":"1f488","๐ช":"1f3aa","๐":"1f682","๐":"1f683","๐":"1f684","๐ ":"1f685","๐":"1f686","๐":"1f687","๐":"1f688","๐":"1f689","๐":"1f68a","๐":"1f69d","๐":"1f69e","๐":"1f68b","๐":"1f68c","๐":"1f68d","๐":"1f68e","๐":"1f690","๐":"1f691","๐":"1f692","๐":"1f693","๐":"1f694","๐":"1f695","๐":"1f696","๐":"1f697","๐":"1f698","๐":"1f699","๐ป":"1f6fb","๐":"1f69a","๐":"1f69b","๐":"1f69c","๐":"1f3ce","๐":"1f3cd","๐ต":"1f6f5","๐ฆฝ":"1f9bd","๐ฆผ":"1f9bc","๐บ":"1f6fa","๐ฒ":"1f6b2","๐ด":"1f6f4","๐น":"1f6f9","๐ผ":"1f6fc","๐":"1f68f","๐ฃ":"1f6e3","๐ค":"1f6e4","๐ข":"1f6e2","โฝ":"26fd","๐":"1f6de","๐จ":"1f6a8","๐ฅ":"1f6a5","๐ฆ":"1f6a6","๐":"1f6d1","๐ง":"1f6a7","โ":"2693","๐":"1f6df","โต":"26f5","๐ถ":"1f6f6","๐ค":"1f6a4","๐ณ":"1f6f3","โด":"26f4","๐ฅ":"1f6e5","๐ข":"1f6a2","โ":"2708","๐ฉ":"1f6e9","๐ซ":"1f6eb","๐ฌ":"1f6ec","๐ช":"1fa82","๐บ":"1f4ba","๐":"1f681","๐":"1f69f","๐ ":"1f6a0","๐ก":"1f6a1","๐ฐ":"1f6f0","๐":"1f680","๐ธ":"1f6f8","๐":"1f6ce","๐งณ":"1f9f3","โ":"231b","โณ":"23f3","โ":"231a","โฐ":"23f0","โฑ":"23f1","โฒ":"23f2","๐ฐ":"1f570","๐":"1f55b","๐ง":"1f567","๐":"1f550","๐":"1f55c","๐":"1f551","๐":"1f55d","๐":"1f552","๐":"1f55e","๐":"1f553","๐":"1f55f","๐":"1f554","๐ ":"1f560","๐":"1f555","๐ก":"1f561","๐":"1f556","๐ข":"1f562","๐":"1f557","๐ฃ":"1f563","๐":"1f558","๐ค":"1f564","๐":"1f559","๐ฅ":"1f565","๐":"1f55a","๐ฆ":"1f566","๐":"1f311","๐":"1f312","๐":"1f313","๐":"1f314","๐":"1f315","๐":"1f316","๐":"1f317","๐":"1f318","๐":"1f319","๐":"1f31a","๐":"1f31b","๐":"1f31c","๐ก":"1f321","โ":"2600","๐":"1f31d","๐":"1f31e","๐ช":"1fa90","โญ":"2b50","๐":"1f31f","๐ ":"1f320","๐":"1f30c","โ":"2601","โ ":"26c5","โ":"26c8","๐ค":"1f324","๐ฅ":"1f325","๐ฆ":"1f326","๐ง":"1f327","๐จ":"1f328","๐ฉ":"1f329","๐ช":"1f32a","๐ซ":"1f32b","๐ฌ":"1f32c","๐":"1f300","๐":"1f308","๐":"1f302","โ":"2602","โ":"2614","โฑ":"26f1","โก":"26a1","โ":"2744","โ":"2603","โ":"26c4","โ":"2604","๐ฅ":"1f525","๐ง":"1f4a7","๐":"1f30a","๐":"1f383","๐":"1f384","๐":"1f386","๐":"1f387","๐งจ":"1f9e8","โจ":"2728","๐":"1f388","๐":"1f389","๐":"1f38a","๐":"1f38b","๐":"1f38d","๐":"1f38e","๐":"1f38f","๐":"1f390","๐":"1f391","๐งง":"1f9e7","๐":"1f380","๐":"1f381","๐":"1f397","๐":"1f39f","๐ซ":"1f3ab","๐":"1f396","๐":"1f3c6","๐ ":"1f3c5","๐ฅ":"1f947","๐ฅ":"1f948","๐ฅ":"1f949","โฝ":"26bd","โพ":"26be","๐ฅ":"1f94e","๐":"1f3c0","๐":"1f3d0","๐":"1f3c8","๐":"1f3c9","๐พ":"1f3be","๐ฅ":"1f94f","๐ณ":"1f3b3","๐":"1f3cf","๐":"1f3d1","๐":"1f3d2","๐ฅ":"1f94d","๐":"1f3d3","๐ธ":"1f3f8","๐ฅ":"1f94a","๐ฅ":"1f94b","๐ฅ ":"1f945","โณ":"26f3","โธ":"26f8","๐ฃ":"1f3a3","๐คฟ":"1f93f","๐ฝ":"1f3bd","๐ฟ":"1f3bf","๐ท":"1f6f7","๐ฅ":"1f94c","๐ฏ":"1f3af","๐ช":"1fa80","๐ช":"1fa81","๐ฑ":"1f3b1","๐ฎ":"1f52e","๐ช":"1fa84","๐งฟ":"1f9ff","๐ชฌ":"1faac","๐ฎ":"1f3ae","๐น":"1f579","๐ฐ":"1f3b0","๐ฒ":"1f3b2","๐งฉ":"1f9e9","๐งธ":"1f9f8","๐ช ":"1fa85","๐ชฉ":"1faa9","๐ช":"1fa86","โ ":"2660","โฅ":"2665","โฆ":"2666","โฃ":"2663","โ":"265f","๐":"1f0cf","๐":"1f004","๐ด":"1f3b4","๐ญ":"1f3ad","๐ผ":"1f5bc","๐จ":"1f3a8","๐งต":"1f9f5","๐ชก":"1faa1","๐งถ":"1f9f6","๐ชข":"1faa2","๐":"1f453","๐ถ":"1f576","๐ฅฝ":"1f97d","๐ฅผ":"1f97c","๐ฆบ":"1f9ba","๐":"1f454","๐":"1f455","๐":"1f456","๐งฃ":"1f9e3","๐งค":"1f9e4","๐งฅ":"1f9e5","๐งฆ":"1f9e6","๐":"1f457","๐":"1f458","๐ฅป":"1f97b","๐ฉฑ":"1fa71","๐ฉฒ":"1fa72","๐ฉณ":"1fa73","๐":"1f459","๐":"1f45a","๐":"1f45b","๐":"1f45c","๐":"1f45d","๐":"1f6cd","๐":"1f392","๐ฉด":"1fa74","๐":"1f45e","๐":"1f45f","๐ฅพ":"1f97e","๐ฅฟ":"1f97f","๐ ":"1f460","๐ก":"1f461","๐ฉฐ":"1fa70","๐ข":"1f462","๐":"1f451","๐":"1f452","๐ฉ":"1f3a9","๐":"1f393","๐งข":"1f9e2","๐ช":"1fa96","โ":"26d1","๐ฟ":"1f4ff","๐":"1f484","๐":"1f48d","๐":"1f48e","๐":"1f507","๐":"1f508","๐":"1f509","๐":"1f50a","๐ข":"1f4e2","๐ฃ":"1f4e3","๐ฏ":"1f4ef","๐":"1f514","๐":"1f515","๐ผ":"1f3bc","๐ต":"1f3b5","๐ถ":"1f3b6","๐":"1f399","๐":"1f39a","๐":"1f39b","๐ค":"1f3a4","๐ง":"1f3a7","๐ป":"1f4fb","๐ท":"1f3b7","๐ช":"1fa97","๐ธ":"1f3b8","๐น":"1f3b9","๐บ":"1f3ba","๐ป":"1f3bb","๐ช":"1fa95","๐ฅ":"1f941","๐ช":"1fa98","๐ฑ":"1f4f1","๐ฒ":"1f4f2","โ":"260e","๐":"1f4de","๐":"1f4df","๐ ":"1f4e0","๐":"1f50b","๐ชซ":"1faab","๐":"1f50c","๐ป":"1f4bb","๐ฅ":"1f5a5","๐จ":"1f5a8","โจ":"2328","๐ฑ":"1f5b1","๐ฒ":"1f5b2","๐ฝ":"1f4bd","๐พ":"1f4be","๐ฟ":"1f4bf","๐":"1f4c0","๐งฎ":"1f9ee","๐ฅ":"1f3a5","๐":"1f39e","๐ฝ":"1f4fd","๐ฌ":"1f3ac","๐บ":"1f4fa","๐ท":"1f4f7","๐ธ":"1f4f8","๐น":"1f4f9","๐ผ":"1f4fc","๐":"1f50d","๐":"1f50e","๐ฏ":"1f56f","๐ก":"1f4a1","๐ฆ":"1f526","๐ฎ":"1f3ee","๐ช":"1fa94","๐":"1f4d4","๐":"1f4d5","๐":"1f4d6","๐":"1f4d7","๐":"1f4d8","๐":"1f4d9","๐":"1f4da","๐":"1f4d3","๐":"1f4d2","๐":"1f4c3","๐":"1f4dc","๐":"1f4c4","๐ฐ":"1f4f0","๐":"1f5de","๐":"1f4d1","๐":"1f516","๐ท":"1f3f7","๐ฐ":"1f4b0","๐ช":"1fa99","๐ด":"1f4b4","๐ต":"1f4b5","๐ถ":"1f4b6","๐ท":"1f4b7","๐ธ":"1f4b8","๐ณ":"1f4b3","๐งพ":"1f9fe","๐น":"1f4b9","โ":"2709","๐ง":"1f4e7","๐จ":"1f4e8","๐ฉ":"1f4e9","๐ค":"1f4e4","๐ฅ":"1f4e5","๐ฆ":"1f4e6","๐ซ":"1f4eb","๐ช":"1f4ea","๐ฌ":"1f4ec","๐ญ":"1f4ed","๐ฎ":"1f4ee","๐ณ":"1f5f3","โ":"270f","โ":"2712","๐":"1f58b","๐":"1f58a","๐":"1f58c","๐":"1f58d","๐":"1f4dd","๐ผ":"1f4bc","๐":"1f4c1","๐":"1f4c2","๐":"1f5c2","๐ ":"1f4c5","๐":"1f4c6","๐":"1f5d2","๐":"1f5d3","๐":"1f4c7","๐":"1f4c8","๐":"1f4c9","๐":"1f4ca","๐":"1f4cb","๐":"1f4cc","๐":"1f4cd","๐":"1f4ce","๐":"1f587","๐":"1f4cf","๐":"1f4d0","โ":"2702","๐":"1f5c3","๐":"1f5c4","๐":"1f5d1","๐":"1f512","๐":"1f513","๐":"1f50f","๐":"1f510","๐":"1f511","๐":"1f5dd","๐จ":"1f528","๐ช":"1fa93","โ":"26cf","โ":"2692","๐ ":"1f6e0","๐ก":"1f5e1","โ":"2694","๐ซ":"1f52b","๐ช":"1fa83","๐น":"1f3f9","๐ก":"1f6e1","๐ช":"1fa9a","๐ง":"1f527","๐ช":"1fa9b","๐ฉ":"1f529","โ":"2699","๐":"1f5dc","โ":"2696","๐ฆฏ":"1f9af","๐":"1f517","โ":"26d3","๐ช":"1fa9d","๐งฐ":"1f9f0","๐งฒ":"1f9f2","๐ช":"1fa9c","โ":"2697","๐งช":"1f9ea","๐งซ":"1f9eb","๐งฌ":"1f9ec","๐ฌ":"1f52c","๐ญ":"1f52d","๐ก":"1f4e1","๐":"1f489","๐ฉธ":"1fa78","๐":"1f48a","๐ฉน":"1fa79","๐ฉผ":"1fa7c","๐ฉบ":"1fa7a","๐ฉป":"1fa7b","๐ช":"1f6aa","๐":"1f6d7","๐ช":"1fa9e","๐ช":"1fa9f","๐":"1f6cf","๐":"1f6cb","๐ช":"1fa91","๐ฝ":"1f6bd","๐ช ":"1faa0","๐ฟ":"1f6bf","๐":"1f6c1","๐ชค":"1faa4","๐ช":"1fa92","๐งด":"1f9f4","๐งท":"1f9f7","๐งน":"1f9f9","๐งบ":"1f9fa","๐งป":"1f9fb","๐ชฃ":"1faa3","๐งผ":"1f9fc","๐ซง":"1fae7","๐ชฅ":"1faa5","๐งฝ":"1f9fd","๐งฏ":"1f9ef","๐":"1f6d2","๐ฌ":"1f6ac","โฐ":"26b0","๐ชฆ":"1faa6","โฑ":"26b1","๐ฟ":"1f5ff","๐ชง":"1faa7","๐ชช":"1faaa","๐ง":"1f3e7","๐ฎ":"1f6ae","๐ฐ":"1f6b0","โฟ":"267f","๐น":"1f6b9","๐บ":"1f6ba","๐ป":"1f6bb","๐ผ":"1f6bc","๐พ":"1f6be","๐":"1f6c2","๐":"1f6c3","๐":"1f6c4","๐ ":"1f6c5","โ ":"26a0","๐ธ":"1f6b8","โ":"26d4","๐ซ":"1f6ab","๐ณ":"1f6b3","๐ญ":"1f6ad","๐ฏ":"1f6af","๐ฑ":"1f6b1","๐ท":"1f6b7","๐ต":"1f4f5","๐":"1f51e","โข":"2622","โฃ":"2623","โฌ":"2b06","โ":"2197","โก":"27a1","โ":"2198","โฌ":"2b07","โ":"2199","โฌ ":"2b05","โ":"2196","โ":"2195","โ":"2194","โฉ":"21a9","โช":"21aa","โคด":"2934","โคต":"2935","๐":"1f503","๐":"1f504","๐":"1f519","๐":"1f51a","๐":"1f51b","๐":"1f51c","๐":"1f51d","๐":"1f6d0","โ":"269b","๐":"1f549","โก":"2721","โธ":"2638","โฏ":"262f","โ":"271d","โฆ":"2626","โช":"262a","โฎ":"262e","๐":"1f54e","๐ฏ":"1f52f","โ":"2648","โ":"2649","โ":"264a","โ":"264b","โ":"264c","โ":"264d","โ":"264e","โ":"264f","โ":"2650","โ":"2651","โ":"2652","โ":"2653","โ":"26ce","๐":"1f500","๐":"1f501","๐":"1f502","โถ":"25b6","โฉ":"23e9","โญ":"23ed","โฏ":"23ef","โ":"25c0","โช":"23ea","โฎ":"23ee","๐ผ":"1f53c","โซ":"23eb","๐ฝ":"1f53d","โฌ":"23ec","โธ":"23f8","โน":"23f9","โบ":"23fa","โ":"23cf","๐ฆ":"1f3a6","๐ ":"1f505","๐":"1f506","๐ถ":"1f4f6","๐ณ":"1f4f3","๐ด":"1f4f4","โ":"2640","โ":"2642","โง":"26a7","โ":"2716","โ":"2795","โ":"2796","โ":"2797","๐ฐ":"1f7f0","โพ":"267e","โผ":"203c","โ":"2049","โ":"2753","โ":"2754","โ":"2755","โ":"2757","ใฐ":"3030","๐ฑ":"1f4b1","๐ฒ":"1f4b2","โ":"2695","โป":"267b","โ":"269c","๐ฑ":"1f531","๐":"1f4db","๐ฐ":"1f530","โญ":"2b55","โ ":"2705","โ":"2611","โ":"2714","โ":"274c","โ":"274e","โฐ":"27b0","โฟ":"27bf","ใฝ":"303d","โณ":"2733","โด":"2734","โ":"2747","ยฉ":"a9","ยฎ":"ae","โข":"2122","๐":"1f51f","๐ ":"1f520","๐ก":"1f521","๐ข":"1f522","๐ฃ":"1f523","๐ค":"1f524","๐ ฐ":"1f170","๐":"1f18e","๐ ฑ":"1f171","๐":"1f191","๐":"1f192","๐":"1f193","โน":"2139","๐":"1f194","โ":"24c2","๐":"1f195","๐":"1f196","๐ พ":"1f17e","๐":"1f197","๐ ฟ":"1f17f","๐":"1f198","๐":"1f199","๐":"1f19a","๐":"1f201","๐":"1f202","๐ท":"1f237","๐ถ":"1f236","๐ฏ":"1f22f","๐":"1f250","๐น":"1f239","๐":"1f21a","๐ฒ":"1f232","๐":"1f251","๐ธ":"1f238","๐ด":"1f234","๐ณ":"1f233","ใ":"3297","ใ":"3299","๐บ":"1f23a","๐ต":"1f235","๐ด":"1f534","๐ ":"1f7e0","๐ก":"1f7e1","๐ข":"1f7e2","๐ต":"1f535","๐ฃ":"1f7e3","๐ค":"1f7e4","โซ":"26ab","โช":"26aa","๐ฅ":"1f7e5","๐ง":"1f7e7","๐จ":"1f7e8","๐ฉ":"1f7e9","๐ฆ":"1f7e6","๐ช":"1f7ea","๐ซ":"1f7eb","โฌ":"2b1b","โฌ":"2b1c","โผ":"25fc","โป":"25fb","โพ":"25fe","โฝ":"25fd","โช":"25aa","โซ":"25ab","๐ถ":"1f536","๐ท":"1f537","๐ธ":"1f538","๐น":"1f539","๐บ":"1f53a","๐ป":"1f53b","๐ ":"1f4a0","๐":"1f518","๐ณ":"1f533","๐ฒ":"1f532","๐":"1f3c1","๐ฉ":"1f6a9","๐":"1f38c","๐ด":"1f3f4","๐ณ":"1f3f3","โบ๏ธ":"263a","โน๏ธ":"2639","โ ๏ธ":"2620","โฃ๏ธ":"2763","โค๏ธ":"2764","๐ณ๏ธ":"1f573","๐จ๏ธ":"1f5e8","๐ฏ๏ธ":"1f5ef","๐๐ป":"1f44b-1f3fb","๐๐ผ":"1f44b-1f3fc","๐๐ฝ":"1f44b-1f3fd","๐๐พ":"1f44b-1f3fe","๐๐ฟ":"1f44b-1f3ff","๐ค๐ป":"1f91a-1f3fb","๐ค๐ผ":"1f91a-1f3fc","๐ค๐ฝ":"1f91a-1f3fd","๐ค๐พ":"1f91a-1f3fe","๐ค๐ฟ":"1f91a-1f3ff","๐๏ธ":"1f590","๐๐ป":"1f590-1f3fb","๐๐ผ":"1f590-1f3fc","๐๐ฝ":"1f590-1f3fd","๐๐พ":"1f590-1f3fe","๐๐ฟ":"1f590-1f3ff","โ๐ป":"270b-1f3fb","โ๐ผ":"270b-1f3fc","โ๐ฝ":"270b-1f3fd","โ๐พ":"270b-1f3fe","โ๐ฟ":"270b-1f3ff","๐๐ป":"1f596-1f3fb","๐๐ผ":"1f596-1f3fc","๐๐ฝ":"1f596-1f3fd","๐๐พ":"1f596-1f3fe","๐๐ฟ":"1f596-1f3ff","๐ซฑ๐ป":"1faf1-1f3fb","๐ซฑ๐ผ":"1faf1-1f3fc","๐ซฑ๐ฝ":"1faf1-1f3fd","๐ซฑ๐พ":"1faf1-1f3fe","๐ซฑ๐ฟ":"1faf1-1f3ff","๐ซฒ๐ป":"1faf2-1f3fb","๐ซฒ๐ผ":"1faf2-1f3fc","๐ซฒ๐ฝ":"1faf2-1f3fd","๐ซฒ๐พ":"1faf2-1f3fe","๐ซฒ๐ฟ":"1faf2-1f3ff","๐ซณ๐ป":"1faf3-1f3fb","๐ซณ๐ผ":"1faf3-1f3fc","๐ซณ๐ฝ":"1faf3-1f3fd","๐ซณ๐พ":"1faf3-1f3fe","๐ซณ๐ฟ":"1faf3-1f3ff","๐ซด๐ป":"1faf4-1f3fb","๐ซด๐ผ":"1faf4-1f3fc","๐ซด๐ฝ":"1faf4-1f3fd","๐ซด๐พ":"1faf4-1f3fe","๐ซด๐ฟ":"1faf4-1f3ff","๐๐ป":"1f44c-1f3fb","๐๐ผ":"1f44c-1f3fc","๐๐ฝ":"1f44c-1f3fd","๐๐พ":"1f44c-1f3fe","๐๐ฟ":"1f44c-1f3ff","๐ค๐ป":"1f90c-1f3fb","๐ค๐ผ":"1f90c-1f3fc","๐ค๐ฝ":"1f90c-1f3fd","๐ค๐พ":"1f90c-1f3fe","๐ค๐ฟ":"1f90c-1f3ff","๐ค๐ป":"1f90f-1f3fb","๐ค๐ผ":"1f90f-1f3fc","๐ค๐ฝ":"1f90f-1f3fd","๐ค๐พ":"1f90f-1f3fe","๐ค๐ฟ":"1f90f-1f3ff","โ๏ธ":"270c","โ๐ป":"270c-1f3fb","โ๐ผ":"270c-1f3fc","โ๐ฝ":"270c-1f3fd","โ๐พ":"270c-1f3fe","โ๐ฟ":"270c-1f3ff","๐ค๐ป":"1f91e-1f3fb","๐ค๐ผ":"1f91e-1f3fc","๐ค๐ฝ":"1f91e-1f3fd","๐ค๐พ":"1f91e-1f3fe","๐ค๐ฟ":"1f91e-1f3ff","๐ซฐ๐ป":"1faf0-1f3fb","๐ซฐ๐ผ":"1faf0-1f3fc","๐ซฐ๐ฝ":"1faf0-1f3fd","๐ซฐ๐พ":"1faf0-1f3fe","๐ซฐ๐ฟ":"1faf0-1f3ff","๐ค๐ป":"1f91f-1f3fb","๐ค๐ผ":"1f91f-1f3fc","๐ค๐ฝ":"1f91f-1f3fd","๐ค๐พ":"1f91f-1f3fe","๐ค๐ฟ":"1f91f-1f3ff","๐ค๐ป":"1f918-1f3fb","๐ค๐ผ":"1f918-1f3fc","๐ค๐ฝ":"1f918-1f3fd","๐ค๐พ":"1f918-1f3fe","๐ค๐ฟ":"1f918-1f3ff","๐ค๐ป":"1f919-1f3fb","๐ค๐ผ":"1f919-1f3fc","๐ค๐ฝ":"1f919-1f3fd","๐ค๐พ":"1f919-1f3fe","๐ค๐ฟ":"1f919-1f3ff","๐๐ป":"1f448-1f3fb","๐๐ผ":"1f448-1f3fc","๐๐ฝ":"1f448-1f3fd","๐๐พ":"1f448-1f3fe","๐๐ฟ":"1f448-1f3ff","๐๐ป":"1f449-1f3fb","๐๐ผ":"1f449-1f3fc","๐๐ฝ":"1f449-1f3fd","๐๐พ":"1f449-1f3fe","๐๐ฟ":"1f449-1f3ff","๐๐ป":"1f446-1f3fb","๐๐ผ":"1f446-1f3fc","๐๐ฝ":"1f446-1f3fd","๐๐พ":"1f446-1f3fe","๐๐ฟ":"1f446-1f3ff","๐๐ป":"1f595-1f3fb","๐๐ผ":"1f595-1f3fc","๐๐ฝ":"1f595-1f3fd","๐๐พ":"1f595-1f3fe","๐๐ฟ":"1f595-1f3ff","๐๐ป":"1f447-1f3fb","๐๐ผ":"1f447-1f3fc","๐๐ฝ":"1f447-1f3fd","๐๐พ":"1f447-1f3fe","๐๐ฟ":"1f447-1f3ff","โ๏ธ":"261d","โ๐ป":"261d-1f3fb","โ๐ผ":"261d-1f3fc","โ๐ฝ":"261d-1f3fd","โ๐พ":"261d-1f3fe","โ๐ฟ":"261d-1f3ff","๐ซต๐ป":"1faf5-1f3fb","๐ซต๐ผ":"1faf5-1f3fc","๐ซต๐ฝ":"1faf5-1f3fd","๐ซต๐พ":"1faf5-1f3fe","๐ซต๐ฟ":"1faf5-1f3ff","๐๐ป":"1f44d-1f3fb","๐๐ผ":"1f44d-1f3fc","๐๐ฝ":"1f44d-1f3fd","๐๐พ":"1f44d-1f3fe","๐๐ฟ":"1f44d-1f3ff","๐๐ป":"1f44e-1f3fb","๐๐ผ":"1f44e-1f3fc","๐๐ฝ":"1f44e-1f3fd","๐๐พ":"1f44e-1f3fe","๐๐ฟ":"1f44e-1f3ff","โ๐ป":"270a-1f3fb","โ๐ผ":"270a-1f3fc","โ๐ฝ":"270a-1f3fd","โ๐พ":"270a-1f3fe","โ๐ฟ":"270a-1f3ff","๐๐ป":"1f44a-1f3fb","๐๐ผ":"1f44a-1f3fc","๐๐ฝ":"1f44a-1f3fd","๐๐พ":"1f44a-1f3fe","๐๐ฟ":"1f44a-1f3ff","๐ค๐ป":"1f91b-1f3fb","๐ค๐ผ":"1f91b-1f3fc","๐ค๐ฝ":"1f91b-1f3fd","๐ค๐พ":"1f91b-1f3fe","๐ค๐ฟ":"1f91b-1f3ff","๐ค๐ป":"1f91c-1f3fb","๐ค๐ผ":"1f91c-1f3fc","๐ค๐ฝ":"1f91c-1f3fd","๐ค๐พ":"1f91c-1f3fe","๐ค๐ฟ":"1f91c-1f3ff","๐๐ป":"1f44f-1f3fb","๐๐ผ":"1f44f-1f3fc","๐๐ฝ":"1f44f-1f3fd","๐๐พ":"1f44f-1f3fe","๐๐ฟ":"1f44f-1f3ff","๐๐ป":"1f64c-1f3fb","๐๐ผ":"1f64c-1f3fc","๐๐ฝ":"1f64c-1f3fd","๐๐พ":"1f64c-1f3fe","๐๐ฟ":"1f64c-1f3ff","๐ซถ๐ป":"1faf6-1f3fb","๐ซถ๐ผ":"1faf6-1f3fc","๐ซถ๐ฝ":"1faf6-1f3fd","๐ซถ๐พ":"1faf6-1f3fe","๐ซถ๐ฟ":"1faf6-1f3ff","๐๐ป":"1f450-1f3fb","๐๐ผ":"1f450-1f3fc","๐๐ฝ":"1f450-1f3fd","๐๐พ":"1f450-1f3fe","๐๐ฟ":"1f450-1f3ff","๐คฒ๐ป":"1f932-1f3fb","๐คฒ๐ผ":"1f932-1f3fc","๐คฒ๐ฝ":"1f932-1f3fd","๐คฒ๐พ":"1f932-1f3fe","๐คฒ๐ฟ":"1f932-1f3ff","๐ค๐ป":"1f91d-1f3fb","๐ค๐ผ":"1f91d-1f3fc","๐ค๐ฝ":"1f91d-1f3fd","๐ค๐พ":"1f91d-1f3fe","๐ค๐ฟ":"1f91d-1f3ff","๐๐ป":"1f64f-1f3fb","๐๐ผ":"1f64f-1f3fc","๐๐ฝ":"1f64f-1f3fd","๐๐พ":"1f64f-1f3fe","๐๐ฟ":"1f64f-1f3ff","โ๏ธ":"270d","โ๐ป":"270d-1f3fb","โ๐ผ":"270d-1f3fc","โ๐ฝ":"270d-1f3fd","โ๐พ":"270d-1f3fe","โ๐ฟ":"270d-1f3ff","๐ ๐ป":"1f485-1f3fb","๐ ๐ผ":"1f485-1f3fc","๐ ๐ฝ":"1f485-1f3fd","๐ ๐พ":"1f485-1f3fe","๐ ๐ฟ":"1f485-1f3ff","๐คณ๐ป":"1f933-1f3fb","๐คณ๐ผ":"1f933-1f3fc","๐คณ๐ฝ":"1f933-1f3fd","๐คณ๐พ":"1f933-1f3fe","๐คณ๐ฟ":"1f933-1f3ff","๐ช๐ป":"1f4aa-1f3fb","๐ช๐ผ":"1f4aa-1f3fc","๐ช๐ฝ":"1f4aa-1f3fd","๐ช๐พ":"1f4aa-1f3fe","๐ช๐ฟ":"1f4aa-1f3ff","๐ฆต๐ป":"1f9b5-1f3fb","๐ฆต๐ผ":"1f9b5-1f3fc","๐ฆต๐ฝ":"1f9b5-1f3fd","๐ฆต๐พ":"1f9b5-1f3fe","๐ฆต๐ฟ":"1f9b5-1f3ff","๐ฆถ๐ป":"1f9b6-1f3fb","๐ฆถ๐ผ":"1f9b6-1f3fc","๐ฆถ๐ฝ":"1f9b6-1f3fd","๐ฆถ๐พ":"1f9b6-1f3fe","๐ฆถ๐ฟ":"1f9b6-1f3ff","๐๐ป":"1f442-1f3fb","๐๐ผ":"1f442-1f3fc","๐๐ฝ":"1f442-1f3fd","๐๐พ":"1f442-1f3fe","๐๐ฟ":"1f442-1f3ff","๐ฆป๐ป":"1f9bb-1f3fb","๐ฆป๐ผ":"1f9bb-1f3fc","๐ฆป๐ฝ":"1f9bb-1f3fd","๐ฆป๐พ":"1f9bb-1f3fe","๐ฆป๐ฟ":"1f9bb-1f3ff","๐๐ป":"1f443-1f3fb","๐๐ผ":"1f443-1f3fc","๐๐ฝ":"1f443-1f3fd","๐๐พ":"1f443-1f3fe","๐๐ฟ":"1f443-1f3ff","๐๏ธ":"1f441","๐ถ๐ป":"1f476-1f3fb","๐ถ๐ผ":"1f476-1f3fc","๐ถ๐ฝ":"1f476-1f3fd","๐ถ๐พ":"1f476-1f3fe","๐ถ๐ฟ":"1f476-1f3ff","๐ง๐ป":"1f9d2-1f3fb","๐ง๐ผ":"1f9d2-1f3fc","๐ง๐ฝ":"1f9d2-1f3fd","๐ง๐พ":"1f9d2-1f3fe","๐ง๐ฟ":"1f9d2-1f3ff","๐ฆ๐ป":"1f466-1f3fb","๐ฆ๐ผ":"1f466-1f3fc","๐ฆ๐ฝ":"1f466-1f3fd","๐ฆ๐พ":"1f466-1f3fe","๐ฆ๐ฟ":"1f466-1f3ff","๐ง๐ป":"1f467-1f3fb","๐ง๐ผ":"1f467-1f3fc","๐ง๐ฝ":"1f467-1f3fd","๐ง๐พ":"1f467-1f3fe","๐ง๐ฟ":"1f467-1f3ff","๐ง๐ป":"1f9d1-1f3fb","๐ง๐ผ":"1f9d1-1f3fc","๐ง๐ฝ":"1f9d1-1f3fd","๐ง๐พ":"1f9d1-1f3fe","๐ง๐ฟ":"1f9d1-1f3ff","๐ฑ๐ป":"1f471-1f3fb","๐ฑ๐ผ":"1f471-1f3fc","๐ฑ๐ฝ":"1f471-1f3fd","๐ฑ๐พ":"1f471-1f3fe","๐ฑ๐ฟ":"1f471-1f3ff","๐จ๐ป":"1f468-1f3fb","๐จ๐ผ":"1f468-1f3fc","๐จ๐ฝ":"1f468-1f3fd","๐จ๐พ":"1f468-1f3fe","๐จ๐ฟ":"1f468-1f3ff","๐ง๐ป":"1f9d4-1f3fb","๐ง๐ผ":"1f9d4-1f3fc","๐ง๐ฝ":"1f9d4-1f3fd","๐ง๐พ":"1f9d4-1f3fe","๐ง๐ฟ":"1f9d4-1f3ff","๐ฉ๐ป":"1f469-1f3fb","๐ฉ๐ผ":"1f469-1f3fc","๐ฉ๐ฝ":"1f469-1f3fd","๐ฉ๐พ":"1f469-1f3fe","๐ฉ๐ฟ":"1f469-1f3ff","๐ง๐ป":"1f9d3-1f3fb","๐ง๐ผ":"1f9d3-1f3fc","๐ง๐ฝ":"1f9d3-1f3fd","๐ง๐พ":"1f9d3-1f3fe","๐ง๐ฟ":"1f9d3-1f3ff","๐ด๐ป":"1f474-1f3fb","๐ด๐ผ":"1f474-1f3fc","๐ด๐ฝ":"1f474-1f3fd","๐ด๐พ":"1f474-1f3fe","๐ด๐ฟ":"1f474-1f3ff","๐ต๐ป":"1f475-1f3fb","๐ต๐ผ":"1f475-1f3fc","๐ต๐ฝ":"1f475-1f3fd","๐ต๐พ":"1f475-1f3fe","๐ต๐ฟ":"1f475-1f3ff","๐๐ป":"1f64d-1f3fb","๐๐ผ":"1f64d-1f3fc","๐๐ฝ":"1f64d-1f3fd","๐๐พ":"1f64d-1f3fe","๐๐ฟ":"1f64d-1f3ff","๐๐ป":"1f64e-1f3fb","๐๐ผ":"1f64e-1f3fc","๐๐ฝ":"1f64e-1f3fd","๐๐พ":"1f64e-1f3fe","๐๐ฟ":"1f64e-1f3ff","๐ ๐ป":"1f645-1f3fb","๐ ๐ผ":"1f645-1f3fc","๐ ๐ฝ":"1f645-1f3fd","๐ ๐พ":"1f645-1f3fe","๐ ๐ฟ":"1f645-1f3ff","๐๐ป":"1f646-1f3fb","๐๐ผ":"1f646-1f3fc","๐๐ฝ":"1f646-1f3fd","๐๐พ":"1f646-1f3fe","๐๐ฟ":"1f646-1f3ff","๐๐ป":"1f481-1f3fb","๐๐ผ":"1f481-1f3fc","๐๐ฝ":"1f481-1f3fd","๐๐พ":"1f481-1f3fe","๐๐ฟ":"1f481-1f3ff","๐๐ป":"1f64b-1f3fb","๐๐ผ":"1f64b-1f3fc","๐๐ฝ":"1f64b-1f3fd","๐๐พ":"1f64b-1f3fe","๐๐ฟ":"1f64b-1f3ff","๐ง๐ป":"1f9cf-1f3fb","๐ง๐ผ":"1f9cf-1f3fc","๐ง๐ฝ":"1f9cf-1f3fd","๐ง๐พ":"1f9cf-1f3fe","๐ง๐ฟ":"1f9cf-1f3ff","๐๐ป":"1f647-1f3fb","๐๐ผ":"1f647-1f3fc","๐๐ฝ":"1f647-1f3fd","๐๐พ":"1f647-1f3fe","๐๐ฟ":"1f647-1f3ff","๐คฆ๐ป":"1f926-1f3fb","๐คฆ๐ผ":"1f926-1f3fc","๐คฆ๐ฝ":"1f926-1f3fd","๐คฆ๐พ":"1f926-1f3fe","๐คฆ๐ฟ":"1f926-1f3ff","๐คท๐ป":"1f937-1f3fb","๐คท๐ผ":"1f937-1f3fc","๐คท๐ฝ":"1f937-1f3fd","๐คท๐พ":"1f937-1f3fe","๐คท๐ฟ":"1f937-1f3ff","๐ฎ๐ป":"1f46e-1f3fb","๐ฎ๐ผ":"1f46e-1f3fc","๐ฎ๐ฝ":"1f46e-1f3fd","๐ฎ๐พ":"1f46e-1f3fe","๐ฎ๐ฟ":"1f46e-1f3ff","๐ต๏ธ":"1f575","๐ต๐ป":"1f575-1f3fb","๐ต๐ผ":"1f575-1f3fc","๐ต๐ฝ":"1f575-1f3fd","๐ต๐พ":"1f575-1f3fe","๐ต๐ฟ":"1f575-1f3ff","๐๐ป":"1f482-1f3fb","๐๐ผ":"1f482-1f3fc","๐๐ฝ":"1f482-1f3fd","๐๐พ":"1f482-1f3fe","๐๐ฟ":"1f482-1f3ff","๐ฅท๐ป":"1f977-1f3fb","๐ฅท๐ผ":"1f977-1f3fc","๐ฅท๐ฝ":"1f977-1f3fd","๐ฅท๐พ":"1f977-1f3fe","๐ฅท๐ฟ":"1f977-1f3ff","๐ท๐ป":"1f477-1f3fb","๐ท๐ผ":"1f477-1f3fc","๐ท๐ฝ":"1f477-1f3fd","๐ท๐พ":"1f477-1f3fe","๐ท๐ฟ":"1f477-1f3ff","๐ซ ๐ป":"1fac5-1f3fb","๐ซ ๐ผ":"1fac5-1f3fc","๐ซ ๐ฝ":"1fac5-1f3fd","๐ซ ๐พ":"1fac5-1f3fe","๐ซ ๐ฟ":"1fac5-1f3ff","๐คด๐ป":"1f934-1f3fb","๐คด๐ผ":"1f934-1f3fc","๐คด๐ฝ":"1f934-1f3fd","๐คด๐พ":"1f934-1f3fe","๐คด๐ฟ":"1f934-1f3ff","๐ธ๐ป":"1f478-1f3fb","๐ธ๐ผ":"1f478-1f3fc","๐ธ๐ฝ":"1f478-1f3fd","๐ธ๐พ":"1f478-1f3fe","๐ธ๐ฟ":"1f478-1f3ff","๐ณ๐ป":"1f473-1f3fb","๐ณ๐ผ":"1f473-1f3fc","๐ณ๐ฝ":"1f473-1f3fd","๐ณ๐พ":"1f473-1f3fe","๐ณ๐ฟ":"1f473-1f3ff","๐ฒ๐ป":"1f472-1f3fb","๐ฒ๐ผ":"1f472-1f3fc","๐ฒ๐ฝ":"1f472-1f3fd","๐ฒ๐พ":"1f472-1f3fe","๐ฒ๐ฟ":"1f472-1f3ff","๐ง๐ป":"1f9d5-1f3fb","๐ง๐ผ":"1f9d5-1f3fc","๐ง๐ฝ":"1f9d5-1f3fd","๐ง๐พ":"1f9d5-1f3fe","๐ง๐ฟ":"1f9d5-1f3ff","๐คต๐ป":"1f935-1f3fb","๐คต๐ผ":"1f935-1f3fc","๐คต๐ฝ":"1f935-1f3fd","๐คต๐พ":"1f935-1f3fe","๐คต๐ฟ":"1f935-1f3ff","๐ฐ๐ป":"1f470-1f3fb","๐ฐ๐ผ":"1f470-1f3fc","๐ฐ๐ฝ":"1f470-1f3fd","๐ฐ๐พ":"1f470-1f3fe","๐ฐ๐ฟ":"1f470-1f3ff","๐คฐ๐ป":"1f930-1f3fb","๐คฐ๐ผ":"1f930-1f3fc","๐คฐ๐ฝ":"1f930-1f3fd","๐คฐ๐พ":"1f930-1f3fe","๐คฐ๐ฟ":"1f930-1f3ff","๐ซ๐ป":"1fac3-1f3fb","๐ซ๐ผ":"1fac3-1f3fc","๐ซ๐ฝ":"1fac3-1f3fd","๐ซ๐พ":"1fac3-1f3fe","๐ซ๐ฟ":"1fac3-1f3ff","๐ซ๐ป":"1fac4-1f3fb","๐ซ๐ผ":"1fac4-1f3fc","๐ซ๐ฝ":"1fac4-1f3fd","๐ซ๐พ":"1fac4-1f3fe","๐ซ๐ฟ":"1fac4-1f3ff","๐คฑ๐ป":"1f931-1f3fb","๐คฑ๐ผ":"1f931-1f3fc","๐คฑ๐ฝ":"1f931-1f3fd","๐คฑ๐พ":"1f931-1f3fe","๐คฑ๐ฟ":"1f931-1f3ff","๐ผ๐ป":"1f47c-1f3fb","๐ผ๐ผ":"1f47c-1f3fc","๐ผ๐ฝ":"1f47c-1f3fd","๐ผ๐พ":"1f47c-1f3fe","๐ผ๐ฟ":"1f47c-1f3ff","๐ ๐ป":"1f385-1f3fb","๐ ๐ผ":"1f385-1f3fc","๐ ๐ฝ":"1f385-1f3fd","๐ ๐พ":"1f385-1f3fe","๐ ๐ฟ":"1f385-1f3ff","๐คถ๐ป":"1f936-1f3fb","๐คถ๐ผ":"1f936-1f3fc","๐คถ๐ฝ":"1f936-1f3fd","๐คถ๐พ":"1f936-1f3fe","๐คถ๐ฟ":"1f936-1f3ff","๐ฆธ๐ป":"1f9b8-1f3fb","๐ฆธ๐ผ":"1f9b8-1f3fc","๐ฆธ๐ฝ":"1f9b8-1f3fd","๐ฆธ๐พ":"1f9b8-1f3fe","๐ฆธ๐ฟ":"1f9b8-1f3ff","๐ฆน๐ป":"1f9b9-1f3fb","๐ฆน๐ผ":"1f9b9-1f3fc","๐ฆน๐ฝ":"1f9b9-1f3fd","๐ฆน๐พ":"1f9b9-1f3fe","๐ฆน๐ฟ":"1f9b9-1f3ff","๐ง๐ป":"1f9d9-1f3fb","๐ง๐ผ":"1f9d9-1f3fc","๐ง๐ฝ":"1f9d9-1f3fd","๐ง๐พ":"1f9d9-1f3fe","๐ง๐ฟ":"1f9d9-1f3ff","๐ง๐ป":"1f9da-1f3fb","๐ง๐ผ":"1f9da-1f3fc","๐ง๐ฝ":"1f9da-1f3fd","๐ง๐พ":"1f9da-1f3fe","๐ง๐ฟ":"1f9da-1f3ff","๐ง๐ป":"1f9db-1f3fb","๐ง๐ผ":"1f9db-1f3fc","๐ง๐ฝ":"1f9db-1f3fd","๐ง๐พ":"1f9db-1f3fe","๐ง๐ฟ":"1f9db-1f3ff","๐ง๐ป":"1f9dc-1f3fb","๐ง๐ผ":"1f9dc-1f3fc","๐ง๐ฝ":"1f9dc-1f3fd","๐ง๐พ":"1f9dc-1f3fe","๐ง๐ฟ":"1f9dc-1f3ff","๐ง๐ป":"1f9dd-1f3fb","๐ง๐ผ":"1f9dd-1f3fc","๐ง๐ฝ":"1f9dd-1f3fd","๐ง๐พ":"1f9dd-1f3fe","๐ง๐ฟ":"1f9dd-1f3ff","๐๐ป":"1f486-1f3fb","๐๐ผ":"1f486-1f3fc","๐๐ฝ":"1f486-1f3fd","๐๐พ":"1f486-1f3fe","๐๐ฟ":"1f486-1f3ff","๐๐ป":"1f487-1f3fb","๐๐ผ":"1f487-1f3fc","๐๐ฝ":"1f487-1f3fd","๐๐พ":"1f487-1f3fe","๐๐ฟ":"1f487-1f3ff","๐ถ๐ป":"1f6b6-1f3fb","๐ถ๐ผ":"1f6b6-1f3fc","๐ถ๐ฝ":"1f6b6-1f3fd","๐ถ๐พ":"1f6b6-1f3fe","๐ถ๐ฟ":"1f6b6-1f3ff","๐ง๐ป":"1f9cd-1f3fb","๐ง๐ผ":"1f9cd-1f3fc","๐ง๐ฝ":"1f9cd-1f3fd","๐ง๐พ":"1f9cd-1f3fe","๐ง๐ฟ":"1f9cd-1f3ff","๐ง๐ป":"1f9ce-1f3fb","๐ง๐ผ":"1f9ce-1f3fc","๐ง๐ฝ":"1f9ce-1f3fd","๐ง๐พ":"1f9ce-1f3fe","๐ง๐ฟ":"1f9ce-1f3ff","๐๐ป":"1f3c3-1f3fb","๐๐ผ":"1f3c3-1f3fc","๐๐ฝ":"1f3c3-1f3fd","๐๐พ":"1f3c3-1f3fe","๐๐ฟ":"1f3c3-1f3ff","๐๐ป":"1f483-1f3fb","๐๐ผ":"1f483-1f3fc","๐๐ฝ":"1f483-1f3fd","๐๐พ":"1f483-1f3fe","๐๐ฟ":"1f483-1f3ff","๐บ๐ป":"1f57a-1f3fb","๐บ๐ผ":"1f57a-1f3fc","๐บ๐ฝ":"1f57a-1f3fd","๐บ๐พ":"1f57a-1f3fe","๐บ๐ฟ":"1f57a-1f3ff","๐ด๏ธ":"1f574","๐ด๐ป":"1f574-1f3fb","๐ด๐ผ":"1f574-1f3fc","๐ด๐ฝ":"1f574-1f3fd","๐ด๐พ":"1f574-1f3fe","๐ด๐ฟ":"1f574-1f3ff","๐ง๐ป":"1f9d6-1f3fb","๐ง๐ผ":"1f9d6-1f3fc","๐ง๐ฝ":"1f9d6-1f3fd","๐ง๐พ":"1f9d6-1f3fe","๐ง๐ฟ":"1f9d6-1f3ff","๐ง๐ป":"1f9d7-1f3fb","๐ง๐ผ":"1f9d7-1f3fc","๐ง๐ฝ":"1f9d7-1f3fd","๐ง๐พ":"1f9d7-1f3fe","๐ง๐ฟ":"1f9d7-1f3ff","๐๐ป":"1f3c7-1f3fb","๐๐ผ":"1f3c7-1f3fc","๐๐ฝ":"1f3c7-1f3fd","๐๐พ":"1f3c7-1f3fe","๐๐ฟ":"1f3c7-1f3ff","โท๏ธ":"26f7","๐๐ป":"1f3c2-1f3fb","๐๐ผ":"1f3c2-1f3fc","๐๐ฝ":"1f3c2-1f3fd","๐๐พ":"1f3c2-1f3fe","๐๐ฟ":"1f3c2-1f3ff","๐๏ธ":"1f3cc","๐๐ป":"1f3cc-1f3fb","๐๐ผ":"1f3cc-1f3fc","๐๐ฝ":"1f3cc-1f3fd","๐๐พ":"1f3cc-1f3fe","๐๐ฟ":"1f3cc-1f3ff","๐๐ป":"1f3c4-1f3fb","๐๐ผ":"1f3c4-1f3fc","๐๐ฝ":"1f3c4-1f3fd","๐๐พ":"1f3c4-1f3fe","๐๐ฟ":"1f3c4-1f3ff","๐ฃ๐ป":"1f6a3-1f3fb","๐ฃ๐ผ":"1f6a3-1f3fc","๐ฃ๐ฝ":"1f6a3-1f3fd","๐ฃ๐พ":"1f6a3-1f3fe","๐ฃ๐ฟ":"1f6a3-1f3ff","๐๐ป":"1f3ca-1f3fb","๐๐ผ":"1f3ca-1f3fc","๐๐ฝ":"1f3ca-1f3fd","๐๐พ":"1f3ca-1f3fe","๐๐ฟ":"1f3ca-1f3ff","โน๏ธ":"26f9","โน๐ป":"26f9-1f3fb","โน๐ผ":"26f9-1f3fc","โน๐ฝ":"26f9-1f3fd","โน๐พ":"26f9-1f3fe","โน๐ฟ":"26f9-1f3ff","๐๏ธ":"1f3cb","๐๐ป":"1f3cb-1f3fb","๐๐ผ":"1f3cb-1f3fc","๐๐ฝ":"1f3cb-1f3fd","๐๐พ":"1f3cb-1f3fe","๐๐ฟ":"1f3cb-1f3ff","๐ด๐ป":"1f6b4-1f3fb","๐ด๐ผ":"1f6b4-1f3fc","๐ด๐ฝ":"1f6b4-1f3fd","๐ด๐พ":"1f6b4-1f3fe","๐ด๐ฟ":"1f6b4-1f3ff","๐ต๐ป":"1f6b5-1f3fb","๐ต๐ผ":"1f6b5-1f3fc","๐ต๐ฝ":"1f6b5-1f3fd","๐ต๐พ":"1f6b5-1f3fe","๐ต๐ฟ":"1f6b5-1f3ff","๐คธ๐ป":"1f938-1f3fb","๐คธ๐ผ":"1f938-1f3fc","๐คธ๐ฝ":"1f938-1f3fd","๐คธ๐พ":"1f938-1f3fe","๐คธ๐ฟ":"1f938-1f3ff","๐คฝ๐ป":"1f93d-1f3fb","๐คฝ๐ผ":"1f93d-1f3fc","๐คฝ๐ฝ":"1f93d-1f3fd","๐คฝ๐พ":"1f93d-1f3fe","๐คฝ๐ฟ":"1f93d-1f3ff","๐คพ๐ป":"1f93e-1f3fb","๐คพ๐ผ":"1f93e-1f3fc","๐คพ๐ฝ":"1f93e-1f3fd","๐คพ๐พ":"1f93e-1f3fe","๐คพ๐ฟ":"1f93e-1f3ff","๐คน๐ป":"1f939-1f3fb","๐คน๐ผ":"1f939-1f3fc","๐คน๐ฝ":"1f939-1f3fd","๐คน๐พ":"1f939-1f3fe","๐คน๐ฟ":"1f939-1f3ff","๐ง๐ป":"1f9d8-1f3fb","๐ง๐ผ":"1f9d8-1f3fc","๐ง๐ฝ":"1f9d8-1f3fd","๐ง๐พ":"1f9d8-1f3fe","๐ง๐ฟ":"1f9d8-1f3ff","๐๐ป":"1f6c0-1f3fb","๐๐ผ":"1f6c0-1f3fc","๐๐ฝ":"1f6c0-1f3fd","๐๐พ":"1f6c0-1f3fe","๐๐ฟ":"1f6c0-1f3ff","๐๐ป":"1f6cc-1f3fb","๐๐ผ":"1f6cc-1f3fc","๐๐ฝ":"1f6cc-1f3fd","๐๐พ":"1f6cc-1f3fe","๐๐ฟ":"1f6cc-1f3ff","๐ญ๐ป":"1f46d-1f3fb","๐ญ๐ผ":"1f46d-1f3fc","๐ญ๐ฝ":"1f46d-1f3fd","๐ญ๐พ":"1f46d-1f3fe","๐ญ๐ฟ":"1f46d-1f3ff","๐ซ๐ป":"1f46b-1f3fb","๐ซ๐ผ":"1f46b-1f3fc","๐ซ๐ฝ":"1f46b-1f3fd","๐ซ๐พ":"1f46b-1f3fe","๐ซ๐ฟ":"1f46b-1f3ff","๐ฌ๐ป":"1f46c-1f3fb","๐ฌ๐ผ":"1f46c-1f3fc","๐ฌ๐ฝ":"1f46c-1f3fd","๐ฌ๐พ":"1f46c-1f3fe","๐ฌ๐ฟ":"1f46c-1f3ff","๐๐ป":"1f48f-1f3fb","๐๐ผ":"1f48f-1f3fc","๐๐ฝ":"1f48f-1f3fd","๐๐พ":"1f48f-1f3fe","๐๐ฟ":"1f48f-1f3ff","๐๐ป":"1f491-1f3fb","๐๐ผ":"1f491-1f3fc","๐๐ฝ":"1f491-1f3fd","๐๐พ":"1f491-1f3fe","๐๐ฟ":"1f491-1f3ff","๐ฃ๏ธ":"1f5e3","๐ฟ๏ธ":"1f43f","๐๏ธ":"1f54a","๐ท๏ธ":"1f577","๐ธ๏ธ":"1f578","๐ต๏ธ":"1f3f5","โ๏ธ":"2618","๐ถ๏ธ":"1f336","๐ฝ๏ธ":"1f37d","๐บ๏ธ":"1f5fa","๐๏ธ":"1f3d4","โฐ๏ธ":"26f0","๐๏ธ":"1f3d5","๐๏ธ":"1f3d6","๐๏ธ":"1f3dc","๐๏ธ":"1f3dd","๐๏ธ":"1f3de","๐๏ธ":"1f3df","๐๏ธ":"1f3db","๐๏ธ":"1f3d7","๐๏ธ":"1f3d8","๐๏ธ":"1f3da","โฉ๏ธ":"26e9","๐๏ธ":"1f3d9","โจ๏ธ":"2668","๐๏ธ":"1f3ce","๐๏ธ":"1f3cd","๐ฃ๏ธ":"1f6e3","๐ค๏ธ":"1f6e4","๐ข๏ธ":"1f6e2","๐ณ๏ธ":"1f6f3","โด๏ธ":"26f4","๐ฅ๏ธ":"1f6e5","โ๏ธ":"2708","๐ฉ๏ธ":"1f6e9","๐ฐ๏ธ":"1f6f0","๐๏ธ":"1f6ce","โฑ๏ธ":"23f1","โฒ๏ธ":"23f2","๐ฐ๏ธ":"1f570","๐ก๏ธ":"1f321","โ๏ธ":"2600","โ๏ธ":"2601","โ๏ธ":"26c8","๐ค๏ธ":"1f324","๐ฅ๏ธ":"1f325","๐ฆ๏ธ":"1f326","๐ง๏ธ":"1f327","๐จ๏ธ":"1f328","๐ฉ๏ธ":"1f329","๐ช๏ธ":"1f32a","๐ซ๏ธ":"1f32b","๐ฌ๏ธ":"1f32c","โ๏ธ":"2602","โฑ๏ธ":"26f1","โ๏ธ":"2744","โ๏ธ":"2603","โ๏ธ":"2604","๐๏ธ":"1f397","๐๏ธ":"1f39f","๐๏ธ":"1f396","โธ๏ธ":"26f8","๐น๏ธ":"1f579","โ ๏ธ":"2660","โฅ๏ธ":"2665","โฆ๏ธ":"2666","โฃ๏ธ":"2663","โ๏ธ":"265f","๐ผ๏ธ":"1f5bc","๐ถ๏ธ":"1f576","๐๏ธ":"1f6cd","โ๏ธ":"26d1","๐๏ธ":"1f399","๐๏ธ":"1f39a","๐๏ธ":"1f39b","โ๏ธ":"260e","๐ฅ๏ธ":"1f5a5","๐จ๏ธ":"1f5a8","โจ๏ธ":"2328","๐ฑ๏ธ":"1f5b1","๐ฒ๏ธ":"1f5b2","๐๏ธ":"1f39e","๐ฝ๏ธ":"1f4fd","๐ฏ๏ธ":"1f56f","๐๏ธ":"1f5de","๐ท๏ธ":"1f3f7","โ๏ธ":"2709","๐ณ๏ธ":"1f5f3","โ๏ธ":"270f","โ๏ธ":"2712","๐๏ธ":"1f58b","๐๏ธ":"1f58a","๐๏ธ":"1f58c","๐๏ธ":"1f58d","๐๏ธ":"1f5c2","๐๏ธ":"1f5d2","๐๏ธ":"1f5d3","๐๏ธ":"1f587","โ๏ธ":"2702","๐๏ธ":"1f5c3","๐๏ธ":"1f5c4","๐๏ธ":"1f5d1","๐๏ธ":"1f5dd","โ๏ธ":"26cf","โ๏ธ":"2692","๐ ๏ธ":"1f6e0","๐ก๏ธ":"1f5e1","โ๏ธ":"2694","๐ก๏ธ":"1f6e1","โ๏ธ":"2699","๐๏ธ":"1f5dc","โ๏ธ":"2696","โ๏ธ":"26d3","โ๏ธ":"2697","๐๏ธ":"1f6cf","๐๏ธ":"1f6cb","โฐ๏ธ":"26b0","โฑ๏ธ":"26b1","โ ๏ธ":"26a0","โข๏ธ":"2622","โฃ๏ธ":"2623","โฌ๏ธ":"2b06","โ๏ธ":"2197","โก๏ธ":"27a1","โ๏ธ":"2198","โฌ๏ธ":"2b07","โ๏ธ":"2199","โฌ ๏ธ":"2b05","โ๏ธ":"2196","โ๏ธ":"2195","โ๏ธ":"2194","โฉ๏ธ":"21a9","โช๏ธ":"21aa","โคด๏ธ":"2934","โคต๏ธ":"2935","โ๏ธ":"269b","๐๏ธ":"1f549","โก๏ธ":"2721","โธ๏ธ":"2638","โฏ๏ธ":"262f","โ๏ธ":"271d","โฆ๏ธ":"2626","โช๏ธ":"262a","โฎ๏ธ":"262e","โถ๏ธ":"25b6","โญ๏ธ":"23ed","โฏ๏ธ":"23ef","โ๏ธ":"25c0","โฎ๏ธ":"23ee","โธ๏ธ":"23f8","โน๏ธ":"23f9","โบ๏ธ":"23fa","โ๏ธ":"23cf","โ๏ธ":"2640","โ๏ธ":"2642","โง๏ธ":"26a7","โ๏ธ":"2716","โพ๏ธ":"267e","โผ๏ธ":"203c","โ๏ธ":"2049","ใฐ๏ธ":"3030","โ๏ธ":"2695","โป๏ธ":"267b","โ๏ธ":"269c","โ๏ธ":"2611","โ๏ธ":"2714","ใฝ๏ธ":"303d","โณ๏ธ":"2733","โด๏ธ":"2734","โ๏ธ":"2747","ยฉ๏ธ":"a9","ยฎ๏ธ":"ae","โข๏ธ":"2122","#โฃ":"23-20e3","*โฃ":"2a-20e3","0โฃ":"30-20e3","1โฃ":"31-20e3","2โฃ":"32-20e3","3โฃ":"33-20e3","4โฃ":"34-20e3","5โฃ":"35-20e3","6โฃ":"36-20e3","7โฃ":"37-20e3","8โฃ":"38-20e3","9โฃ":"39-20e3","๐ ฐ๏ธ":"1f170","๐ ฑ๏ธ":"1f171","โน๏ธ":"2139","โ๏ธ":"24c2","๐ พ๏ธ":"1f17e","๐ ฟ๏ธ":"1f17f","๐๏ธ":"1f202","๐ท๏ธ":"1f237","ใ๏ธ":"3297","ใ๏ธ":"3299","โผ๏ธ":"25fc","โป๏ธ":"25fb","โช๏ธ":"25aa","โซ๏ธ":"25ab","๐ณ๏ธ":"1f3f3","๐ฆ๐จ":"1f1e6-1f1e8","๐ฆ๐ฉ":"1f1e6-1f1e9","๐ฆ๐ช":"1f1e6-1f1ea","๐ฆ๐ซ":"1f1e6-1f1eb","๐ฆ๐ฌ":"1f1e6-1f1ec","๐ฆ๐ฎ":"1f1e6-1f1ee","๐ฆ๐ฑ":"1f1e6-1f1f1","๐ฆ๐ฒ":"1f1e6-1f1f2","๐ฆ๐ด":"1f1e6-1f1f4","๐ฆ๐ถ":"1f1e6-1f1f6","๐ฆ๐ท":"1f1e6-1f1f7","๐ฆ๐ธ":"1f1e6-1f1f8","๐ฆ๐น":"1f1e6-1f1f9","๐ฆ๐บ":"1f1e6-1f1fa","๐ฆ๐ผ":"1f1e6-1f1fc","๐ฆ๐ฝ":"1f1e6-1f1fd","๐ฆ๐ฟ":"1f1e6-1f1ff","๐ง๐ฆ":"1f1e7-1f1e6","๐ง๐ง":"1f1e7-1f1e7","๐ง๐ฉ":"1f1e7-1f1e9","๐ง๐ช":"1f1e7-1f1ea","๐ง๐ซ":"1f1e7-1f1eb","๐ง๐ฌ":"1f1e7-1f1ec","๐ง๐ญ":"1f1e7-1f1ed","๐ง๐ฎ":"1f1e7-1f1ee","๐ง๐ฏ":"1f1e7-1f1ef","๐ง๐ฑ":"1f1e7-1f1f1","๐ง๐ฒ":"1f1e7-1f1f2","๐ง๐ณ":"1f1e7-1f1f3","๐ง๐ด":"1f1e7-1f1f4","๐ง๐ถ":"1f1e7-1f1f6","๐ง๐ท":"1f1e7-1f1f7","๐ง๐ธ":"1f1e7-1f1f8","๐ง๐น":"1f1e7-1f1f9","๐ง๐ป":"1f1e7-1f1fb","๐ง๐ผ":"1f1e7-1f1fc","๐ง๐พ":"1f1e7-1f1fe","๐ง๐ฟ":"1f1e7-1f1ff","๐จ๐ฆ":"1f1e8-1f1e6","๐จ๐จ":"1f1e8-1f1e8","๐จ๐ฉ":"1f1e8-1f1e9","๐จ๐ซ":"1f1e8-1f1eb","๐จ๐ฌ":"1f1e8-1f1ec","๐จ๐ญ":"1f1e8-1f1ed","๐จ๐ฎ":"1f1e8-1f1ee","๐จ๐ฐ":"1f1e8-1f1f0","๐จ๐ฑ":"1f1e8-1f1f1","๐จ๐ฒ":"1f1e8-1f1f2","๐จ๐ณ":"1f1e8-1f1f3","๐จ๐ด":"1f1e8-1f1f4","๐จ๐ต":"1f1e8-1f1f5","๐จ๐ท":"1f1e8-1f1f7","๐จ๐บ":"1f1e8-1f1fa","๐จ๐ป":"1f1e8-1f1fb","๐จ๐ผ":"1f1e8-1f1fc","๐จ๐ฝ":"1f1e8-1f1fd","๐จ๐พ":"1f1e8-1f1fe","๐จ๐ฟ":"1f1e8-1f1ff","๐ฉ๐ช":"1f1e9-1f1ea","๐ฉ๐ฌ":"1f1e9-1f1ec","๐ฉ๐ฏ":"1f1e9-1f1ef","๐ฉ๐ฐ":"1f1e9-1f1f0","๐ฉ๐ฒ":"1f1e9-1f1f2","๐ฉ๐ด":"1f1e9-1f1f4","๐ฉ๐ฟ":"1f1e9-1f1ff","๐ช๐ฆ":"1f1ea-1f1e6","๐ช๐จ":"1f1ea-1f1e8","๐ช๐ช":"1f1ea-1f1ea","๐ช๐ฌ":"1f1ea-1f1ec","๐ช๐ญ":"1f1ea-1f1ed","๐ช๐ท":"1f1ea-1f1f7","๐ช๐ธ":"1f1ea-1f1f8","๐ช๐น":"1f1ea-1f1f9","๐ช๐บ":"1f1ea-1f1fa","๐ซ๐ฎ":"1f1eb-1f1ee","๐ซ๐ฏ":"1f1eb-1f1ef","๐ซ๐ฐ":"1f1eb-1f1f0","๐ซ๐ฒ":"1f1eb-1f1f2","๐ซ๐ด":"1f1eb-1f1f4","๐ซ๐ท":"1f1eb-1f1f7","๐ฌ๐ฆ":"1f1ec-1f1e6","๐ฌ๐ง":"1f1ec-1f1e7","๐ฌ๐ฉ":"1f1ec-1f1e9","๐ฌ๐ช":"1f1ec-1f1ea","๐ฌ๐ซ":"1f1ec-1f1eb","๐ฌ๐ฌ":"1f1ec-1f1ec","๐ฌ๐ญ":"1f1ec-1f1ed","๐ฌ๐ฎ":"1f1ec-1f1ee","๐ฌ๐ฑ":"1f1ec-1f1f1","๐ฌ๐ฒ":"1f1ec-1f1f2","๐ฌ๐ณ":"1f1ec-1f1f3","๐ฌ๐ต":"1f1ec-1f1f5","๐ฌ๐ถ":"1f1ec-1f1f6","๐ฌ๐ท":"1f1ec-1f1f7","๐ฌ๐ธ":"1f1ec-1f1f8","๐ฌ๐น":"1f1ec-1f1f9","๐ฌ๐บ":"1f1ec-1f1fa","๐ฌ๐ผ":"1f1ec-1f1fc","๐ฌ๐พ":"1f1ec-1f1fe","๐ญ๐ฐ":"1f1ed-1f1f0","๐ญ๐ฒ":"1f1ed-1f1f2","๐ญ๐ณ":"1f1ed-1f1f3","๐ญ๐ท":"1f1ed-1f1f7","๐ญ๐น":"1f1ed-1f1f9","๐ญ๐บ":"1f1ed-1f1fa","๐ฎ๐จ":"1f1ee-1f1e8","๐ฎ๐ฉ":"1f1ee-1f1e9","๐ฎ๐ช":"1f1ee-1f1ea","๐ฎ๐ฑ":"1f1ee-1f1f1","๐ฎ๐ฒ":"1f1ee-1f1f2","๐ฎ๐ณ":"1f1ee-1f1f3","๐ฎ๐ด":"1f1ee-1f1f4","๐ฎ๐ถ":"1f1ee-1f1f6","๐ฎ๐ท":"1f1ee-1f1f7","๐ฎ๐ธ":"1f1ee-1f1f8","๐ฎ๐น":"1f1ee-1f1f9","๐ฏ๐ช":"1f1ef-1f1ea","๐ฏ๐ฒ":"1f1ef-1f1f2","๐ฏ๐ด":"1f1ef-1f1f4","๐ฏ๐ต":"1f1ef-1f1f5","๐ฐ๐ช":"1f1f0-1f1ea","๐ฐ๐ฌ":"1f1f0-1f1ec","๐ฐ๐ญ":"1f1f0-1f1ed","๐ฐ๐ฎ":"1f1f0-1f1ee","๐ฐ๐ฒ":"1f1f0-1f1f2","๐ฐ๐ณ":"1f1f0-1f1f3","๐ฐ๐ต":"1f1f0-1f1f5","๐ฐ๐ท":"1f1f0-1f1f7","๐ฐ๐ผ":"1f1f0-1f1fc","๐ฐ๐พ":"1f1f0-1f1fe","๐ฐ๐ฟ":"1f1f0-1f1ff","๐ฑ๐ฆ":"1f1f1-1f1e6","๐ฑ๐ง":"1f1f1-1f1e7","๐ฑ๐จ":"1f1f1-1f1e8","๐ฑ๐ฎ":"1f1f1-1f1ee","๐ฑ๐ฐ":"1f1f1-1f1f0","๐ฑ๐ท":"1f1f1-1f1f7","๐ฑ๐ธ":"1f1f1-1f1f8","๐ฑ๐น":"1f1f1-1f1f9","๐ฑ๐บ":"1f1f1-1f1fa","๐ฑ๐ป":"1f1f1-1f1fb","๐ฑ๐พ":"1f1f1-1f1fe","๐ฒ๐ฆ":"1f1f2-1f1e6","๐ฒ๐จ":"1f1f2-1f1e8","๐ฒ๐ฉ":"1f1f2-1f1e9","๐ฒ๐ช":"1f1f2-1f1ea","๐ฒ๐ซ":"1f1f2-1f1eb","๐ฒ๐ฌ":"1f1f2-1f1ec","๐ฒ๐ญ":"1f1f2-1f1ed","๐ฒ๐ฐ":"1f1f2-1f1f0","๐ฒ๐ฑ":"1f1f2-1f1f1","๐ฒ๐ฒ":"1f1f2-1f1f2","๐ฒ๐ณ":"1f1f2-1f1f3","๐ฒ๐ด":"1f1f2-1f1f4","๐ฒ๐ต":"1f1f2-1f1f5","๐ฒ๐ถ":"1f1f2-1f1f6","๐ฒ๐ท":"1f1f2-1f1f7","๐ฒ๐ธ":"1f1f2-1f1f8","๐ฒ๐น":"1f1f2-1f1f9","๐ฒ๐บ":"1f1f2-1f1fa","๐ฒ๐ป":"1f1f2-1f1fb","๐ฒ๐ผ":"1f1f2-1f1fc","๐ฒ๐ฝ":"1f1f2-1f1fd","๐ฒ๐พ":"1f1f2-1f1fe","๐ฒ๐ฟ":"1f1f2-1f1ff","๐ณ๐ฆ":"1f1f3-1f1e6","๐ณ๐จ":"1f1f3-1f1e8","๐ณ๐ช":"1f1f3-1f1ea","๐ณ๐ซ":"1f1f3-1f1eb","๐ณ๐ฌ":"1f1f3-1f1ec","๐ณ๐ฎ":"1f1f3-1f1ee","๐ณ๐ฑ":"1f1f3-1f1f1","๐ณ๐ด":"1f1f3-1f1f4","๐ณ๐ต":"1f1f3-1f1f5","๐ณ๐ท":"1f1f3-1f1f7","๐ณ๐บ":"1f1f3-1f1fa","๐ณ๐ฟ":"1f1f3-1f1ff","๐ด๐ฒ":"1f1f4-1f1f2","๐ต๐ฆ":"1f1f5-1f1e6","๐ต๐ช":"1f1f5-1f1ea","๐ต๐ซ":"1f1f5-1f1eb","๐ต๐ฌ":"1f1f5-1f1ec","๐ต๐ญ":"1f1f5-1f1ed","๐ต๐ฐ":"1f1f5-1f1f0","๐ต๐ฑ":"1f1f5-1f1f1","๐ต๐ฒ":"1f1f5-1f1f2","๐ต๐ณ":"1f1f5-1f1f3","๐ต๐ท":"1f1f5-1f1f7","๐ต๐ธ":"1f1f5-1f1f8","๐ต๐น":"1f1f5-1f1f9","๐ต๐ผ":"1f1f5-1f1fc","๐ต๐พ":"1f1f5-1f1fe","๐ถ๐ฆ":"1f1f6-1f1e6","๐ท๐ช":"1f1f7-1f1ea","๐ท๐ด":"1f1f7-1f1f4","๐ท๐ธ":"1f1f7-1f1f8","๐ท๐บ":"1f1f7-1f1fa","๐ท๐ผ":"1f1f7-1f1fc","๐ธ๐ฆ":"1f1f8-1f1e6","๐ธ๐ง":"1f1f8-1f1e7","๐ธ๐จ":"1f1f8-1f1e8","๐ธ๐ฉ":"1f1f8-1f1e9","๐ธ๐ช":"1f1f8-1f1ea","๐ธ๐ฌ":"1f1f8-1f1ec","๐ธ๐ญ":"1f1f8-1f1ed","๐ธ๐ฎ":"1f1f8-1f1ee","๐ธ๐ฏ":"1f1f8-1f1ef","๐ธ๐ฐ":"1f1f8-1f1f0","๐ธ๐ฑ":"1f1f8-1f1f1","๐ธ๐ฒ":"1f1f8-1f1f2","๐ธ๐ณ":"1f1f8-1f1f3","๐ธ๐ด":"1f1f8-1f1f4","๐ธ๐ท":"1f1f8-1f1f7","๐ธ๐ธ":"1f1f8-1f1f8","๐ธ๐น":"1f1f8-1f1f9","๐ธ๐ป":"1f1f8-1f1fb","๐ธ๐ฝ":"1f1f8-1f1fd","๐ธ๐พ":"1f1f8-1f1fe","๐ธ๐ฟ":"1f1f8-1f1ff","๐น๐ฆ":"1f1f9-1f1e6","๐น๐จ":"1f1f9-1f1e8","๐น๐ฉ":"1f1f9-1f1e9","๐น๐ซ":"1f1f9-1f1eb","๐น๐ฌ":"1f1f9-1f1ec","๐น๐ญ":"1f1f9-1f1ed","๐น๐ฏ":"1f1f9-1f1ef","๐น๐ฐ":"1f1f9-1f1f0","๐น๐ฑ":"1f1f9-1f1f1","๐น๐ฒ":"1f1f9-1f1f2","๐น๐ณ":"1f1f9-1f1f3","๐น๐ด":"1f1f9-1f1f4","๐น๐ท":"1f1f9-1f1f7","๐น๐น":"1f1f9-1f1f9","๐น๐ป":"1f1f9-1f1fb","๐น๐ผ":"1f1f9-1f1fc","๐น๐ฟ":"1f1f9-1f1ff","๐บ๐ฆ":"1f1fa-1f1e6","๐บ๐ฌ":"1f1fa-1f1ec","๐บ๐ฒ":"1f1fa-1f1f2","๐บ๐ณ":"1f1fa-1f1f3","๐บ๐ธ":"1f1fa-1f1f8","๐บ๐พ":"1f1fa-1f1fe","๐บ๐ฟ":"1f1fa-1f1ff","๐ป๐ฆ":"1f1fb-1f1e6","๐ป๐จ":"1f1fb-1f1e8","๐ป๐ช":"1f1fb-1f1ea","๐ป๐ฌ":"1f1fb-1f1ec","๐ป๐ฎ":"1f1fb-1f1ee","๐ป๐ณ":"1f1fb-1f1f3","๐ป๐บ":"1f1fb-1f1fa","๐ผ๐ซ":"1f1fc-1f1eb","๐ผ๐ธ":"1f1fc-1f1f8","๐ฝ๐ฐ":"1f1fd-1f1f0","๐พ๐ช":"1f1fe-1f1ea","๐พ๐น":"1f1fe-1f1f9","๐ฟ๐ฆ":"1f1ff-1f1e6","๐ฟ๐ฒ":"1f1ff-1f1f2","๐ฟ๐ผ":"1f1ff-1f1fc","๐ถโ๐ซ":"1f636-200d-1f32b-fe0f","๐ฎโ๐จ":"1f62e-200d-1f4a8","๐ตโ๐ซ":"1f635-200d-1f4ab","โคโ๐ฅ":"2764-fe0f-200d-1f525","โคโ๐ฉน":"2764-fe0f-200d-1fa79","๐โ๐จ":"1f441-200d-1f5e8","๐งโโ":"1f9d4-200d-2642-fe0f","๐งโโ":"1f9d4-200d-2640-fe0f","๐จโ๐ฆฐ":"1f468-200d-1f9b0","๐จโ๐ฆฑ":"1f468-200d-1f9b1","๐จโ๐ฆณ":"1f468-200d-1f9b3","๐จโ๐ฆฒ":"1f468-200d-1f9b2","๐ฉโ๐ฆฐ":"1f469-200d-1f9b0","๐งโ๐ฆฐ":"1f9d1-200d-1f9b0","๐ฉโ๐ฆฑ":"1f469-200d-1f9b1","๐งโ๐ฆฑ":"1f9d1-200d-1f9b1","๐ฉโ๐ฆณ":"1f469-200d-1f9b3","๐งโ๐ฆณ":"1f9d1-200d-1f9b3","๐ฉโ๐ฆฒ":"1f469-200d-1f9b2","๐งโ๐ฆฒ":"1f9d1-200d-1f9b2","๐ฑโโ":"1f471-200d-2640-fe0f","๐ฑโโ":"1f471-200d-2642-fe0f","๐โโ":"1f64d-200d-2642-fe0f","๐โโ":"1f64d-200d-2640-fe0f","๐โโ":"1f64e-200d-2642-fe0f","๐โโ":"1f64e-200d-2640-fe0f","๐ โโ":"1f645-200d-2642-fe0f","๐ โโ":"1f645-200d-2640-fe0f","๐โโ":"1f646-200d-2642-fe0f","๐โโ":"1f646-200d-2640-fe0f","๐โโ":"1f481-200d-2642-fe0f","๐โโ":"1f481-200d-2640-fe0f","๐โโ":"1f64b-200d-2642-fe0f","๐โโ":"1f64b-200d-2640-fe0f","๐งโโ":"1f9cf-200d-2642-fe0f","๐งโโ":"1f9cf-200d-2640-fe0f","๐โโ":"1f647-200d-2642-fe0f","๐โโ":"1f647-200d-2640-fe0f","๐คฆโโ":"1f926-200d-2642-fe0f","๐คฆโโ":"1f926-200d-2640-fe0f","๐คทโโ":"1f937-200d-2642-fe0f","๐คทโโ":"1f937-200d-2640-fe0f","๐งโโ":"1f9d1-200d-2695-fe0f","๐จโโ":"1f468-200d-2695-fe0f","๐ฉโโ":"1f469-200d-2695-fe0f","๐งโ๐":"1f9d1-200d-1f393","๐จโ๐":"1f468-200d-1f393","๐ฉโ๐":"1f469-200d-1f393","๐งโ๐ซ":"1f9d1-200d-1f3eb","๐จโ๐ซ":"1f468-200d-1f3eb","๐ฉโ๐ซ":"1f469-200d-1f3eb","๐งโโ":"1f9d1-200d-2696-fe0f","๐จโโ":"1f468-200d-2696-fe0f","๐ฉโโ":"1f469-200d-2696-fe0f","๐งโ๐พ":"1f9d1-200d-1f33e","๐จโ๐พ":"1f468-200d-1f33e","๐ฉโ๐พ":"1f469-200d-1f33e","๐งโ๐ณ":"1f9d1-200d-1f373","๐จโ๐ณ":"1f468-200d-1f373","๐ฉโ๐ณ":"1f469-200d-1f373","๐งโ๐ง":"1f9d1-200d-1f527","๐จโ๐ง":"1f468-200d-1f527","๐ฉโ๐ง":"1f469-200d-1f527","๐งโ๐ญ":"1f9d1-200d-1f3ed","๐จโ๐ญ":"1f468-200d-1f3ed","๐ฉโ๐ญ":"1f469-200d-1f3ed","๐งโ๐ผ":"1f9d1-200d-1f4bc","๐จโ๐ผ":"1f468-200d-1f4bc","๐ฉโ๐ผ":"1f469-200d-1f4bc","๐งโ๐ฌ":"1f9d1-200d-1f52c","๐จโ๐ฌ":"1f468-200d-1f52c","๐ฉโ๐ฌ":"1f469-200d-1f52c","๐งโ๐ป":"1f9d1-200d-1f4bb","๐จโ๐ป":"1f468-200d-1f4bb","๐ฉโ๐ป":"1f469-200d-1f4bb","๐งโ๐ค":"1f9d1-200d-1f3a4","๐จโ๐ค":"1f468-200d-1f3a4","๐ฉโ๐ค":"1f469-200d-1f3a4","๐งโ๐จ":"1f9d1-200d-1f3a8","๐จโ๐จ":"1f468-200d-1f3a8","๐ฉโ๐จ":"1f469-200d-1f3a8","๐งโโ":"1f9d1-200d-2708-fe0f","๐จโโ":"1f468-200d-2708-fe0f","๐ฉโโ":"1f469-200d-2708-fe0f","๐งโ๐":"1f9d1-200d-1f680","๐จโ๐":"1f468-200d-1f680","๐ฉโ๐":"1f469-200d-1f680","๐งโ๐":"1f9d1-200d-1f692","๐จโ๐":"1f468-200d-1f692","๐ฉโ๐":"1f469-200d-1f692","๐ฎโโ":"1f46e-200d-2642-fe0f","๐ฎโโ":"1f46e-200d-2640-fe0f","๐ตโโ":"1f575-fe0f-200d-2642-fe0f","๐ตโโ":"1f575-fe0f-200d-2640-fe0f","๐โโ":"1f482-200d-2642-fe0f","๐โโ":"1f482-200d-2640-fe0f","๐ทโโ":"1f477-200d-2642-fe0f","๐ทโโ":"1f477-200d-2640-fe0f","๐ณโโ":"1f473-200d-2642-fe0f","๐ณโโ":"1f473-200d-2640-fe0f","๐คตโโ":"1f935-200d-2642-fe0f","๐คตโโ":"1f935-200d-2640-fe0f","๐ฐโโ":"1f470-200d-2642-fe0f","๐ฐโโ":"1f470-200d-2640-fe0f","๐ฉโ๐ผ":"1f469-200d-1f37c","๐จโ๐ผ":"1f468-200d-1f37c","๐งโ๐ผ":"1f9d1-200d-1f37c","๐งโ๐":"1f9d1-200d-1f384","๐ฆธโโ":"1f9b8-200d-2642-fe0f","๐ฆธโโ":"1f9b8-200d-2640-fe0f","๐ฆนโโ":"1f9b9-200d-2642-fe0f","๐ฆนโโ":"1f9b9-200d-2640-fe0f","๐งโโ":"1f9d9-200d-2642-fe0f","๐งโโ":"1f9d9-200d-2640-fe0f","๐งโโ":"1f9da-200d-2642-fe0f","๐งโโ":"1f9da-200d-2640-fe0f","๐งโโ":"1f9db-200d-2642-fe0f","๐งโโ":"1f9db-200d-2640-fe0f","๐งโโ":"1f9dc-200d-2642-fe0f","๐งโโ":"1f9dc-200d-2640-fe0f","๐งโโ":"1f9dd-200d-2642-fe0f","๐งโโ":"1f9dd-200d-2640-fe0f","๐งโโ":"1f9de-200d-2642-fe0f","๐งโโ":"1f9de-200d-2640-fe0f","๐งโโ":"1f9df-200d-2642-fe0f","๐งโโ":"1f9df-200d-2640-fe0f","๐โโ":"1f486-200d-2642-fe0f","๐โโ":"1f486-200d-2640-fe0f","๐โโ":"1f487-200d-2642-fe0f","๐โโ":"1f487-200d-2640-fe0f","๐ถโโ":"1f6b6-200d-2642-fe0f","๐ถโโ":"1f6b6-200d-2640-fe0f","๐งโโ":"1f9cd-200d-2642-fe0f","๐งโโ":"1f9cd-200d-2640-fe0f","๐งโโ":"1f9ce-200d-2642-fe0f","๐งโโ":"1f9ce-200d-2640-fe0f","๐งโ๐ฆฏ":"1f9d1-200d-1f9af","๐จโ๐ฆฏ":"1f468-200d-1f9af","๐ฉโ๐ฆฏ":"1f469-200d-1f9af","๐งโ๐ฆผ":"1f9d1-200d-1f9bc","๐จโ๐ฆผ":"1f468-200d-1f9bc","๐ฉโ๐ฆผ":"1f469-200d-1f9bc","๐งโ๐ฆฝ":"1f9d1-200d-1f9bd","๐จโ๐ฆฝ":"1f468-200d-1f9bd","๐ฉโ๐ฆฝ":"1f469-200d-1f9bd","๐โโ":"1f3c3-200d-2642-fe0f","๐โโ":"1f3c3-200d-2640-fe0f","๐ฏโโ":"1f46f-200d-2642-fe0f","๐ฏโโ":"1f46f-200d-2640-fe0f","๐งโโ":"1f9d6-200d-2642-fe0f","๐งโโ":"1f9d6-200d-2640-fe0f","๐งโโ":"1f9d7-200d-2642-fe0f","๐งโโ":"1f9d7-200d-2640-fe0f","๐โโ":"1f3cc-fe0f-200d-2642-fe0f","๐โโ":"1f3cc-fe0f-200d-2640-fe0f","๐โโ":"1f3c4-200d-2642-fe0f","๐โโ":"1f3c4-200d-2640-fe0f","๐ฃโโ":"1f6a3-200d-2642-fe0f","๐ฃโโ":"1f6a3-200d-2640-fe0f","๐โโ":"1f3ca-200d-2642-fe0f","๐โโ":"1f3ca-200d-2640-fe0f","โนโโ":"26f9-fe0f-200d-2642-fe0f","โนโโ":"26f9-fe0f-200d-2640-fe0f","๐โโ":"1f3cb-fe0f-200d-2642-fe0f","๐โโ":"1f3cb-fe0f-200d-2640-fe0f","๐ดโโ":"1f6b4-200d-2642-fe0f","๐ดโโ":"1f6b4-200d-2640-fe0f","๐ตโโ":"1f6b5-200d-2642-fe0f","๐ตโโ":"1f6b5-200d-2640-fe0f","๐คธโโ":"1f938-200d-2642-fe0f","๐คธโโ":"1f938-200d-2640-fe0f","๐คผโโ":"1f93c-200d-2642-fe0f","๐คผโโ":"1f93c-200d-2640-fe0f","๐คฝโโ":"1f93d-200d-2642-fe0f","๐คฝโโ":"1f93d-200d-2640-fe0f","๐คพโโ":"1f93e-200d-2642-fe0f","๐คพโโ":"1f93e-200d-2640-fe0f","๐คนโโ":"1f939-200d-2642-fe0f","๐คนโโ":"1f939-200d-2640-fe0f","๐งโโ":"1f9d8-200d-2642-fe0f","๐งโโ":"1f9d8-200d-2640-fe0f","๐จโ๐ฆ":"1f468-200d-1f466","๐จโ๐ง":"1f468-200d-1f467","๐ฉโ๐ฆ":"1f469-200d-1f466","๐ฉโ๐ง":"1f469-200d-1f467","๐โ๐ฆบ":"1f415-200d-1f9ba","๐โโฌ":"1f408-200d-2b1b","๐ปโโ":"1f43b-200d-2744-fe0f","#๏ธโฃ":"23-20e3","*๏ธโฃ":"2a-20e3","0๏ธโฃ":"30-20e3","1๏ธโฃ":"31-20e3","2๏ธโฃ":"32-20e3","3๏ธโฃ":"33-20e3","4๏ธโฃ":"34-20e3","5๏ธโฃ":"35-20e3","6๏ธโฃ":"36-20e3","7๏ธโฃ":"37-20e3","8๏ธโฃ":"38-20e3","9๏ธโฃ":"39-20e3","๐ณโ๐":"1f3f3-fe0f-200d-1f308","๐ณโโง":"1f3f3-fe0f-200d-26a7-fe0f","๐ดโโ ":"1f3f4-200d-2620-fe0f","๐ถโ๐ซ๏ธ":"1f636-200d-1f32b-fe0f","โค๏ธโ๐ฅ":"2764-fe0f-200d-1f525","โค๏ธโ๐ฉน":"2764-fe0f-200d-1fa79","๐โ๐จ๏ธ":"1f441-200d-1f5e8","๐๏ธโ๐จ":"1f441-200d-1f5e8","๐งโโ๏ธ":"1f9d4-200d-2642-fe0f","๐ง๐ปโโ":"1f9d4-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d4-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d4-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d4-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d4-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d4-200d-2640-fe0f","๐ง๐ปโโ":"1f9d4-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d4-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d4-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d4-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d4-1f3ff-200d-2640-fe0f","๐จ๐ปโ๐ฆฐ":"1f468-1f3fb-200d-1f9b0","๐จ๐ผโ๐ฆฐ":"1f468-1f3fc-200d-1f9b0","๐จ๐ฝโ๐ฆฐ":"1f468-1f3fd-200d-1f9b0","๐จ๐พโ๐ฆฐ":"1f468-1f3fe-200d-1f9b0","๐จ๐ฟโ๐ฆฐ":"1f468-1f3ff-200d-1f9b0","๐จ๐ปโ๐ฆฑ":"1f468-1f3fb-200d-1f9b1","๐จ๐ผโ๐ฆฑ":"1f468-1f3fc-200d-1f9b1","๐จ๐ฝโ๐ฆฑ":"1f468-1f3fd-200d-1f9b1","๐จ๐พโ๐ฆฑ":"1f468-1f3fe-200d-1f9b1","๐จ๐ฟโ๐ฆฑ":"1f468-1f3ff-200d-1f9b1","๐จ๐ปโ๐ฆณ":"1f468-1f3fb-200d-1f9b3","๐จ๐ผโ๐ฆณ":"1f468-1f3fc-200d-1f9b3","๐จ๐ฝโ๐ฆณ":"1f468-1f3fd-200d-1f9b3","๐จ๐พโ๐ฆณ":"1f468-1f3fe-200d-1f9b3","๐จ๐ฟโ๐ฆณ":"1f468-1f3ff-200d-1f9b3","๐จ๐ปโ๐ฆฒ":"1f468-1f3fb-200d-1f9b2","๐จ๐ผโ๐ฆฒ":"1f468-1f3fc-200d-1f9b2","๐จ๐ฝโ๐ฆฒ":"1f468-1f3fd-200d-1f9b2","๐จ๐พโ๐ฆฒ":"1f468-1f3fe-200d-1f9b2","๐จ๐ฟโ๐ฆฒ":"1f468-1f3ff-200d-1f9b2","๐ฉ๐ปโ๐ฆฐ":"1f469-1f3fb-200d-1f9b0","๐ฉ๐ผโ๐ฆฐ":"1f469-1f3fc-200d-1f9b0","๐ฉ๐ฝโ๐ฆฐ":"1f469-1f3fd-200d-1f9b0","๐ฉ๐พโ๐ฆฐ":"1f469-1f3fe-200d-1f9b0","๐ฉ๐ฟโ๐ฆฐ":"1f469-1f3ff-200d-1f9b0","๐ง๐ปโ๐ฆฐ":"1f9d1-1f3fb-200d-1f9b0","๐ง๐ผโ๐ฆฐ":"1f9d1-1f3fc-200d-1f9b0","๐ง๐ฝโ๐ฆฐ":"1f9d1-1f3fd-200d-1f9b0","๐ง๐พโ๐ฆฐ":"1f9d1-1f3fe-200d-1f9b0","๐ง๐ฟโ๐ฆฐ":"1f9d1-1f3ff-200d-1f9b0","๐ฉ๐ปโ๐ฆฑ":"1f469-1f3fb-200d-1f9b1","๐ฉ๐ผโ๐ฆฑ":"1f469-1f3fc-200d-1f9b1","๐ฉ๐ฝโ๐ฆฑ":"1f469-1f3fd-200d-1f9b1","๐ฉ๐พโ๐ฆฑ":"1f469-1f3fe-200d-1f9b1","๐ฉ๐ฟโ๐ฆฑ":"1f469-1f3ff-200d-1f9b1","๐ง๐ปโ๐ฆฑ":"1f9d1-1f3fb-200d-1f9b1","๐ง๐ผโ๐ฆฑ":"1f9d1-1f3fc-200d-1f9b1","๐ง๐ฝโ๐ฆฑ":"1f9d1-1f3fd-200d-1f9b1","๐ง๐พโ๐ฆฑ":"1f9d1-1f3fe-200d-1f9b1","๐ง๐ฟโ๐ฆฑ":"1f9d1-1f3ff-200d-1f9b1","๐ฉ๐ปโ๐ฆณ":"1f469-1f3fb-200d-1f9b3","๐ฉ๐ผโ๐ฆณ":"1f469-1f3fc-200d-1f9b3","๐ฉ๐ฝโ๐ฆณ":"1f469-1f3fd-200d-1f9b3","๐ฉ๐พโ๐ฆณ":"1f469-1f3fe-200d-1f9b3","๐ฉ๐ฟโ๐ฆณ":"1f469-1f3ff-200d-1f9b3","๐ง๐ปโ๐ฆณ":"1f9d1-1f3fb-200d-1f9b3","๐ง๐ผโ๐ฆณ":"1f9d1-1f3fc-200d-1f9b3","๐ง๐ฝโ๐ฆณ":"1f9d1-1f3fd-200d-1f9b3","๐ง๐พโ๐ฆณ":"1f9d1-1f3fe-200d-1f9b3","๐ง๐ฟโ๐ฆณ":"1f9d1-1f3ff-200d-1f9b3","๐ฉ๐ปโ๐ฆฒ":"1f469-1f3fb-200d-1f9b2","๐ฉ๐ผโ๐ฆฒ":"1f469-1f3fc-200d-1f9b2","๐ฉ๐ฝโ๐ฆฒ":"1f469-1f3fd-200d-1f9b2","๐ฉ๐พโ๐ฆฒ":"1f469-1f3fe-200d-1f9b2","๐ฉ๐ฟโ๐ฆฒ":"1f469-1f3ff-200d-1f9b2","๐ง๐ปโ๐ฆฒ":"1f9d1-1f3fb-200d-1f9b2","๐ง๐ผโ๐ฆฒ":"1f9d1-1f3fc-200d-1f9b2","๐ง๐ฝโ๐ฆฒ":"1f9d1-1f3fd-200d-1f9b2","๐ง๐พโ๐ฆฒ":"1f9d1-1f3fe-200d-1f9b2","๐ง๐ฟโ๐ฆฒ":"1f9d1-1f3ff-200d-1f9b2","๐ฑโโ๏ธ":"1f471-200d-2640-fe0f","๐ฑ๐ปโโ":"1f471-1f3fb-200d-2640-fe0f","๐ฑ๐ผโโ":"1f471-1f3fc-200d-2640-fe0f","๐ฑ๐ฝโโ":"1f471-1f3fd-200d-2640-fe0f","๐ฑ๐พโโ":"1f471-1f3fe-200d-2640-fe0f","๐ฑ๐ฟโโ":"1f471-1f3ff-200d-2640-fe0f","๐ฑโโ๏ธ":"1f471-200d-2642-fe0f","๐ฑ๐ปโโ":"1f471-1f3fb-200d-2642-fe0f","๐ฑ๐ผโโ":"1f471-1f3fc-200d-2642-fe0f","๐ฑ๐ฝโโ":"1f471-1f3fd-200d-2642-fe0f","๐ฑ๐พโโ":"1f471-1f3fe-200d-2642-fe0f","๐ฑ๐ฟโโ":"1f471-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64d-200d-2642-fe0f","๐๐ปโโ":"1f64d-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f64d-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f64d-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f64d-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f64d-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64d-200d-2640-fe0f","๐๐ปโโ":"1f64d-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f64d-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f64d-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f64d-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f64d-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f64e-200d-2642-fe0f","๐๐ปโโ":"1f64e-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f64e-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f64e-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f64e-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f64e-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64e-200d-2640-fe0f","๐๐ปโโ":"1f64e-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f64e-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f64e-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f64e-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f64e-1f3ff-200d-2640-fe0f","๐ โโ๏ธ":"1f645-200d-2642-fe0f","๐ ๐ปโโ":"1f645-1f3fb-200d-2642-fe0f","๐ ๐ผโโ":"1f645-1f3fc-200d-2642-fe0f","๐ ๐ฝโโ":"1f645-1f3fd-200d-2642-fe0f","๐ ๐พโโ":"1f645-1f3fe-200d-2642-fe0f","๐ ๐ฟโโ":"1f645-1f3ff-200d-2642-fe0f","๐ โโ๏ธ":"1f645-200d-2640-fe0f","๐ ๐ปโโ":"1f645-1f3fb-200d-2640-fe0f","๐ ๐ผโโ":"1f645-1f3fc-200d-2640-fe0f","๐ ๐ฝโโ":"1f645-1f3fd-200d-2640-fe0f","๐ ๐พโโ":"1f645-1f3fe-200d-2640-fe0f","๐ ๐ฟโโ":"1f645-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f646-200d-2642-fe0f","๐๐ปโโ":"1f646-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f646-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f646-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f646-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f646-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f646-200d-2640-fe0f","๐๐ปโโ":"1f646-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f646-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f646-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f646-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f646-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f481-200d-2642-fe0f","๐๐ปโโ":"1f481-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f481-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f481-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f481-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f481-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f481-200d-2640-fe0f","๐๐ปโโ":"1f481-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f481-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f481-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f481-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f481-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f64b-200d-2642-fe0f","๐๐ปโโ":"1f64b-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f64b-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f64b-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f64b-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f64b-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64b-200d-2640-fe0f","๐๐ปโโ":"1f64b-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f64b-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f64b-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f64b-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f64b-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9cf-200d-2642-fe0f","๐ง๐ปโโ":"1f9cf-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9cf-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9cf-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9cf-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9cf-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9cf-200d-2640-fe0f","๐ง๐ปโโ":"1f9cf-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9cf-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9cf-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9cf-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9cf-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f647-200d-2642-fe0f","๐๐ปโโ":"1f647-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f647-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f647-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f647-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f647-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f647-200d-2640-fe0f","๐๐ปโโ":"1f647-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f647-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f647-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f647-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f647-1f3ff-200d-2640-fe0f","๐คฆโโ๏ธ":"1f926-200d-2642-fe0f","๐คฆ๐ปโโ":"1f926-1f3fb-200d-2642-fe0f","๐คฆ๐ผโโ":"1f926-1f3fc-200d-2642-fe0f","๐คฆ๐ฝโโ":"1f926-1f3fd-200d-2642-fe0f","๐คฆ๐พโโ":"1f926-1f3fe-200d-2642-fe0f","๐คฆ๐ฟโโ":"1f926-1f3ff-200d-2642-fe0f","๐คฆโโ๏ธ":"1f926-200d-2640-fe0f","๐คฆ๐ปโโ":"1f926-1f3fb-200d-2640-fe0f","๐คฆ๐ผโโ":"1f926-1f3fc-200d-2640-fe0f","๐คฆ๐ฝโโ":"1f926-1f3fd-200d-2640-fe0f","๐คฆ๐พโโ":"1f926-1f3fe-200d-2640-fe0f","๐คฆ๐ฟโโ":"1f926-1f3ff-200d-2640-fe0f","๐คทโโ๏ธ":"1f937-200d-2642-fe0f","๐คท๐ปโโ":"1f937-1f3fb-200d-2642-fe0f","๐คท๐ผโโ":"1f937-1f3fc-200d-2642-fe0f","๐คท๐ฝโโ":"1f937-1f3fd-200d-2642-fe0f","๐คท๐พโโ":"1f937-1f3fe-200d-2642-fe0f","๐คท๐ฟโโ":"1f937-1f3ff-200d-2642-fe0f","๐คทโโ๏ธ":"1f937-200d-2640-fe0f","๐คท๐ปโโ":"1f937-1f3fb-200d-2640-fe0f","๐คท๐ผโโ":"1f937-1f3fc-200d-2640-fe0f","๐คท๐ฝโโ":"1f937-1f3fd-200d-2640-fe0f","๐คท๐พโโ":"1f937-1f3fe-200d-2640-fe0f","๐คท๐ฟโโ":"1f937-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d1-200d-2695-fe0f","๐ง๐ปโโ":"1f9d1-1f3fb-200d-2695-fe0f","๐ง๐ผโโ":"1f9d1-1f3fc-200d-2695-fe0f","๐ง๐ฝโโ":"1f9d1-1f3fd-200d-2695-fe0f","๐ง๐พโโ":"1f9d1-1f3fe-200d-2695-fe0f","๐ง๐ฟโโ":"1f9d1-1f3ff-200d-2695-fe0f","๐จโโ๏ธ":"1f468-200d-2695-fe0f","๐จ๐ปโโ":"1f468-1f3fb-200d-2695-fe0f","๐จ๐ผโโ":"1f468-1f3fc-200d-2695-fe0f","๐จ๐ฝโโ":"1f468-1f3fd-200d-2695-fe0f","๐จ๐พโโ":"1f468-1f3fe-200d-2695-fe0f","๐จ๐ฟโโ":"1f468-1f3ff-200d-2695-fe0f","๐ฉโโ๏ธ":"1f469-200d-2695-fe0f","๐ฉ๐ปโโ":"1f469-1f3fb-200d-2695-fe0f","๐ฉ๐ผโโ":"1f469-1f3fc-200d-2695-fe0f","๐ฉ๐ฝโโ":"1f469-1f3fd-200d-2695-fe0f","๐ฉ๐พโโ":"1f469-1f3fe-200d-2695-fe0f","๐ฉ๐ฟโโ":"1f469-1f3ff-200d-2695-fe0f","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f393","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f393","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f393","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f393","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f393","๐จ๐ปโ๐":"1f468-1f3fb-200d-1f393","๐จ๐ผโ๐":"1f468-1f3fc-200d-1f393","๐จ๐ฝโ๐":"1f468-1f3fd-200d-1f393","๐จ๐พโ๐":"1f468-1f3fe-200d-1f393","๐จ๐ฟโ๐":"1f468-1f3ff-200d-1f393","๐ฉ๐ปโ๐":"1f469-1f3fb-200d-1f393","๐ฉ๐ผโ๐":"1f469-1f3fc-200d-1f393","๐ฉ๐ฝโ๐":"1f469-1f3fd-200d-1f393","๐ฉ๐พโ๐":"1f469-1f3fe-200d-1f393","๐ฉ๐ฟโ๐":"1f469-1f3ff-200d-1f393","๐ง๐ปโ๐ซ":"1f9d1-1f3fb-200d-1f3eb","๐ง๐ผโ๐ซ":"1f9d1-1f3fc-200d-1f3eb","๐ง๐ฝโ๐ซ":"1f9d1-1f3fd-200d-1f3eb","๐ง๐พโ๐ซ":"1f9d1-1f3fe-200d-1f3eb","๐ง๐ฟโ๐ซ":"1f9d1-1f3ff-200d-1f3eb","๐จ๐ปโ๐ซ":"1f468-1f3fb-200d-1f3eb","๐จ๐ผโ๐ซ":"1f468-1f3fc-200d-1f3eb","๐จ๐ฝโ๐ซ":"1f468-1f3fd-200d-1f3eb","๐จ๐พโ๐ซ":"1f468-1f3fe-200d-1f3eb","๐จ๐ฟโ๐ซ":"1f468-1f3ff-200d-1f3eb","๐ฉ๐ปโ๐ซ":"1f469-1f3fb-200d-1f3eb","๐ฉ๐ผโ๐ซ":"1f469-1f3fc-200d-1f3eb","๐ฉ๐ฝโ๐ซ":"1f469-1f3fd-200d-1f3eb","๐ฉ๐พโ๐ซ":"1f469-1f3fe-200d-1f3eb","๐ฉ๐ฟโ๐ซ":"1f469-1f3ff-200d-1f3eb","๐งโโ๏ธ":"1f9d1-200d-2696-fe0f","๐ง๐ปโโ":"1f9d1-1f3fb-200d-2696-fe0f","๐ง๐ผโโ":"1f9d1-1f3fc-200d-2696-fe0f","๐ง๐ฝโโ":"1f9d1-1f3fd-200d-2696-fe0f","๐ง๐พโโ":"1f9d1-1f3fe-200d-2696-fe0f","๐ง๐ฟโโ":"1f9d1-1f3ff-200d-2696-fe0f","๐จโโ๏ธ":"1f468-200d-2696-fe0f","๐จ๐ปโโ":"1f468-1f3fb-200d-2696-fe0f","๐จ๐ผโโ":"1f468-1f3fc-200d-2696-fe0f","๐จ๐ฝโโ":"1f468-1f3fd-200d-2696-fe0f","๐จ๐พโโ":"1f468-1f3fe-200d-2696-fe0f","๐จ๐ฟโโ":"1f468-1f3ff-200d-2696-fe0f","๐ฉโโ๏ธ":"1f469-200d-2696-fe0f","๐ฉ๐ปโโ":"1f469-1f3fb-200d-2696-fe0f","๐ฉ๐ผโโ":"1f469-1f3fc-200d-2696-fe0f","๐ฉ๐ฝโโ":"1f469-1f3fd-200d-2696-fe0f","๐ฉ๐พโโ":"1f469-1f3fe-200d-2696-fe0f","๐ฉ๐ฟโโ":"1f469-1f3ff-200d-2696-fe0f","๐ง๐ปโ๐พ":"1f9d1-1f3fb-200d-1f33e","๐ง๐ผโ๐พ":"1f9d1-1f3fc-200d-1f33e","๐ง๐ฝโ๐พ":"1f9d1-1f3fd-200d-1f33e","๐ง๐พโ๐พ":"1f9d1-1f3fe-200d-1f33e","๐ง๐ฟโ๐พ":"1f9d1-1f3ff-200d-1f33e","๐จ๐ปโ๐พ":"1f468-1f3fb-200d-1f33e","๐จ๐ผโ๐พ":"1f468-1f3fc-200d-1f33e","๐จ๐ฝโ๐พ":"1f468-1f3fd-200d-1f33e","๐จ๐พโ๐พ":"1f468-1f3fe-200d-1f33e","๐จ๐ฟโ๐พ":"1f468-1f3ff-200d-1f33e","๐ฉ๐ปโ๐พ":"1f469-1f3fb-200d-1f33e","๐ฉ๐ผโ๐พ":"1f469-1f3fc-200d-1f33e","๐ฉ๐ฝโ๐พ":"1f469-1f3fd-200d-1f33e","๐ฉ๐พโ๐พ":"1f469-1f3fe-200d-1f33e","๐ฉ๐ฟโ๐พ":"1f469-1f3ff-200d-1f33e","๐ง๐ปโ๐ณ":"1f9d1-1f3fb-200d-1f373","๐ง๐ผโ๐ณ":"1f9d1-1f3fc-200d-1f373","๐ง๐ฝโ๐ณ":"1f9d1-1f3fd-200d-1f373","๐ง๐พโ๐ณ":"1f9d1-1f3fe-200d-1f373","๐ง๐ฟโ๐ณ":"1f9d1-1f3ff-200d-1f373","๐จ๐ปโ๐ณ":"1f468-1f3fb-200d-1f373","๐จ๐ผโ๐ณ":"1f468-1f3fc-200d-1f373","๐จ๐ฝโ๐ณ":"1f468-1f3fd-200d-1f373","๐จ๐พโ๐ณ":"1f468-1f3fe-200d-1f373","๐จ๐ฟโ๐ณ":"1f468-1f3ff-200d-1f373","๐ฉ๐ปโ๐ณ":"1f469-1f3fb-200d-1f373","๐ฉ๐ผโ๐ณ":"1f469-1f3fc-200d-1f373","๐ฉ๐ฝโ๐ณ":"1f469-1f3fd-200d-1f373","๐ฉ๐พโ๐ณ":"1f469-1f3fe-200d-1f373","๐ฉ๐ฟโ๐ณ":"1f469-1f3ff-200d-1f373","๐ง๐ปโ๐ง":"1f9d1-1f3fb-200d-1f527","๐ง๐ผโ๐ง":"1f9d1-1f3fc-200d-1f527","๐ง๐ฝโ๐ง":"1f9d1-1f3fd-200d-1f527","๐ง๐พโ๐ง":"1f9d1-1f3fe-200d-1f527","๐ง๐ฟโ๐ง":"1f9d1-1f3ff-200d-1f527","๐จ๐ปโ๐ง":"1f468-1f3fb-200d-1f527","๐จ๐ผโ๐ง":"1f468-1f3fc-200d-1f527","๐จ๐ฝโ๐ง":"1f468-1f3fd-200d-1f527","๐จ๐พโ๐ง":"1f468-1f3fe-200d-1f527","๐จ๐ฟโ๐ง":"1f468-1f3ff-200d-1f527","๐ฉ๐ปโ๐ง":"1f469-1f3fb-200d-1f527","๐ฉ๐ผโ๐ง":"1f469-1f3fc-200d-1f527","๐ฉ๐ฝโ๐ง":"1f469-1f3fd-200d-1f527","๐ฉ๐พโ๐ง":"1f469-1f3fe-200d-1f527","๐ฉ๐ฟโ๐ง":"1f469-1f3ff-200d-1f527","๐ง๐ปโ๐ญ":"1f9d1-1f3fb-200d-1f3ed","๐ง๐ผโ๐ญ":"1f9d1-1f3fc-200d-1f3ed","๐ง๐ฝโ๐ญ":"1f9d1-1f3fd-200d-1f3ed","๐ง๐พโ๐ญ":"1f9d1-1f3fe-200d-1f3ed","๐ง๐ฟโ๐ญ":"1f9d1-1f3ff-200d-1f3ed","๐จ๐ปโ๐ญ":"1f468-1f3fb-200d-1f3ed","๐จ๐ผโ๐ญ":"1f468-1f3fc-200d-1f3ed","๐จ๐ฝโ๐ญ":"1f468-1f3fd-200d-1f3ed","๐จ๐พโ๐ญ":"1f468-1f3fe-200d-1f3ed","๐จ๐ฟโ๐ญ":"1f468-1f3ff-200d-1f3ed","๐ฉ๐ปโ๐ญ":"1f469-1f3fb-200d-1f3ed","๐ฉ๐ผโ๐ญ":"1f469-1f3fc-200d-1f3ed","๐ฉ๐ฝโ๐ญ":"1f469-1f3fd-200d-1f3ed","๐ฉ๐พโ๐ญ":"1f469-1f3fe-200d-1f3ed","๐ฉ๐ฟโ๐ญ":"1f469-1f3ff-200d-1f3ed","๐ง๐ปโ๐ผ":"1f9d1-1f3fb-200d-1f4bc","๐ง๐ผโ๐ผ":"1f9d1-1f3fc-200d-1f4bc","๐ง๐ฝโ๐ผ":"1f9d1-1f3fd-200d-1f4bc","๐ง๐พโ๐ผ":"1f9d1-1f3fe-200d-1f4bc","๐ง๐ฟโ๐ผ":"1f9d1-1f3ff-200d-1f4bc","๐จ๐ปโ๐ผ":"1f468-1f3fb-200d-1f4bc","๐จ๐ผโ๐ผ":"1f468-1f3fc-200d-1f4bc","๐จ๐ฝโ๐ผ":"1f468-1f3fd-200d-1f4bc","๐จ๐พโ๐ผ":"1f468-1f3fe-200d-1f4bc","๐จ๐ฟโ๐ผ":"1f468-1f3ff-200d-1f4bc","๐ฉ๐ปโ๐ผ":"1f469-1f3fb-200d-1f4bc","๐ฉ๐ผโ๐ผ":"1f469-1f3fc-200d-1f4bc","๐ฉ๐ฝโ๐ผ":"1f469-1f3fd-200d-1f4bc","๐ฉ๐พโ๐ผ":"1f469-1f3fe-200d-1f4bc","๐ฉ๐ฟโ๐ผ":"1f469-1f3ff-200d-1f4bc","๐ง๐ปโ๐ฌ":"1f9d1-1f3fb-200d-1f52c","๐ง๐ผโ๐ฌ":"1f9d1-1f3fc-200d-1f52c","๐ง๐ฝโ๐ฌ":"1f9d1-1f3fd-200d-1f52c","๐ง๐พโ๐ฌ":"1f9d1-1f3fe-200d-1f52c","๐ง๐ฟโ๐ฌ":"1f9d1-1f3ff-200d-1f52c","๐จ๐ปโ๐ฌ":"1f468-1f3fb-200d-1f52c","๐จ๐ผโ๐ฌ":"1f468-1f3fc-200d-1f52c","๐จ๐ฝโ๐ฌ":"1f468-1f3fd-200d-1f52c","๐จ๐พโ๐ฌ":"1f468-1f3fe-200d-1f52c","๐จ๐ฟโ๐ฌ":"1f468-1f3ff-200d-1f52c","๐ฉ๐ปโ๐ฌ":"1f469-1f3fb-200d-1f52c","๐ฉ๐ผโ๐ฌ":"1f469-1f3fc-200d-1f52c","๐ฉ๐ฝโ๐ฌ":"1f469-1f3fd-200d-1f52c","๐ฉ๐พโ๐ฌ":"1f469-1f3fe-200d-1f52c","๐ฉ๐ฟโ๐ฌ":"1f469-1f3ff-200d-1f52c","๐ง๐ปโ๐ป":"1f9d1-1f3fb-200d-1f4bb","๐ง๐ผโ๐ป":"1f9d1-1f3fc-200d-1f4bb","๐ง๐ฝโ๐ป":"1f9d1-1f3fd-200d-1f4bb","๐ง๐พโ๐ป":"1f9d1-1f3fe-200d-1f4bb","๐ง๐ฟโ๐ป":"1f9d1-1f3ff-200d-1f4bb","๐จ๐ปโ๐ป":"1f468-1f3fb-200d-1f4bb","๐จ๐ผโ๐ป":"1f468-1f3fc-200d-1f4bb","๐จ๐ฝโ๐ป":"1f468-1f3fd-200d-1f4bb","๐จ๐พโ๐ป":"1f468-1f3fe-200d-1f4bb","๐จ๐ฟโ๐ป":"1f468-1f3ff-200d-1f4bb","๐ฉ๐ปโ๐ป":"1f469-1f3fb-200d-1f4bb","๐ฉ๐ผโ๐ป":"1f469-1f3fc-200d-1f4bb","๐ฉ๐ฝโ๐ป":"1f469-1f3fd-200d-1f4bb","๐ฉ๐พโ๐ป":"1f469-1f3fe-200d-1f4bb","๐ฉ๐ฟโ๐ป":"1f469-1f3ff-200d-1f4bb","๐ง๐ปโ๐ค":"1f9d1-1f3fb-200d-1f3a4","๐ง๐ผโ๐ค":"1f9d1-1f3fc-200d-1f3a4","๐ง๐ฝโ๐ค":"1f9d1-1f3fd-200d-1f3a4","๐ง๐พโ๐ค":"1f9d1-1f3fe-200d-1f3a4","๐ง๐ฟโ๐ค":"1f9d1-1f3ff-200d-1f3a4","๐จ๐ปโ๐ค":"1f468-1f3fb-200d-1f3a4","๐จ๐ผโ๐ค":"1f468-1f3fc-200d-1f3a4","๐จ๐ฝโ๐ค":"1f468-1f3fd-200d-1f3a4","๐จ๐พโ๐ค":"1f468-1f3fe-200d-1f3a4","๐จ๐ฟโ๐ค":"1f468-1f3ff-200d-1f3a4","๐ฉ๐ปโ๐ค":"1f469-1f3fb-200d-1f3a4","๐ฉ๐ผโ๐ค":"1f469-1f3fc-200d-1f3a4","๐ฉ๐ฝโ๐ค":"1f469-1f3fd-200d-1f3a4","๐ฉ๐พโ๐ค":"1f469-1f3fe-200d-1f3a4","๐ฉ๐ฟโ๐ค":"1f469-1f3ff-200d-1f3a4","๐ง๐ปโ๐จ":"1f9d1-1f3fb-200d-1f3a8","๐ง๐ผโ๐จ":"1f9d1-1f3fc-200d-1f3a8","๐ง๐ฝโ๐จ":"1f9d1-1f3fd-200d-1f3a8","๐ง๐พโ๐จ":"1f9d1-1f3fe-200d-1f3a8","๐ง๐ฟโ๐จ":"1f9d1-1f3ff-200d-1f3a8","๐จ๐ปโ๐จ":"1f468-1f3fb-200d-1f3a8","๐จ๐ผโ๐จ":"1f468-1f3fc-200d-1f3a8","๐จ๐ฝโ๐จ":"1f468-1f3fd-200d-1f3a8","๐จ๐พโ๐จ":"1f468-1f3fe-200d-1f3a8","๐จ๐ฟโ๐จ":"1f468-1f3ff-200d-1f3a8","๐ฉ๐ปโ๐จ":"1f469-1f3fb-200d-1f3a8","๐ฉ๐ผโ๐จ":"1f469-1f3fc-200d-1f3a8","๐ฉ๐ฝโ๐จ":"1f469-1f3fd-200d-1f3a8","๐ฉ๐พโ๐จ":"1f469-1f3fe-200d-1f3a8","๐ฉ๐ฟโ๐จ":"1f469-1f3ff-200d-1f3a8","๐งโโ๏ธ":"1f9d1-200d-2708-fe0f","๐ง๐ปโโ":"1f9d1-1f3fb-200d-2708-fe0f","๐ง๐ผโโ":"1f9d1-1f3fc-200d-2708-fe0f","๐ง๐ฝโโ":"1f9d1-1f3fd-200d-2708-fe0f","๐ง๐พโโ":"1f9d1-1f3fe-200d-2708-fe0f","๐ง๐ฟโโ":"1f9d1-1f3ff-200d-2708-fe0f","๐จโโ๏ธ":"1f468-200d-2708-fe0f","๐จ๐ปโโ":"1f468-1f3fb-200d-2708-fe0f","๐จ๐ผโโ":"1f468-1f3fc-200d-2708-fe0f","๐จ๐ฝโโ":"1f468-1f3fd-200d-2708-fe0f","๐จ๐พโโ":"1f468-1f3fe-200d-2708-fe0f","๐จ๐ฟโโ":"1f468-1f3ff-200d-2708-fe0f","๐ฉโโ๏ธ":"1f469-200d-2708-fe0f","๐ฉ๐ปโโ":"1f469-1f3fb-200d-2708-fe0f","๐ฉ๐ผโโ":"1f469-1f3fc-200d-2708-fe0f","๐ฉ๐ฝโโ":"1f469-1f3fd-200d-2708-fe0f","๐ฉ๐พโโ":"1f469-1f3fe-200d-2708-fe0f","๐ฉ๐ฟโโ":"1f469-1f3ff-200d-2708-fe0f","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f680","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f680","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f680","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f680","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f680","๐จ๐ปโ๐":"1f468-1f3fb-200d-1f680","๐จ๐ผโ๐":"1f468-1f3fc-200d-1f680","๐จ๐ฝโ๐":"1f468-1f3fd-200d-1f680","๐จ๐พโ๐":"1f468-1f3fe-200d-1f680","๐จ๐ฟโ๐":"1f468-1f3ff-200d-1f680","๐ฉ๐ปโ๐":"1f469-1f3fb-200d-1f680","๐ฉ๐ผโ๐":"1f469-1f3fc-200d-1f680","๐ฉ๐ฝโ๐":"1f469-1f3fd-200d-1f680","๐ฉ๐พโ๐":"1f469-1f3fe-200d-1f680","๐ฉ๐ฟโ๐":"1f469-1f3ff-200d-1f680","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f692","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f692","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f692","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f692","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f692","๐จ๐ปโ๐":"1f468-1f3fb-200d-1f692","๐จ๐ผโ๐":"1f468-1f3fc-200d-1f692","๐จ๐ฝโ๐":"1f468-1f3fd-200d-1f692","๐จ๐พโ๐":"1f468-1f3fe-200d-1f692","๐จ๐ฟโ๐":"1f468-1f3ff-200d-1f692","๐ฉ๐ปโ๐":"1f469-1f3fb-200d-1f692","๐ฉ๐ผโ๐":"1f469-1f3fc-200d-1f692","๐ฉ๐ฝโ๐":"1f469-1f3fd-200d-1f692","๐ฉ๐พโ๐":"1f469-1f3fe-200d-1f692","๐ฉ๐ฟโ๐":"1f469-1f3ff-200d-1f692","๐ฎโโ๏ธ":"1f46e-200d-2642-fe0f","๐ฎ๐ปโโ":"1f46e-1f3fb-200d-2642-fe0f","๐ฎ๐ผโโ":"1f46e-1f3fc-200d-2642-fe0f","๐ฎ๐ฝโโ":"1f46e-1f3fd-200d-2642-fe0f","๐ฎ๐พโโ":"1f46e-1f3fe-200d-2642-fe0f","๐ฎ๐ฟโโ":"1f46e-1f3ff-200d-2642-fe0f","๐ฎโโ๏ธ":"1f46e-200d-2640-fe0f","๐ฎ๐ปโโ":"1f46e-1f3fb-200d-2640-fe0f","๐ฎ๐ผโโ":"1f46e-1f3fc-200d-2640-fe0f","๐ฎ๐ฝโโ":"1f46e-1f3fd-200d-2640-fe0f","๐ฎ๐พโโ":"1f46e-1f3fe-200d-2640-fe0f","๐ฎ๐ฟโโ":"1f46e-1f3ff-200d-2640-fe0f","๐ตโโ๏ธ":"1f575-fe0f-200d-2642-fe0f","๐ต๏ธโโ":"1f575-fe0f-200d-2642-fe0f","๐ต๐ปโโ":"1f575-1f3fb-200d-2642-fe0f","๐ต๐ผโโ":"1f575-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ":"1f575-1f3fd-200d-2642-fe0f","๐ต๐พโโ":"1f575-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ":"1f575-1f3ff-200d-2642-fe0f","๐ตโโ๏ธ":"1f575-fe0f-200d-2640-fe0f","๐ต๏ธโโ":"1f575-fe0f-200d-2640-fe0f","๐ต๐ปโโ":"1f575-1f3fb-200d-2640-fe0f","๐ต๐ผโโ":"1f575-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ":"1f575-1f3fd-200d-2640-fe0f","๐ต๐พโโ":"1f575-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ":"1f575-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f482-200d-2642-fe0f","๐๐ปโโ":"1f482-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f482-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f482-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f482-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f482-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f482-200d-2640-fe0f","๐๐ปโโ":"1f482-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f482-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f482-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f482-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f482-1f3ff-200d-2640-fe0f","๐ทโโ๏ธ":"1f477-200d-2642-fe0f","๐ท๐ปโโ":"1f477-1f3fb-200d-2642-fe0f","๐ท๐ผโโ":"1f477-1f3fc-200d-2642-fe0f","๐ท๐ฝโโ":"1f477-1f3fd-200d-2642-fe0f","๐ท๐พโโ":"1f477-1f3fe-200d-2642-fe0f","๐ท๐ฟโโ":"1f477-1f3ff-200d-2642-fe0f","๐ทโโ๏ธ":"1f477-200d-2640-fe0f","๐ท๐ปโโ":"1f477-1f3fb-200d-2640-fe0f","๐ท๐ผโโ":"1f477-1f3fc-200d-2640-fe0f","๐ท๐ฝโโ":"1f477-1f3fd-200d-2640-fe0f","๐ท๐พโโ":"1f477-1f3fe-200d-2640-fe0f","๐ท๐ฟโโ":"1f477-1f3ff-200d-2640-fe0f","๐ณโโ๏ธ":"1f473-200d-2642-fe0f","๐ณ๐ปโโ":"1f473-1f3fb-200d-2642-fe0f","๐ณ๐ผโโ":"1f473-1f3fc-200d-2642-fe0f","๐ณ๐ฝโโ":"1f473-1f3fd-200d-2642-fe0f","๐ณ๐พโโ":"1f473-1f3fe-200d-2642-fe0f","๐ณ๐ฟโโ":"1f473-1f3ff-200d-2642-fe0f","๐ณโโ๏ธ":"1f473-200d-2640-fe0f","๐ณ๐ปโโ":"1f473-1f3fb-200d-2640-fe0f","๐ณ๐ผโโ":"1f473-1f3fc-200d-2640-fe0f","๐ณ๐ฝโโ":"1f473-1f3fd-200d-2640-fe0f","๐ณ๐พโโ":"1f473-1f3fe-200d-2640-fe0f","๐ณ๐ฟโโ":"1f473-1f3ff-200d-2640-fe0f","๐คตโโ๏ธ":"1f935-200d-2642-fe0f","๐คต๐ปโโ":"1f935-1f3fb-200d-2642-fe0f","๐คต๐ผโโ":"1f935-1f3fc-200d-2642-fe0f","๐คต๐ฝโโ":"1f935-1f3fd-200d-2642-fe0f","๐คต๐พโโ":"1f935-1f3fe-200d-2642-fe0f","๐คต๐ฟโโ":"1f935-1f3ff-200d-2642-fe0f","๐คตโโ๏ธ":"1f935-200d-2640-fe0f","๐คต๐ปโโ":"1f935-1f3fb-200d-2640-fe0f","๐คต๐ผโโ":"1f935-1f3fc-200d-2640-fe0f","๐คต๐ฝโโ":"1f935-1f3fd-200d-2640-fe0f","๐คต๐พโโ":"1f935-1f3fe-200d-2640-fe0f","๐คต๐ฟโโ":"1f935-1f3ff-200d-2640-fe0f","๐ฐโโ๏ธ":"1f470-200d-2642-fe0f","๐ฐ๐ปโโ":"1f470-1f3fb-200d-2642-fe0f","๐ฐ๐ผโโ":"1f470-1f3fc-200d-2642-fe0f","๐ฐ๐ฝโโ":"1f470-1f3fd-200d-2642-fe0f","๐ฐ๐พโโ":"1f470-1f3fe-200d-2642-fe0f","๐ฐ๐ฟโโ":"1f470-1f3ff-200d-2642-fe0f","๐ฐโโ๏ธ":"1f470-200d-2640-fe0f","๐ฐ๐ปโโ":"1f470-1f3fb-200d-2640-fe0f","๐ฐ๐ผโโ":"1f470-1f3fc-200d-2640-fe0f","๐ฐ๐ฝโโ":"1f470-1f3fd-200d-2640-fe0f","๐ฐ๐พโโ":"1f470-1f3fe-200d-2640-fe0f","๐ฐ๐ฟโโ":"1f470-1f3ff-200d-2640-fe0f","๐ฉ๐ปโ๐ผ":"1f469-1f3fb-200d-1f37c","๐ฉ๐ผโ๐ผ":"1f469-1f3fc-200d-1f37c","๐ฉ๐ฝโ๐ผ":"1f469-1f3fd-200d-1f37c","๐ฉ๐พโ๐ผ":"1f469-1f3fe-200d-1f37c","๐ฉ๐ฟโ๐ผ":"1f469-1f3ff-200d-1f37c","๐จ๐ปโ๐ผ":"1f468-1f3fb-200d-1f37c","๐จ๐ผโ๐ผ":"1f468-1f3fc-200d-1f37c","๐จ๐ฝโ๐ผ":"1f468-1f3fd-200d-1f37c","๐จ๐พโ๐ผ":"1f468-1f3fe-200d-1f37c","๐จ๐ฟโ๐ผ":"1f468-1f3ff-200d-1f37c","๐ง๐ปโ๐ผ":"1f9d1-1f3fb-200d-1f37c","๐ง๐ผโ๐ผ":"1f9d1-1f3fc-200d-1f37c","๐ง๐ฝโ๐ผ":"1f9d1-1f3fd-200d-1f37c","๐ง๐พโ๐ผ":"1f9d1-1f3fe-200d-1f37c","๐ง๐ฟโ๐ผ":"1f9d1-1f3ff-200d-1f37c","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f384","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f384","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f384","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f384","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f384","๐ฆธโโ๏ธ":"1f9b8-200d-2642-fe0f","๐ฆธ๐ปโโ":"1f9b8-1f3fb-200d-2642-fe0f","๐ฆธ๐ผโโ":"1f9b8-1f3fc-200d-2642-fe0f","๐ฆธ๐ฝโโ":"1f9b8-1f3fd-200d-2642-fe0f","๐ฆธ๐พโโ":"1f9b8-1f3fe-200d-2642-fe0f","๐ฆธ๐ฟโโ":"1f9b8-1f3ff-200d-2642-fe0f","๐ฆธโโ๏ธ":"1f9b8-200d-2640-fe0f","๐ฆธ๐ปโโ":"1f9b8-1f3fb-200d-2640-fe0f","๐ฆธ๐ผโโ":"1f9b8-1f3fc-200d-2640-fe0f","๐ฆธ๐ฝโโ":"1f9b8-1f3fd-200d-2640-fe0f","๐ฆธ๐พโโ":"1f9b8-1f3fe-200d-2640-fe0f","๐ฆธ๐ฟโโ":"1f9b8-1f3ff-200d-2640-fe0f","๐ฆนโโ๏ธ":"1f9b9-200d-2642-fe0f","๐ฆน๐ปโโ":"1f9b9-1f3fb-200d-2642-fe0f","๐ฆน๐ผโโ":"1f9b9-1f3fc-200d-2642-fe0f","๐ฆน๐ฝโโ":"1f9b9-1f3fd-200d-2642-fe0f","๐ฆน๐พโโ":"1f9b9-1f3fe-200d-2642-fe0f","๐ฆน๐ฟโโ":"1f9b9-1f3ff-200d-2642-fe0f","๐ฆนโโ๏ธ":"1f9b9-200d-2640-fe0f","๐ฆน๐ปโโ":"1f9b9-1f3fb-200d-2640-fe0f","๐ฆน๐ผโโ":"1f9b9-1f3fc-200d-2640-fe0f","๐ฆน๐ฝโโ":"1f9b9-1f3fd-200d-2640-fe0f","๐ฆน๐พโโ":"1f9b9-1f3fe-200d-2640-fe0f","๐ฆน๐ฟโโ":"1f9b9-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d9-200d-2642-fe0f","๐ง๐ปโโ":"1f9d9-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d9-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d9-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d9-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d9-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d9-200d-2640-fe0f","๐ง๐ปโโ":"1f9d9-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d9-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d9-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d9-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d9-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9da-200d-2642-fe0f","๐ง๐ปโโ":"1f9da-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9da-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9da-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9da-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9da-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9da-200d-2640-fe0f","๐ง๐ปโโ":"1f9da-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9da-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9da-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9da-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9da-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9db-200d-2642-fe0f","๐ง๐ปโโ":"1f9db-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9db-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9db-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9db-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9db-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9db-200d-2640-fe0f","๐ง๐ปโโ":"1f9db-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9db-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9db-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9db-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9db-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9dc-200d-2642-fe0f","๐ง๐ปโโ":"1f9dc-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9dc-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9dc-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9dc-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9dc-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9dc-200d-2640-fe0f","๐ง๐ปโโ":"1f9dc-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9dc-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9dc-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9dc-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9dc-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9dd-200d-2642-fe0f","๐ง๐ปโโ":"1f9dd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9dd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9dd-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9dd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9dd-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9dd-200d-2640-fe0f","๐ง๐ปโโ":"1f9dd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9dd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9dd-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9dd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9dd-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9de-200d-2642-fe0f","๐งโโ๏ธ":"1f9de-200d-2640-fe0f","๐งโโ๏ธ":"1f9df-200d-2642-fe0f","๐งโโ๏ธ":"1f9df-200d-2640-fe0f","๐โโ๏ธ":"1f486-200d-2642-fe0f","๐๐ปโโ":"1f486-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f486-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f486-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f486-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f486-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f486-200d-2640-fe0f","๐๐ปโโ":"1f486-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f486-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f486-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f486-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f486-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f487-200d-2642-fe0f","๐๐ปโโ":"1f487-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f487-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f487-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f487-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f487-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f487-200d-2640-fe0f","๐๐ปโโ":"1f487-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f487-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f487-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f487-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f487-1f3ff-200d-2640-fe0f","๐ถโโ๏ธ":"1f6b6-200d-2642-fe0f","๐ถ๐ปโโ":"1f6b6-1f3fb-200d-2642-fe0f","๐ถ๐ผโโ":"1f6b6-1f3fc-200d-2642-fe0f","๐ถ๐ฝโโ":"1f6b6-1f3fd-200d-2642-fe0f","๐ถ๐พโโ":"1f6b6-1f3fe-200d-2642-fe0f","๐ถ๐ฟโโ":"1f6b6-1f3ff-200d-2642-fe0f","๐ถโโ๏ธ":"1f6b6-200d-2640-fe0f","๐ถ๐ปโโ":"1f6b6-1f3fb-200d-2640-fe0f","๐ถ๐ผโโ":"1f6b6-1f3fc-200d-2640-fe0f","๐ถ๐ฝโโ":"1f6b6-1f3fd-200d-2640-fe0f","๐ถ๐พโโ":"1f6b6-1f3fe-200d-2640-fe0f","๐ถ๐ฟโโ":"1f6b6-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9cd-200d-2642-fe0f","๐ง๐ปโโ":"1f9cd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9cd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9cd-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9cd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9cd-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9cd-200d-2640-fe0f","๐ง๐ปโโ":"1f9cd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9cd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9cd-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9cd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9cd-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9ce-200d-2642-fe0f","๐ง๐ปโโ":"1f9ce-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9ce-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9ce-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9ce-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9ce-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9ce-200d-2640-fe0f","๐ง๐ปโโ":"1f9ce-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9ce-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9ce-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9ce-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9ce-1f3ff-200d-2640-fe0f","๐ง๐ปโ๐ฆฏ":"1f9d1-1f3fb-200d-1f9af","๐ง๐ผโ๐ฆฏ":"1f9d1-1f3fc-200d-1f9af","๐ง๐ฝโ๐ฆฏ":"1f9d1-1f3fd-200d-1f9af","๐ง๐พโ๐ฆฏ":"1f9d1-1f3fe-200d-1f9af","๐ง๐ฟโ๐ฆฏ":"1f9d1-1f3ff-200d-1f9af","๐จ๐ปโ๐ฆฏ":"1f468-1f3fb-200d-1f9af","๐จ๐ผโ๐ฆฏ":"1f468-1f3fc-200d-1f9af","๐จ๐ฝโ๐ฆฏ":"1f468-1f3fd-200d-1f9af","๐จ๐พโ๐ฆฏ":"1f468-1f3fe-200d-1f9af","๐จ๐ฟโ๐ฆฏ":"1f468-1f3ff-200d-1f9af","๐ฉ๐ปโ๐ฆฏ":"1f469-1f3fb-200d-1f9af","๐ฉ๐ผโ๐ฆฏ":"1f469-1f3fc-200d-1f9af","๐ฉ๐ฝโ๐ฆฏ":"1f469-1f3fd-200d-1f9af","๐ฉ๐พโ๐ฆฏ":"1f469-1f3fe-200d-1f9af","๐ฉ๐ฟโ๐ฆฏ":"1f469-1f3ff-200d-1f9af","๐ง๐ปโ๐ฆผ":"1f9d1-1f3fb-200d-1f9bc","๐ง๐ผโ๐ฆผ":"1f9d1-1f3fc-200d-1f9bc","๐ง๐ฝโ๐ฆผ":"1f9d1-1f3fd-200d-1f9bc","๐ง๐พโ๐ฆผ":"1f9d1-1f3fe-200d-1f9bc","๐ง๐ฟโ๐ฆผ":"1f9d1-1f3ff-200d-1f9bc","๐จ๐ปโ๐ฆผ":"1f468-1f3fb-200d-1f9bc","๐จ๐ผโ๐ฆผ":"1f468-1f3fc-200d-1f9bc","๐จ๐ฝโ๐ฆผ":"1f468-1f3fd-200d-1f9bc","๐จ๐พโ๐ฆผ":"1f468-1f3fe-200d-1f9bc","๐จ๐ฟโ๐ฆผ":"1f468-1f3ff-200d-1f9bc","๐ฉ๐ปโ๐ฆผ":"1f469-1f3fb-200d-1f9bc","๐ฉ๐ผโ๐ฆผ":"1f469-1f3fc-200d-1f9bc","๐ฉ๐ฝโ๐ฆผ":"1f469-1f3fd-200d-1f9bc","๐ฉ๐พโ๐ฆผ":"1f469-1f3fe-200d-1f9bc","๐ฉ๐ฟโ๐ฆผ":"1f469-1f3ff-200d-1f9bc","๐ง๐ปโ๐ฆฝ":"1f9d1-1f3fb-200d-1f9bd","๐ง๐ผโ๐ฆฝ":"1f9d1-1f3fc-200d-1f9bd","๐ง๐ฝโ๐ฆฝ":"1f9d1-1f3fd-200d-1f9bd","๐ง๐พโ๐ฆฝ":"1f9d1-1f3fe-200d-1f9bd","๐ง๐ฟโ๐ฆฝ":"1f9d1-1f3ff-200d-1f9bd","๐จ๐ปโ๐ฆฝ":"1f468-1f3fb-200d-1f9bd","๐จ๐ผโ๐ฆฝ":"1f468-1f3fc-200d-1f9bd","๐จ๐ฝโ๐ฆฝ":"1f468-1f3fd-200d-1f9bd","๐จ๐พโ๐ฆฝ":"1f468-1f3fe-200d-1f9bd","๐จ๐ฟโ๐ฆฝ":"1f468-1f3ff-200d-1f9bd","๐ฉ๐ปโ๐ฆฝ":"1f469-1f3fb-200d-1f9bd","๐ฉ๐ผโ๐ฆฝ":"1f469-1f3fc-200d-1f9bd","๐ฉ๐ฝโ๐ฆฝ":"1f469-1f3fd-200d-1f9bd","๐ฉ๐พโ๐ฆฝ":"1f469-1f3fe-200d-1f9bd","๐ฉ๐ฟโ๐ฆฝ":"1f469-1f3ff-200d-1f9bd","๐โโ๏ธ":"1f3c3-200d-2642-fe0f","๐๐ปโโ":"1f3c3-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3c3-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3c3-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3c3-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3c3-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3c3-200d-2640-fe0f","๐๐ปโโ":"1f3c3-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3c3-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3c3-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3c3-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3c3-1f3ff-200d-2640-fe0f","๐ฏโโ๏ธ":"1f46f-200d-2642-fe0f","๐ฏโโ๏ธ":"1f46f-200d-2640-fe0f","๐งโโ๏ธ":"1f9d6-200d-2642-fe0f","๐ง๐ปโโ":"1f9d6-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d6-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d6-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d6-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d6-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d6-200d-2640-fe0f","๐ง๐ปโโ":"1f9d6-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d6-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d6-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d6-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d6-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d7-200d-2642-fe0f","๐ง๐ปโโ":"1f9d7-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d7-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d7-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d7-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d7-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d7-200d-2640-fe0f","๐ง๐ปโโ":"1f9d7-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d7-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d7-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d7-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d7-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3cc-fe0f-200d-2642-fe0f","๐๏ธโโ":"1f3cc-fe0f-200d-2642-fe0f","๐๐ปโโ":"1f3cc-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3cc-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3cc-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3cc-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3cc-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3cc-fe0f-200d-2640-fe0f","๐๏ธโโ":"1f3cc-fe0f-200d-2640-fe0f","๐๐ปโโ":"1f3cc-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3cc-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3cc-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3cc-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3cc-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3c4-200d-2642-fe0f","๐๐ปโโ":"1f3c4-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3c4-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3c4-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3c4-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3c4-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3c4-200d-2640-fe0f","๐๐ปโโ":"1f3c4-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3c4-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3c4-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3c4-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3c4-1f3ff-200d-2640-fe0f","๐ฃโโ๏ธ":"1f6a3-200d-2642-fe0f","๐ฃ๐ปโโ":"1f6a3-1f3fb-200d-2642-fe0f","๐ฃ๐ผโโ":"1f6a3-1f3fc-200d-2642-fe0f","๐ฃ๐ฝโโ":"1f6a3-1f3fd-200d-2642-fe0f","๐ฃ๐พโโ":"1f6a3-1f3fe-200d-2642-fe0f","๐ฃ๐ฟโโ":"1f6a3-1f3ff-200d-2642-fe0f","๐ฃโโ๏ธ":"1f6a3-200d-2640-fe0f","๐ฃ๐ปโโ":"1f6a3-1f3fb-200d-2640-fe0f","๐ฃ๐ผโโ":"1f6a3-1f3fc-200d-2640-fe0f","๐ฃ๐ฝโโ":"1f6a3-1f3fd-200d-2640-fe0f","๐ฃ๐พโโ":"1f6a3-1f3fe-200d-2640-fe0f","๐ฃ๐ฟโโ":"1f6a3-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3ca-200d-2642-fe0f","๐๐ปโโ":"1f3ca-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3ca-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3ca-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3ca-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3ca-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3ca-200d-2640-fe0f","๐๐ปโโ":"1f3ca-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3ca-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3ca-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3ca-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3ca-1f3ff-200d-2640-fe0f","โนโโ๏ธ":"26f9-fe0f-200d-2642-fe0f","โน๏ธโโ":"26f9-fe0f-200d-2642-fe0f","โน๐ปโโ":"26f9-1f3fb-200d-2642-fe0f","โน๐ผโโ":"26f9-1f3fc-200d-2642-fe0f","โน๐ฝโโ":"26f9-1f3fd-200d-2642-fe0f","โน๐พโโ":"26f9-1f3fe-200d-2642-fe0f","โน๐ฟโโ":"26f9-1f3ff-200d-2642-fe0f","โนโโ๏ธ":"26f9-fe0f-200d-2640-fe0f","โน๏ธโโ":"26f9-fe0f-200d-2640-fe0f","โน๐ปโโ":"26f9-1f3fb-200d-2640-fe0f","โน๐ผโโ":"26f9-1f3fc-200d-2640-fe0f","โน๐ฝโโ":"26f9-1f3fd-200d-2640-fe0f","โน๐พโโ":"26f9-1f3fe-200d-2640-fe0f","โน๐ฟโโ":"26f9-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3cb-fe0f-200d-2642-fe0f","๐๏ธโโ":"1f3cb-fe0f-200d-2642-fe0f","๐๐ปโโ":"1f3cb-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3cb-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3cb-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3cb-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3cb-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3cb-fe0f-200d-2640-fe0f","๐๏ธโโ":"1f3cb-fe0f-200d-2640-fe0f","๐๐ปโโ":"1f3cb-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3cb-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3cb-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3cb-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3cb-1f3ff-200d-2640-fe0f","๐ดโโ๏ธ":"1f6b4-200d-2642-fe0f","๐ด๐ปโโ":"1f6b4-1f3fb-200d-2642-fe0f","๐ด๐ผโโ":"1f6b4-1f3fc-200d-2642-fe0f","๐ด๐ฝโโ":"1f6b4-1f3fd-200d-2642-fe0f","๐ด๐พโโ":"1f6b4-1f3fe-200d-2642-fe0f","๐ด๐ฟโโ":"1f6b4-1f3ff-200d-2642-fe0f","๐ดโโ๏ธ":"1f6b4-200d-2640-fe0f","๐ด๐ปโโ":"1f6b4-1f3fb-200d-2640-fe0f","๐ด๐ผโโ":"1f6b4-1f3fc-200d-2640-fe0f","๐ด๐ฝโโ":"1f6b4-1f3fd-200d-2640-fe0f","๐ด๐พโโ":"1f6b4-1f3fe-200d-2640-fe0f","๐ด๐ฟโโ":"1f6b4-1f3ff-200d-2640-fe0f","๐ตโโ๏ธ":"1f6b5-200d-2642-fe0f","๐ต๐ปโโ":"1f6b5-1f3fb-200d-2642-fe0f","๐ต๐ผโโ":"1f6b5-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ":"1f6b5-1f3fd-200d-2642-fe0f","๐ต๐พโโ":"1f6b5-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ":"1f6b5-1f3ff-200d-2642-fe0f","๐ตโโ๏ธ":"1f6b5-200d-2640-fe0f","๐ต๐ปโโ":"1f6b5-1f3fb-200d-2640-fe0f","๐ต๐ผโโ":"1f6b5-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ":"1f6b5-1f3fd-200d-2640-fe0f","๐ต๐พโโ":"1f6b5-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ":"1f6b5-1f3ff-200d-2640-fe0f","๐คธโโ๏ธ":"1f938-200d-2642-fe0f","๐คธ๐ปโโ":"1f938-1f3fb-200d-2642-fe0f","๐คธ๐ผโโ":"1f938-1f3fc-200d-2642-fe0f","๐คธ๐ฝโโ":"1f938-1f3fd-200d-2642-fe0f","๐คธ๐พโโ":"1f938-1f3fe-200d-2642-fe0f","๐คธ๐ฟโโ":"1f938-1f3ff-200d-2642-fe0f","๐คธโโ๏ธ":"1f938-200d-2640-fe0f","๐คธ๐ปโโ":"1f938-1f3fb-200d-2640-fe0f","๐คธ๐ผโโ":"1f938-1f3fc-200d-2640-fe0f","๐คธ๐ฝโโ":"1f938-1f3fd-200d-2640-fe0f","๐คธ๐พโโ":"1f938-1f3fe-200d-2640-fe0f","๐คธ๐ฟโโ":"1f938-1f3ff-200d-2640-fe0f","๐คผโโ๏ธ":"1f93c-200d-2642-fe0f","๐คผโโ๏ธ":"1f93c-200d-2640-fe0f","๐คฝโโ๏ธ":"1f93d-200d-2642-fe0f","๐คฝ๐ปโโ":"1f93d-1f3fb-200d-2642-fe0f","๐คฝ๐ผโโ":"1f93d-1f3fc-200d-2642-fe0f","๐คฝ๐ฝโโ":"1f93d-1f3fd-200d-2642-fe0f","๐คฝ๐พโโ":"1f93d-1f3fe-200d-2642-fe0f","๐คฝ๐ฟโโ":"1f93d-1f3ff-200d-2642-fe0f","๐คฝโโ๏ธ":"1f93d-200d-2640-fe0f","๐คฝ๐ปโโ":"1f93d-1f3fb-200d-2640-fe0f","๐คฝ๐ผโโ":"1f93d-1f3fc-200d-2640-fe0f","๐คฝ๐ฝโโ":"1f93d-1f3fd-200d-2640-fe0f","๐คฝ๐พโโ":"1f93d-1f3fe-200d-2640-fe0f","๐คฝ๐ฟโโ":"1f93d-1f3ff-200d-2640-fe0f","๐คพโโ๏ธ":"1f93e-200d-2642-fe0f","๐คพ๐ปโโ":"1f93e-1f3fb-200d-2642-fe0f","๐คพ๐ผโโ":"1f93e-1f3fc-200d-2642-fe0f","๐คพ๐ฝโโ":"1f93e-1f3fd-200d-2642-fe0f","๐คพ๐พโโ":"1f93e-1f3fe-200d-2642-fe0f","๐คพ๐ฟโโ":"1f93e-1f3ff-200d-2642-fe0f","๐คพโโ๏ธ":"1f93e-200d-2640-fe0f","๐คพ๐ปโโ":"1f93e-1f3fb-200d-2640-fe0f","๐คพ๐ผโโ":"1f93e-1f3fc-200d-2640-fe0f","๐คพ๐ฝโโ":"1f93e-1f3fd-200d-2640-fe0f","๐คพ๐พโโ":"1f93e-1f3fe-200d-2640-fe0f","๐คพ๐ฟโโ":"1f93e-1f3ff-200d-2640-fe0f","๐คนโโ๏ธ":"1f939-200d-2642-fe0f","๐คน๐ปโโ":"1f939-1f3fb-200d-2642-fe0f","๐คน๐ผโโ":"1f939-1f3fc-200d-2642-fe0f","๐คน๐ฝโโ":"1f939-1f3fd-200d-2642-fe0f","๐คน๐พโโ":"1f939-1f3fe-200d-2642-fe0f","๐คน๐ฟโโ":"1f939-1f3ff-200d-2642-fe0f","๐คนโโ๏ธ":"1f939-200d-2640-fe0f","๐คน๐ปโโ":"1f939-1f3fb-200d-2640-fe0f","๐คน๐ผโโ":"1f939-1f3fc-200d-2640-fe0f","๐คน๐ฝโโ":"1f939-1f3fd-200d-2640-fe0f","๐คน๐พโโ":"1f939-1f3fe-200d-2640-fe0f","๐คน๐ฟโโ":"1f939-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d8-200d-2642-fe0f","๐ง๐ปโโ":"1f9d8-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d8-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d8-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d8-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d8-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d8-200d-2640-fe0f","๐ง๐ปโโ":"1f9d8-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d8-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d8-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d8-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d8-1f3ff-200d-2640-fe0f","๐ปโโ๏ธ":"1f43b-200d-2744-fe0f","๐ณ๏ธโ๐":"1f3f3-fe0f-200d-1f308","๐ณโโง๏ธ":"1f3f3-fe0f-200d-26a7-fe0f","๐ณ๏ธโโง":"1f3f3-fe0f-200d-26a7-fe0f","๐ดโโ ๏ธ":"1f3f4-200d-2620-fe0f","๐๏ธโ๐จ๏ธ":"1f441-200d-1f5e8","๐ซฑ๐ปโ๐ซฒ๐ผ":"1faf1-1f3fb-200d-1faf2-1f3fc","๐ซฑ๐ปโ๐ซฒ๐ฝ":"1faf1-1f3fb-200d-1faf2-1f3fd","๐ซฑ๐ปโ๐ซฒ๐พ":"1faf1-1f3fb-200d-1faf2-1f3fe","๐ซฑ๐ปโ๐ซฒ๐ฟ":"1faf1-1f3fb-200d-1faf2-1f3ff","๐ซฑ๐ผโ๐ซฒ๐ป":"1faf1-1f3fc-200d-1faf2-1f3fb","๐ซฑ๐ผโ๐ซฒ๐ฝ":"1faf1-1f3fc-200d-1faf2-1f3fd","๐ซฑ๐ผโ๐ซฒ๐พ":"1faf1-1f3fc-200d-1faf2-1f3fe","๐ซฑ๐ผโ๐ซฒ๐ฟ":"1faf1-1f3fc-200d-1faf2-1f3ff","๐ซฑ๐ฝโ๐ซฒ๐ป":"1faf1-1f3fd-200d-1faf2-1f3fb","๐ซฑ๐ฝโ๐ซฒ๐ผ":"1faf1-1f3fd-200d-1faf2-1f3fc","๐ซฑ๐ฝโ๐ซฒ๐พ":"1faf1-1f3fd-200d-1faf2-1f3fe","๐ซฑ๐ฝโ๐ซฒ๐ฟ":"1faf1-1f3fd-200d-1faf2-1f3ff","๐ซฑ๐พโ๐ซฒ๐ป":"1faf1-1f3fe-200d-1faf2-1f3fb","๐ซฑ๐พโ๐ซฒ๐ผ":"1faf1-1f3fe-200d-1faf2-1f3fc","๐ซฑ๐พโ๐ซฒ๐ฝ":"1faf1-1f3fe-200d-1faf2-1f3fd","๐ซฑ๐พโ๐ซฒ๐ฟ":"1faf1-1f3fe-200d-1faf2-1f3ff","๐ซฑ๐ฟโ๐ซฒ๐ป":"1faf1-1f3ff-200d-1faf2-1f3fb","๐ซฑ๐ฟโ๐ซฒ๐ผ":"1faf1-1f3ff-200d-1faf2-1f3fc","๐ซฑ๐ฟโ๐ซฒ๐ฝ":"1faf1-1f3ff-200d-1faf2-1f3fd","๐ซฑ๐ฟโ๐ซฒ๐พ":"1faf1-1f3ff-200d-1faf2-1f3fe","๐ง๐ปโโ๏ธ":"1f9d4-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d4-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d4-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d4-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d4-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d4-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d4-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d4-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d4-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d4-1f3ff-200d-2640-fe0f","๐ฑ๐ปโโ๏ธ":"1f471-1f3fb-200d-2640-fe0f","๐ฑ๐ผโโ๏ธ":"1f471-1f3fc-200d-2640-fe0f","๐ฑ๐ฝโโ๏ธ":"1f471-1f3fd-200d-2640-fe0f","๐ฑ๐พโโ๏ธ":"1f471-1f3fe-200d-2640-fe0f","๐ฑ๐ฟโโ๏ธ":"1f471-1f3ff-200d-2640-fe0f","๐ฑ๐ปโโ๏ธ":"1f471-1f3fb-200d-2642-fe0f","๐ฑ๐ผโโ๏ธ":"1f471-1f3fc-200d-2642-fe0f","๐ฑ๐ฝโโ๏ธ":"1f471-1f3fd-200d-2642-fe0f","๐ฑ๐พโโ๏ธ":"1f471-1f3fe-200d-2642-fe0f","๐ฑ๐ฟโโ๏ธ":"1f471-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64d-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f64d-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f64d-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f64d-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f64d-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64d-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f64d-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f64d-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f64d-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f64d-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f64e-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f64e-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f64e-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f64e-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f64e-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64e-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f64e-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f64e-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f64e-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f64e-1f3ff-200d-2640-fe0f","๐ ๐ปโโ๏ธ":"1f645-1f3fb-200d-2642-fe0f","๐ ๐ผโโ๏ธ":"1f645-1f3fc-200d-2642-fe0f","๐ ๐ฝโโ๏ธ":"1f645-1f3fd-200d-2642-fe0f","๐ ๐พโโ๏ธ":"1f645-1f3fe-200d-2642-fe0f","๐ ๐ฟโโ๏ธ":"1f645-1f3ff-200d-2642-fe0f","๐ ๐ปโโ๏ธ":"1f645-1f3fb-200d-2640-fe0f","๐ ๐ผโโ๏ธ":"1f645-1f3fc-200d-2640-fe0f","๐ ๐ฝโโ๏ธ":"1f645-1f3fd-200d-2640-fe0f","๐ ๐พโโ๏ธ":"1f645-1f3fe-200d-2640-fe0f","๐ ๐ฟโโ๏ธ":"1f645-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f646-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f646-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f646-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f646-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f646-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f646-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f646-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f646-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f646-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f646-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f481-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f481-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f481-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f481-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f481-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f481-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f481-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f481-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f481-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f481-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f64b-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f64b-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f64b-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f64b-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f64b-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64b-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f64b-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f64b-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f64b-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f64b-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9cf-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9cf-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9cf-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9cf-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9cf-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9cf-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9cf-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9cf-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9cf-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9cf-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f647-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f647-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f647-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f647-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f647-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f647-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f647-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f647-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f647-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f647-1f3ff-200d-2640-fe0f","๐คฆ๐ปโโ๏ธ":"1f926-1f3fb-200d-2642-fe0f","๐คฆ๐ผโโ๏ธ":"1f926-1f3fc-200d-2642-fe0f","๐คฆ๐ฝโโ๏ธ":"1f926-1f3fd-200d-2642-fe0f","๐คฆ๐พโโ๏ธ":"1f926-1f3fe-200d-2642-fe0f","๐คฆ๐ฟโโ๏ธ":"1f926-1f3ff-200d-2642-fe0f","๐คฆ๐ปโโ๏ธ":"1f926-1f3fb-200d-2640-fe0f","๐คฆ๐ผโโ๏ธ":"1f926-1f3fc-200d-2640-fe0f","๐คฆ๐ฝโโ๏ธ":"1f926-1f3fd-200d-2640-fe0f","๐คฆ๐พโโ๏ธ":"1f926-1f3fe-200d-2640-fe0f","๐คฆ๐ฟโโ๏ธ":"1f926-1f3ff-200d-2640-fe0f","๐คท๐ปโโ๏ธ":"1f937-1f3fb-200d-2642-fe0f","๐คท๐ผโโ๏ธ":"1f937-1f3fc-200d-2642-fe0f","๐คท๐ฝโโ๏ธ":"1f937-1f3fd-200d-2642-fe0f","๐คท๐พโโ๏ธ":"1f937-1f3fe-200d-2642-fe0f","๐คท๐ฟโโ๏ธ":"1f937-1f3ff-200d-2642-fe0f","๐คท๐ปโโ๏ธ":"1f937-1f3fb-200d-2640-fe0f","๐คท๐ผโโ๏ธ":"1f937-1f3fc-200d-2640-fe0f","๐คท๐ฝโโ๏ธ":"1f937-1f3fd-200d-2640-fe0f","๐คท๐พโโ๏ธ":"1f937-1f3fe-200d-2640-fe0f","๐คท๐ฟโโ๏ธ":"1f937-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d1-1f3fb-200d-2695-fe0f","๐ง๐ผโโ๏ธ":"1f9d1-1f3fc-200d-2695-fe0f","๐ง๐ฝโโ๏ธ":"1f9d1-1f3fd-200d-2695-fe0f","๐ง๐พโโ๏ธ":"1f9d1-1f3fe-200d-2695-fe0f","๐ง๐ฟโโ๏ธ":"1f9d1-1f3ff-200d-2695-fe0f","๐จ๐ปโโ๏ธ":"1f468-1f3fb-200d-2695-fe0f","๐จ๐ผโโ๏ธ":"1f468-1f3fc-200d-2695-fe0f","๐จ๐ฝโโ๏ธ":"1f468-1f3fd-200d-2695-fe0f","๐จ๐พโโ๏ธ":"1f468-1f3fe-200d-2695-fe0f","๐จ๐ฟโโ๏ธ":"1f468-1f3ff-200d-2695-fe0f","๐ฉ๐ปโโ๏ธ":"1f469-1f3fb-200d-2695-fe0f","๐ฉ๐ผโโ๏ธ":"1f469-1f3fc-200d-2695-fe0f","๐ฉ๐ฝโโ๏ธ":"1f469-1f3fd-200d-2695-fe0f","๐ฉ๐พโโ๏ธ":"1f469-1f3fe-200d-2695-fe0f","๐ฉ๐ฟโโ๏ธ":"1f469-1f3ff-200d-2695-fe0f","๐ง๐ปโโ๏ธ":"1f9d1-1f3fb-200d-2696-fe0f","๐ง๐ผโโ๏ธ":"1f9d1-1f3fc-200d-2696-fe0f","๐ง๐ฝโโ๏ธ":"1f9d1-1f3fd-200d-2696-fe0f","๐ง๐พโโ๏ธ":"1f9d1-1f3fe-200d-2696-fe0f","๐ง๐ฟโโ๏ธ":"1f9d1-1f3ff-200d-2696-fe0f","๐จ๐ปโโ๏ธ":"1f468-1f3fb-200d-2696-fe0f","๐จ๐ผโโ๏ธ":"1f468-1f3fc-200d-2696-fe0f","๐จ๐ฝโโ๏ธ":"1f468-1f3fd-200d-2696-fe0f","๐จ๐พโโ๏ธ":"1f468-1f3fe-200d-2696-fe0f","๐จ๐ฟโโ๏ธ":"1f468-1f3ff-200d-2696-fe0f","๐ฉ๐ปโโ๏ธ":"1f469-1f3fb-200d-2696-fe0f","๐ฉ๐ผโโ๏ธ":"1f469-1f3fc-200d-2696-fe0f","๐ฉ๐ฝโโ๏ธ":"1f469-1f3fd-200d-2696-fe0f","๐ฉ๐พโโ๏ธ":"1f469-1f3fe-200d-2696-fe0f","๐ฉ๐ฟโโ๏ธ":"1f469-1f3ff-200d-2696-fe0f","๐ง๐ปโโ๏ธ":"1f9d1-1f3fb-200d-2708-fe0f","๐ง๐ผโโ๏ธ":"1f9d1-1f3fc-200d-2708-fe0f","๐ง๐ฝโโ๏ธ":"1f9d1-1f3fd-200d-2708-fe0f","๐ง๐พโโ๏ธ":"1f9d1-1f3fe-200d-2708-fe0f","๐ง๐ฟโโ๏ธ":"1f9d1-1f3ff-200d-2708-fe0f","๐จ๐ปโโ๏ธ":"1f468-1f3fb-200d-2708-fe0f","๐จ๐ผโโ๏ธ":"1f468-1f3fc-200d-2708-fe0f","๐จ๐ฝโโ๏ธ":"1f468-1f3fd-200d-2708-fe0f","๐จ๐พโโ๏ธ":"1f468-1f3fe-200d-2708-fe0f","๐จ๐ฟโโ๏ธ":"1f468-1f3ff-200d-2708-fe0f","๐ฉ๐ปโโ๏ธ":"1f469-1f3fb-200d-2708-fe0f","๐ฉ๐ผโโ๏ธ":"1f469-1f3fc-200d-2708-fe0f","๐ฉ๐ฝโโ๏ธ":"1f469-1f3fd-200d-2708-fe0f","๐ฉ๐พโโ๏ธ":"1f469-1f3fe-200d-2708-fe0f","๐ฉ๐ฟโโ๏ธ":"1f469-1f3ff-200d-2708-fe0f","๐ฎ๐ปโโ๏ธ":"1f46e-1f3fb-200d-2642-fe0f","๐ฎ๐ผโโ๏ธ":"1f46e-1f3fc-200d-2642-fe0f","๐ฎ๐ฝโโ๏ธ":"1f46e-1f3fd-200d-2642-fe0f","๐ฎ๐พโโ๏ธ":"1f46e-1f3fe-200d-2642-fe0f","๐ฎ๐ฟโโ๏ธ":"1f46e-1f3ff-200d-2642-fe0f","๐ฎ๐ปโโ๏ธ":"1f46e-1f3fb-200d-2640-fe0f","๐ฎ๐ผโโ๏ธ":"1f46e-1f3fc-200d-2640-fe0f","๐ฎ๐ฝโโ๏ธ":"1f46e-1f3fd-200d-2640-fe0f","๐ฎ๐พโโ๏ธ":"1f46e-1f3fe-200d-2640-fe0f","๐ฎ๐ฟโโ๏ธ":"1f46e-1f3ff-200d-2640-fe0f","๐ต๏ธโโ๏ธ":"1f575-fe0f-200d-2642-fe0f","๐ต๐ปโโ๏ธ":"1f575-1f3fb-200d-2642-fe0f","๐ต๐ผโโ๏ธ":"1f575-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ๏ธ":"1f575-1f3fd-200d-2642-fe0f","๐ต๐พโโ๏ธ":"1f575-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ๏ธ":"1f575-1f3ff-200d-2642-fe0f","๐ต๏ธโโ๏ธ":"1f575-fe0f-200d-2640-fe0f","๐ต๐ปโโ๏ธ":"1f575-1f3fb-200d-2640-fe0f","๐ต๐ผโโ๏ธ":"1f575-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ๏ธ":"1f575-1f3fd-200d-2640-fe0f","๐ต๐พโโ๏ธ":"1f575-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ๏ธ":"1f575-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f482-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f482-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f482-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f482-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f482-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f482-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f482-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f482-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f482-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f482-1f3ff-200d-2640-fe0f","๐ท๐ปโโ๏ธ":"1f477-1f3fb-200d-2642-fe0f","๐ท๐ผโโ๏ธ":"1f477-1f3fc-200d-2642-fe0f","๐ท๐ฝโโ๏ธ":"1f477-1f3fd-200d-2642-fe0f","๐ท๐พโโ๏ธ":"1f477-1f3fe-200d-2642-fe0f","๐ท๐ฟโโ๏ธ":"1f477-1f3ff-200d-2642-fe0f","๐ท๐ปโโ๏ธ":"1f477-1f3fb-200d-2640-fe0f","๐ท๐ผโโ๏ธ":"1f477-1f3fc-200d-2640-fe0f","๐ท๐ฝโโ๏ธ":"1f477-1f3fd-200d-2640-fe0f","๐ท๐พโโ๏ธ":"1f477-1f3fe-200d-2640-fe0f","๐ท๐ฟโโ๏ธ":"1f477-1f3ff-200d-2640-fe0f","๐ณ๐ปโโ๏ธ":"1f473-1f3fb-200d-2642-fe0f","๐ณ๐ผโโ๏ธ":"1f473-1f3fc-200d-2642-fe0f","๐ณ๐ฝโโ๏ธ":"1f473-1f3fd-200d-2642-fe0f","๐ณ๐พโโ๏ธ":"1f473-1f3fe-200d-2642-fe0f","๐ณ๐ฟโโ๏ธ":"1f473-1f3ff-200d-2642-fe0f","๐ณ๐ปโโ๏ธ":"1f473-1f3fb-200d-2640-fe0f","๐ณ๐ผโโ๏ธ":"1f473-1f3fc-200d-2640-fe0f","๐ณ๐ฝโโ๏ธ":"1f473-1f3fd-200d-2640-fe0f","๐ณ๐พโโ๏ธ":"1f473-1f3fe-200d-2640-fe0f","๐ณ๐ฟโโ๏ธ":"1f473-1f3ff-200d-2640-fe0f","๐คต๐ปโโ๏ธ":"1f935-1f3fb-200d-2642-fe0f","๐คต๐ผโโ๏ธ":"1f935-1f3fc-200d-2642-fe0f","๐คต๐ฝโโ๏ธ":"1f935-1f3fd-200d-2642-fe0f","๐คต๐พโโ๏ธ":"1f935-1f3fe-200d-2642-fe0f","๐คต๐ฟโโ๏ธ":"1f935-1f3ff-200d-2642-fe0f","๐คต๐ปโโ๏ธ":"1f935-1f3fb-200d-2640-fe0f","๐คต๐ผโโ๏ธ":"1f935-1f3fc-200d-2640-fe0f","๐คต๐ฝโโ๏ธ":"1f935-1f3fd-200d-2640-fe0f","๐คต๐พโโ๏ธ":"1f935-1f3fe-200d-2640-fe0f","๐คต๐ฟโโ๏ธ":"1f935-1f3ff-200d-2640-fe0f","๐ฐ๐ปโโ๏ธ":"1f470-1f3fb-200d-2642-fe0f","๐ฐ๐ผโโ๏ธ":"1f470-1f3fc-200d-2642-fe0f","๐ฐ๐ฝโโ๏ธ":"1f470-1f3fd-200d-2642-fe0f","๐ฐ๐พโโ๏ธ":"1f470-1f3fe-200d-2642-fe0f","๐ฐ๐ฟโโ๏ธ":"1f470-1f3ff-200d-2642-fe0f","๐ฐ๐ปโโ๏ธ":"1f470-1f3fb-200d-2640-fe0f","๐ฐ๐ผโโ๏ธ":"1f470-1f3fc-200d-2640-fe0f","๐ฐ๐ฝโโ๏ธ":"1f470-1f3fd-200d-2640-fe0f","๐ฐ๐พโโ๏ธ":"1f470-1f3fe-200d-2640-fe0f","๐ฐ๐ฟโโ๏ธ":"1f470-1f3ff-200d-2640-fe0f","๐ฆธ๐ปโโ๏ธ":"1f9b8-1f3fb-200d-2642-fe0f","๐ฆธ๐ผโโ๏ธ":"1f9b8-1f3fc-200d-2642-fe0f","๐ฆธ๐ฝโโ๏ธ":"1f9b8-1f3fd-200d-2642-fe0f","๐ฆธ๐พโโ๏ธ":"1f9b8-1f3fe-200d-2642-fe0f","๐ฆธ๐ฟโโ๏ธ":"1f9b8-1f3ff-200d-2642-fe0f","๐ฆธ๐ปโโ๏ธ":"1f9b8-1f3fb-200d-2640-fe0f","๐ฆธ๐ผโโ๏ธ":"1f9b8-1f3fc-200d-2640-fe0f","๐ฆธ๐ฝโโ๏ธ":"1f9b8-1f3fd-200d-2640-fe0f","๐ฆธ๐พโโ๏ธ":"1f9b8-1f3fe-200d-2640-fe0f","๐ฆธ๐ฟโโ๏ธ":"1f9b8-1f3ff-200d-2640-fe0f","๐ฆน๐ปโโ๏ธ":"1f9b9-1f3fb-200d-2642-fe0f","๐ฆน๐ผโโ๏ธ":"1f9b9-1f3fc-200d-2642-fe0f","๐ฆน๐ฝโโ๏ธ":"1f9b9-1f3fd-200d-2642-fe0f","๐ฆน๐พโโ๏ธ":"1f9b9-1f3fe-200d-2642-fe0f","๐ฆน๐ฟโโ๏ธ":"1f9b9-1f3ff-200d-2642-fe0f","๐ฆน๐ปโโ๏ธ":"1f9b9-1f3fb-200d-2640-fe0f","๐ฆน๐ผโโ๏ธ":"1f9b9-1f3fc-200d-2640-fe0f","๐ฆน๐ฝโโ๏ธ":"1f9b9-1f3fd-200d-2640-fe0f","๐ฆน๐พโโ๏ธ":"1f9b9-1f3fe-200d-2640-fe0f","๐ฆน๐ฟโโ๏ธ":"1f9b9-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d9-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d9-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d9-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d9-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d9-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d9-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d9-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d9-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d9-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d9-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9da-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9da-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9da-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9da-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9da-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9da-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9da-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9da-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9da-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9da-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9db-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9db-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9db-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9db-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9db-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9db-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9db-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9db-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9db-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9db-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9dc-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9dc-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9dc-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9dc-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9dc-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9dc-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9dc-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9dc-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9dc-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9dc-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9dd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9dd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9dd-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9dd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9dd-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9dd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9dd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9dd-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9dd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9dd-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f486-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f486-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f486-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f486-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f486-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f486-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f486-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f486-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f486-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f486-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f487-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f487-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f487-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f487-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f487-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f487-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f487-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f487-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f487-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f487-1f3ff-200d-2640-fe0f","๐ถ๐ปโโ๏ธ":"1f6b6-1f3fb-200d-2642-fe0f","๐ถ๐ผโโ๏ธ":"1f6b6-1f3fc-200d-2642-fe0f","๐ถ๐ฝโโ๏ธ":"1f6b6-1f3fd-200d-2642-fe0f","๐ถ๐พโโ๏ธ":"1f6b6-1f3fe-200d-2642-fe0f","๐ถ๐ฟโโ๏ธ":"1f6b6-1f3ff-200d-2642-fe0f","๐ถ๐ปโโ๏ธ":"1f6b6-1f3fb-200d-2640-fe0f","๐ถ๐ผโโ๏ธ":"1f6b6-1f3fc-200d-2640-fe0f","๐ถ๐ฝโโ๏ธ":"1f6b6-1f3fd-200d-2640-fe0f","๐ถ๐พโโ๏ธ":"1f6b6-1f3fe-200d-2640-fe0f","๐ถ๐ฟโโ๏ธ":"1f6b6-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9cd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9cd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9cd-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9cd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9cd-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9cd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9cd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9cd-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9cd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9cd-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9ce-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9ce-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9ce-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9ce-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9ce-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9ce-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9ce-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9ce-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9ce-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9ce-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3c3-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3c3-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3c3-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3c3-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3c3-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3c3-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3c3-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3c3-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3c3-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3c3-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d6-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d6-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d6-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d6-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d6-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d6-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d6-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d6-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d6-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d6-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d7-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d7-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d7-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d7-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d7-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d7-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d7-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d7-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d7-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d7-1f3ff-200d-2640-fe0f","๐๏ธโโ๏ธ":"1f3cc-fe0f-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3cc-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3cc-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3cc-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3cc-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3cc-1f3ff-200d-2642-fe0f","๐๏ธโโ๏ธ":"1f3cc-fe0f-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3cc-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3cc-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3cc-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3cc-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3cc-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3c4-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3c4-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3c4-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3c4-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3c4-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3c4-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3c4-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3c4-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3c4-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3c4-1f3ff-200d-2640-fe0f","๐ฃ๐ปโโ๏ธ":"1f6a3-1f3fb-200d-2642-fe0f","๐ฃ๐ผโโ๏ธ":"1f6a3-1f3fc-200d-2642-fe0f","๐ฃ๐ฝโโ๏ธ":"1f6a3-1f3fd-200d-2642-fe0f","๐ฃ๐พโโ๏ธ":"1f6a3-1f3fe-200d-2642-fe0f","๐ฃ๐ฟโโ๏ธ":"1f6a3-1f3ff-200d-2642-fe0f","๐ฃ๐ปโโ๏ธ":"1f6a3-1f3fb-200d-2640-fe0f","๐ฃ๐ผโโ๏ธ":"1f6a3-1f3fc-200d-2640-fe0f","๐ฃ๐ฝโโ๏ธ":"1f6a3-1f3fd-200d-2640-fe0f","๐ฃ๐พโโ๏ธ":"1f6a3-1f3fe-200d-2640-fe0f","๐ฃ๐ฟโโ๏ธ":"1f6a3-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3ca-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3ca-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3ca-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3ca-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3ca-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3ca-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3ca-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3ca-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3ca-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3ca-1f3ff-200d-2640-fe0f","โน๏ธโโ๏ธ":"26f9-fe0f-200d-2642-fe0f","โน๐ปโโ๏ธ":"26f9-1f3fb-200d-2642-fe0f","โน๐ผโโ๏ธ":"26f9-1f3fc-200d-2642-fe0f","โน๐ฝโโ๏ธ":"26f9-1f3fd-200d-2642-fe0f","โน๐พโโ๏ธ":"26f9-1f3fe-200d-2642-fe0f","โน๐ฟโโ๏ธ":"26f9-1f3ff-200d-2642-fe0f","โน๏ธโโ๏ธ":"26f9-fe0f-200d-2640-fe0f","โน๐ปโโ๏ธ":"26f9-1f3fb-200d-2640-fe0f","โน๐ผโโ๏ธ":"26f9-1f3fc-200d-2640-fe0f","โน๐ฝโโ๏ธ":"26f9-1f3fd-200d-2640-fe0f","โน๐พโโ๏ธ":"26f9-1f3fe-200d-2640-fe0f","โน๐ฟโโ๏ธ":"26f9-1f3ff-200d-2640-fe0f","๐๏ธโโ๏ธ":"1f3cb-fe0f-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3cb-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3cb-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3cb-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3cb-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3cb-1f3ff-200d-2642-fe0f","๐๏ธโโ๏ธ":"1f3cb-fe0f-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3cb-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3cb-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3cb-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3cb-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3cb-1f3ff-200d-2640-fe0f","๐ด๐ปโโ๏ธ":"1f6b4-1f3fb-200d-2642-fe0f","๐ด๐ผโโ๏ธ":"1f6b4-1f3fc-200d-2642-fe0f","๐ด๐ฝโโ๏ธ":"1f6b4-1f3fd-200d-2642-fe0f","๐ด๐พโโ๏ธ":"1f6b4-1f3fe-200d-2642-fe0f","๐ด๐ฟโโ๏ธ":"1f6b4-1f3ff-200d-2642-fe0f","๐ด๐ปโโ๏ธ":"1f6b4-1f3fb-200d-2640-fe0f","๐ด๐ผโโ๏ธ":"1f6b4-1f3fc-200d-2640-fe0f","๐ด๐ฝโโ๏ธ":"1f6b4-1f3fd-200d-2640-fe0f","๐ด๐พโโ๏ธ":"1f6b4-1f3fe-200d-2640-fe0f","๐ด๐ฟโโ๏ธ":"1f6b4-1f3ff-200d-2640-fe0f","๐ต๐ปโโ๏ธ":"1f6b5-1f3fb-200d-2642-fe0f","๐ต๐ผโโ๏ธ":"1f6b5-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ๏ธ":"1f6b5-1f3fd-200d-2642-fe0f","๐ต๐พโโ๏ธ":"1f6b5-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ๏ธ":"1f6b5-1f3ff-200d-2642-fe0f","๐ต๐ปโโ๏ธ":"1f6b5-1f3fb-200d-2640-fe0f","๐ต๐ผโโ๏ธ":"1f6b5-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ๏ธ":"1f6b5-1f3fd-200d-2640-fe0f","๐ต๐พโโ๏ธ":"1f6b5-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ๏ธ":"1f6b5-1f3ff-200d-2640-fe0f","๐คธ๐ปโโ๏ธ":"1f938-1f3fb-200d-2642-fe0f","๐คธ๐ผโโ๏ธ":"1f938-1f3fc-200d-2642-fe0f","๐คธ๐ฝโโ๏ธ":"1f938-1f3fd-200d-2642-fe0f","๐คธ๐พโโ๏ธ":"1f938-1f3fe-200d-2642-fe0f","๐คธ๐ฟโโ๏ธ":"1f938-1f3ff-200d-2642-fe0f","๐คธ๐ปโโ๏ธ":"1f938-1f3fb-200d-2640-fe0f","๐คธ๐ผโโ๏ธ":"1f938-1f3fc-200d-2640-fe0f","๐คธ๐ฝโโ๏ธ":"1f938-1f3fd-200d-2640-fe0f","๐คธ๐พโโ๏ธ":"1f938-1f3fe-200d-2640-fe0f","๐คธ๐ฟโโ๏ธ":"1f938-1f3ff-200d-2640-fe0f","๐คฝ๐ปโโ๏ธ":"1f93d-1f3fb-200d-2642-fe0f","๐คฝ๐ผโโ๏ธ":"1f93d-1f3fc-200d-2642-fe0f","๐คฝ๐ฝโโ๏ธ":"1f93d-1f3fd-200d-2642-fe0f","๐คฝ๐พโโ๏ธ":"1f93d-1f3fe-200d-2642-fe0f","๐คฝ๐ฟโโ๏ธ":"1f93d-1f3ff-200d-2642-fe0f","๐คฝ๐ปโโ๏ธ":"1f93d-1f3fb-200d-2640-fe0f","๐คฝ๐ผโโ๏ธ":"1f93d-1f3fc-200d-2640-fe0f","๐คฝ๐ฝโโ๏ธ":"1f93d-1f3fd-200d-2640-fe0f","๐คฝ๐พโโ๏ธ":"1f93d-1f3fe-200d-2640-fe0f","๐คฝ๐ฟโโ๏ธ":"1f93d-1f3ff-200d-2640-fe0f","๐คพ๐ปโโ๏ธ":"1f93e-1f3fb-200d-2642-fe0f","๐คพ๐ผโโ๏ธ":"1f93e-1f3fc-200d-2642-fe0f","๐คพ๐ฝโโ๏ธ":"1f93e-1f3fd-200d-2642-fe0f","๐คพ๐พโโ๏ธ":"1f93e-1f3fe-200d-2642-fe0f","๐คพ๐ฟโโ๏ธ":"1f93e-1f3ff-200d-2642-fe0f","๐คพ๐ปโโ๏ธ":"1f93e-1f3fb-200d-2640-fe0f","๐คพ๐ผโโ๏ธ":"1f93e-1f3fc-200d-2640-fe0f","๐คพ๐ฝโโ๏ธ":"1f93e-1f3fd-200d-2640-fe0f","๐คพ๐พโโ๏ธ":"1f93e-1f3fe-200d-2640-fe0f","๐คพ๐ฟโโ๏ธ":"1f93e-1f3ff-200d-2640-fe0f","๐คน๐ปโโ๏ธ":"1f939-1f3fb-200d-2642-fe0f","๐คน๐ผโโ๏ธ":"1f939-1f3fc-200d-2642-fe0f","๐คน๐ฝโโ๏ธ":"1f939-1f3fd-200d-2642-fe0f","๐คน๐พโโ๏ธ":"1f939-1f3fe-200d-2642-fe0f","๐คน๐ฟโโ๏ธ":"1f939-1f3ff-200d-2642-fe0f","๐คน๐ปโโ๏ธ":"1f939-1f3fb-200d-2640-fe0f","๐คน๐ผโโ๏ธ":"1f939-1f3fc-200d-2640-fe0f","๐คน๐ฝโโ๏ธ":"1f939-1f3fd-200d-2640-fe0f","๐คน๐พโโ๏ธ":"1f939-1f3fe-200d-2640-fe0f","๐คน๐ฟโโ๏ธ":"1f939-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d8-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d8-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d8-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d8-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d8-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d8-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d8-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d8-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d8-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d8-1f3ff-200d-2640-fe0f","๐งโ๐คโ๐ง":"1f9d1-200d-1f91d-200d-1f9d1","๐ฉโโคโ๐จ":"1f469-200d-2764-fe0f-200d-1f468","๐จโโคโ๐จ":"1f468-200d-2764-fe0f-200d-1f468","๐ฉโโคโ๐ฉ":"1f469-200d-2764-fe0f-200d-1f469","๐จโ๐ฉโ๐ฆ":"1f468-200d-1f469-200d-1f466","๐จโ๐ฉโ๐ง":"1f468-200d-1f469-200d-1f467","๐จโ๐จโ๐ฆ":"1f468-200d-1f468-200d-1f466","๐จโ๐จโ๐ง":"1f468-200d-1f468-200d-1f467","๐ฉโ๐ฉโ๐ฆ":"1f469-200d-1f469-200d-1f466","๐ฉโ๐ฉโ๐ง":"1f469-200d-1f469-200d-1f467","๐จโ๐ฆโ๐ฆ":"1f468-200d-1f466-200d-1f466","๐จโ๐งโ๐ฆ":"1f468-200d-1f467-200d-1f466","๐จโ๐งโ๐ง":"1f468-200d-1f467-200d-1f467","๐ฉโ๐ฆโ๐ฆ":"1f469-200d-1f466-200d-1f466","๐ฉโ๐งโ๐ฆ":"1f469-200d-1f467-200d-1f466","๐ฉโ๐งโ๐ง":"1f469-200d-1f467-200d-1f467","๐ณ๏ธโโง๏ธ":"1f3f3-fe0f-200d-26a7-fe0f","๐ฉโโค๏ธโ๐จ":"1f469-200d-2764-fe0f-200d-1f468","๐จโโค๏ธโ๐จ":"1f468-200d-2764-fe0f-200d-1f468","๐ฉโโค๏ธโ๐ฉ":"1f469-200d-2764-fe0f-200d-1f469","๐ง๐ปโ๐คโ๐ง๐ป":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ปโ๐คโ๐ง๐ผ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ปโ๐คโ๐ง๐ฝ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ปโ๐คโ๐ง๐พ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ปโ๐คโ๐ง๐ฟ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐ผโ๐คโ๐ง๐ป":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ผโ๐คโ๐ง๐ผ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ผโ๐คโ๐ง๐ฝ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ผโ๐คโ๐ง๐พ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ผโ๐คโ๐ง๐ฟ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐ฝโ๐คโ๐ง๐ป":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ฝโ๐คโ๐ง๐ผ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ฝโ๐คโ๐ง๐ฝ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ฝโ๐คโ๐ง๐พ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ฝโ๐คโ๐ง๐ฟ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐พโ๐คโ๐ง๐ป":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐พโ๐คโ๐ง๐ผ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐พโ๐คโ๐ง๐ฝ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐พโ๐คโ๐ง๐พ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐พโ๐คโ๐ง๐ฟ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐ฟโ๐คโ๐ง๐ป":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ฟโ๐คโ๐ง๐ผ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ฟโ๐คโ๐ง๐ฝ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ฟโ๐คโ๐ง๐พ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ฟโ๐คโ๐ง๐ฟ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","๐ฉ๐ปโ๐คโ๐ฉ๐ผ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐ปโ๐คโ๐ฉ๐ฝ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐ปโ๐คโ๐ฉ๐พ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ปโ๐คโ๐ฉ๐ฟ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐ผโ๐คโ๐ฉ๐ป":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐ผโ๐คโ๐ฉ๐ฝ":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐ผโ๐คโ๐ฉ๐พ":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ผโ๐คโ๐ฉ๐ฟ":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐ฝโ๐คโ๐ฉ๐ป":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐ฝโ๐คโ๐ฉ๐ผ":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐ฝโ๐คโ๐ฉ๐พ":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ฝโ๐คโ๐ฉ๐ฟ":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐พโ๐คโ๐ฉ๐ป":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐พโ๐คโ๐ฉ๐ผ":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐พโ๐คโ๐ฉ๐ฝ":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐พโ๐คโ๐ฉ๐ฟ":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐ฟโ๐คโ๐ฉ๐ป":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐ฟโ๐คโ๐ฉ๐ผ":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐ฟโ๐คโ๐ฉ๐ฝ":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐ฟโ๐คโ๐ฉ๐พ":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ปโ๐คโ๐จ๐ผ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐ปโ๐คโ๐จ๐ฝ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐ปโ๐คโ๐จ๐พ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","๐ฉ๐ปโ๐คโ๐จ๐ฟ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐ผโ๐คโ๐จ๐ป":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐ผโ๐คโ๐จ๐ฝ":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐ผโ๐คโ๐จ๐พ":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","๐ฉ๐ผโ๐คโ๐จ๐ฟ":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐ฝโ๐คโ๐จ๐ป":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐ฝโ๐คโ๐จ๐ผ":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐ฝโ๐คโ๐จ๐พ":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","๐ฉ๐ฝโ๐คโ๐จ๐ฟ":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐พโ๐คโ๐จ๐ป":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐พโ๐คโ๐จ๐ผ":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐พโ๐คโ๐จ๐ฝ":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐พโ๐คโ๐จ๐ฟ":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐ฟโ๐คโ๐จ๐ป":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐ฟโ๐คโ๐จ๐ผ":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐ฟโ๐คโ๐จ๐ฝ":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐ฟโ๐คโ๐จ๐พ":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","๐จ๐ปโ๐คโ๐จ๐ผ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","๐จ๐ปโ๐คโ๐จ๐ฝ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","๐จ๐ปโ๐คโ๐จ๐พ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","๐จ๐ปโ๐คโ๐จ๐ฟ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","๐จ๐ผโ๐คโ๐จ๐ป":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","๐จ๐ผโ๐คโ๐จ๐ฝ":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","๐จ๐ผโ๐คโ๐จ๐พ":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","๐จ๐ผโ๐คโ๐จ๐ฟ":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","๐จ๐ฝโ๐คโ๐จ๐ป":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","๐จ๐ฝโ๐คโ๐จ๐ผ":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","๐จ๐ฝโ๐คโ๐จ๐พ":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","๐จ๐ฝโ๐คโ๐จ๐ฟ":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","๐จ๐พโ๐คโ๐จ๐ป":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","๐จ๐พโ๐คโ๐จ๐ผ":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","๐จ๐พโ๐คโ๐จ๐ฝ":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","๐จ๐พโ๐คโ๐จ๐ฟ":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","๐จ๐ฟโ๐คโ๐จ๐ป":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","๐จ๐ฟโ๐คโ๐จ๐ผ":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","๐จ๐ฟโ๐คโ๐จ๐ฝ":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","๐จ๐ฟโ๐คโ๐จ๐พ":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","๐ฉโโคโ๐โ๐จ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","๐จโโคโ๐โ๐จ":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","๐ฉโโคโ๐โ๐ฉ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","๐ง๐ปโโคโ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ปโโคโ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ปโโคโ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ปโโคโ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ผโโคโ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ผโโคโ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ผโโคโ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ผโโคโ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฝโโคโ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฝโโคโ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฝโโคโ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ฝโโคโ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐พโโคโ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐พโโคโ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐พโโคโ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐พโโคโ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฟโโคโ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฟโโคโ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฟโโคโ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ฟโโคโ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ฉ๐ปโโคโ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ปโโคโ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ปโโคโ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ปโโคโ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ปโโคโ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ผโโคโ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ผโโคโ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ผโโคโ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ผโโคโ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ผโโคโ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฝโโคโ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฝโโคโ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฝโโคโ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฝโโคโ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฝโโคโ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐พโโคโ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐พโโคโ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐พโโคโ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐พโโคโ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐พโโคโ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฟโโคโ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฟโโคโ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฟโโคโ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฟโโคโ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฟโโคโ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ปโโคโ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ปโโคโ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ปโโคโ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ปโโคโ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ปโโคโ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ผโโคโ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ผโโคโ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ผโโคโ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ผโโคโ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ผโโคโ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฝโโคโ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฝโโคโ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฝโโคโ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฝโโคโ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฝโโคโ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐พโโคโ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐พโโคโ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐พโโคโ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐พโโคโ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐พโโคโ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฟโโคโ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฟโโคโ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฟโโคโ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฟโโคโ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฟโโคโ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ปโโคโ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ปโโคโ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ปโโคโ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ปโโคโ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ปโโคโ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ผโโคโ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ผโโคโ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ผโโคโ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ผโโคโ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ผโโคโ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฝโโคโ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฝโโคโ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฝโโคโ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฝโโคโ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฝโโคโ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐พโโคโ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐พโโคโ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐พโโคโ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐พโโคโ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐พโโคโ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฟโโคโ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฟโโคโ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฟโโคโ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฟโโคโ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฟโโคโ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","๐จโ๐ฉโ๐งโ๐ฆ":"1f468-200d-1f469-200d-1f467-200d-1f466","๐จโ๐ฉโ๐ฆโ๐ฆ":"1f468-200d-1f469-200d-1f466-200d-1f466","๐จโ๐ฉโ๐งโ๐ง":"1f468-200d-1f469-200d-1f467-200d-1f467","๐จโ๐จโ๐งโ๐ฆ":"1f468-200d-1f468-200d-1f467-200d-1f466","๐จโ๐จโ๐ฆโ๐ฆ":"1f468-200d-1f468-200d-1f466-200d-1f466","๐จโ๐จโ๐งโ๐ง":"1f468-200d-1f468-200d-1f467-200d-1f467","๐ฉโ๐ฉโ๐งโ๐ฆ":"1f469-200d-1f469-200d-1f467-200d-1f466","๐ฉโ๐ฉโ๐ฆโ๐ฆ":"1f469-200d-1f469-200d-1f466-200d-1f466","๐ฉโ๐ฉโ๐งโ๐ง":"1f469-200d-1f469-200d-1f467-200d-1f467","๐ด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","๐ด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","๐ด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","๐ฉโโค๏ธโ๐โ๐จ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","๐จโโค๏ธโ๐โ๐จ":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","๐ฉโโค๏ธโ๐โ๐ฉ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","๐ง๐ปโโค๏ธโ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ปโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ปโโค๏ธโ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ปโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ผโโค๏ธโ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ผโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ผโโค๏ธโ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ผโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฝโโค๏ธโ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฝโโค๏ธโ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฝโโค๏ธโ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ฝโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐พโโค๏ธโ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐พโโค๏ธโ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐พโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐พโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฟโโค๏ธโ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฟโโค๏ธโ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฟโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ฟโโค๏ธโ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ฉ๐ปโโค๏ธโ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ปโโค๏ธโ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ปโโค๏ธโ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ปโโค๏ธโ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ปโโค๏ธโ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ผโโค๏ธโ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ผโโค๏ธโ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ผโโค๏ธโ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ผโโค๏ธโ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ผโโค๏ธโ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฝโโค๏ธโ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฝโโค๏ธโ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฝโโค๏ธโ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฝโโค๏ธโ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฝโโค๏ธโ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐พโโค๏ธโ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐พโโค๏ธโ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐พโโค๏ธโ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐พโโค๏ธโ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐พโโค๏ธโ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฟโโค๏ธโ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฟโโค๏ธโ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฟโโค๏ธโ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฟโโค๏ธโ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฟโโค๏ธโ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ปโโค๏ธโ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ปโโค๏ธโ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ปโโค๏ธโ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ปโโค๏ธโ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ปโโค๏ธโ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ผโโค๏ธโ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ผโโค๏ธโ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ผโโค๏ธโ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ผโโค๏ธโ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ผโโค๏ธโ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฝโโค๏ธโ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฝโโค๏ธโ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฝโโค๏ธโ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฝโโค๏ธโ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฝโโค๏ธโ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐พโโค๏ธโ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐พโโค๏ธโ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐พโโค๏ธโ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐พโโค๏ธโ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐พโโค๏ธโ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฟโโค๏ธโ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฟโโค๏ธโ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฟโโค๏ธโ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฟโโค๏ธโ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฟโโค๏ธโ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ปโโค๏ธโ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ปโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ปโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ปโโค๏ธโ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ปโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ผโโค๏ธโ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ผโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ผโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ผโโค๏ธโ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ผโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฝโโค๏ธโ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐พโโค๏ธโ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐พโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐พโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐พโโค๏ธโ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐พโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฟโโค๏ธโ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","๐ง๐ปโโคโ๐โ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ปโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ปโโคโ๐โ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ปโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ผโโคโ๐โ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ผโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ผโโคโ๐โ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ผโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฝโโคโ๐โ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฝโโคโ๐โ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฝโโคโ๐โ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ฝโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐พโโคโ๐โ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐พโโคโ๐โ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐พโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐พโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฟโโคโ๐โ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฟโโคโ๐โ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฟโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ฟโโคโ๐โ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ฉ๐ปโโคโ๐โ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ปโโคโ๐โ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ปโโคโ๐โ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ปโโคโ๐โ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ปโโคโ๐โ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ผโโคโ๐โ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ผโโคโ๐โ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ผโโคโ๐โ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ผโโคโ๐โ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ผโโคโ๐โ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฝโโคโ๐โ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฝโโคโ๐โ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฝโโคโ๐โ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฝโโคโ๐โ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฝโโคโ๐โ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐พโโคโ๐โ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐พโโคโ๐โ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐พโโคโ๐โ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐พโโคโ๐โ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐พโโคโ๐โ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฟโโคโ๐โ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฟโโคโ๐โ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฟโโคโ๐โ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฟโโคโ๐โ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฟโโคโ๐โ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ปโโคโ๐โ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ปโโคโ๐โ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ปโโคโ๐โ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ปโโคโ๐โ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ปโโคโ๐โ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ผโโคโ๐โ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ผโโคโ๐โ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ผโโคโ๐โ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ผโโคโ๐โ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ผโโคโ๐โ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฝโโคโ๐โ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฝโโคโ๐โ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฝโโคโ๐โ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฝโโคโ๐โ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฝโโคโ๐โ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐พโโคโ๐โ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐พโโคโ๐โ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐พโโคโ๐โ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐พโโคโ๐โ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐พโโคโ๐โ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฟโโคโ๐โ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฟโโคโ๐โ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฟโโคโ๐โ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฟโโคโ๐โ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฟโโคโ๐โ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ปโโคโ๐โ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ปโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ปโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ปโโคโ๐โ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ปโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ผโโคโ๐โ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ผโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ผโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ผโโคโ๐โ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ผโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฝโโคโ๐โ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐พโโคโ๐โ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐พโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐พโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐พโโคโ๐โ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐พโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฟโโคโ๐โ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ง๐ปโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ปโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ปโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ปโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ผโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ผโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ผโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ผโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฝโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฝโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฝโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ฝโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐พโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐พโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐พโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐พโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฟโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฟโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฟโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ฟโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐พโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ปโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ปโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ปโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ปโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ปโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ผโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ผโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ผโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ผโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ผโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฝโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐พโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐พโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐พโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐พโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐พโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฟโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff"} \ No newline at end of file diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_mart_data_light.js b/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.js index 45086fc4c..45086fc4c 100644 --- a/app/javascript/flavours/glitch/util/emoji/emoji_mart_data_light.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_mart_data_light.js diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js b/app/javascript/flavours/glitch/features/emoji/emoji_mart_search_light.js index 70694ab6d..70694ab6d 100644 --- a/app/javascript/flavours/glitch/util/emoji/emoji_mart_search_light.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_mart_search_light.js diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_picker.js b/app/javascript/flavours/glitch/features/emoji/emoji_picker.js index 044d38cb2..044d38cb2 100644 --- a/app/javascript/flavours/glitch/util/emoji/emoji_picker.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_picker.js diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_unicode_mapping_light.js b/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.js index 918684c31..918684c31 100644 --- a/app/javascript/flavours/glitch/util/emoji/emoji_unicode_mapping_light.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_unicode_mapping_light.js diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_utils.js b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js index dbf725c1f..dbf725c1f 100644 --- a/app/javascript/flavours/glitch/util/emoji/emoji_utils.js +++ b/app/javascript/flavours/glitch/features/emoji/emoji_utils.js diff --git a/app/javascript/flavours/glitch/util/emoji/unicode_to_filename.js b/app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js index c75c4cd7d..c75c4cd7d 100644 --- a/app/javascript/flavours/glitch/util/emoji/unicode_to_filename.js +++ b/app/javascript/flavours/glitch/features/emoji/unicode_to_filename.js diff --git a/app/javascript/flavours/glitch/util/emoji/unicode_to_unified_name.js b/app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js index d29550f12..d29550f12 100644 --- a/app/javascript/flavours/glitch/util/emoji/unicode_to_unified_name.js +++ b/app/javascript/flavours/glitch/features/emoji/unicode_to_unified_name.js diff --git a/app/javascript/flavours/glitch/features/explore/components/story.js b/app/javascript/flavours/glitch/features/explore/components/story.js new file mode 100644 index 000000000..8270d3ccb --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/components/story.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Blurhash from 'flavours/glitch/components/blurhash'; +import { accountsCountRenderer } from 'flavours/glitch/components/hashtag'; +import ShortNumber from 'flavours/glitch/components/short_number'; +import Skeleton from 'flavours/glitch/components/skeleton'; +import classNames from 'classnames'; + +export default class Story extends React.PureComponent { + + static propTypes = { + url: PropTypes.string, + title: PropTypes.string, + publisher: PropTypes.string, + sharedTimes: PropTypes.number, + thumbnail: PropTypes.string, + blurhash: PropTypes.string, + }; + + state = { + thumbnailLoaded: false, + }; + + handleImageLoad = () => this.setState({ thumbnailLoaded: true }); + + render () { + const { url, title, publisher, sharedTimes, thumbnail, blurhash } = this.props; + + const { thumbnailLoaded } = this.state; + + return ( + <a className='story' href={url} target='blank' rel='noopener'> + <div className='story__details'> + <div className='story__details__publisher'>{publisher ? publisher : <Skeleton width={50} />}</div> + <div className='story__details__title'>{title ? title : <Skeleton />}</div> + <div className='story__details__shared'>{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div> + </div> + + <div className='story__thumbnail'> + {thumbnail ? ( + <React.Fragment> + <div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div> + <img src={thumbnail} onLoad={this.handleImageLoad} alt='' role='presentation' /> + </React.Fragment> + ) : <Skeleton />} + </div> + </a> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/explore/index.js b/app/javascript/flavours/glitch/features/explore/index.js new file mode 100644 index 000000000..24fa26eec --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/index.js @@ -0,0 +1,97 @@ +import React from 'react'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import Column from 'flavours/glitch/components/column'; +import ColumnHeader from 'flavours/glitch/components/column_header'; +import { NavLink, Switch, Route } from 'react-router-dom'; +import Links from './links'; +import Tags from './tags'; +import Statuses from './statuses'; +import Suggestions from './suggestions'; +import Search from 'flavours/glitch/features/compose/containers/search_container'; +import SearchResults from './results'; +import { showTrends } from 'flavours/glitch/initial_state'; +import { Helmet } from 'react-helmet'; + +const messages = defineMessages({ + title: { id: 'explore.title', defaultMessage: 'Explore' }, + searchResults: { id: 'explore.search_results', defaultMessage: 'Search results' }, +}); + +const mapStateToProps = state => ({ + layout: state.getIn(['meta', 'layout']), + isSearching: state.getIn(['search', 'submitted']) || !showTrends, +}); + +export default @connect(mapStateToProps) +@injectIntl +class Explore extends React.PureComponent { + + static contextTypes = { + router: PropTypes.object, + identity: PropTypes.object, + }; + + static propTypes = { + intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, + isSearching: PropTypes.bool, + }; + + handleHeaderClick = () => { + this.column.scrollTop(); + } + + setRef = c => { + this.column = c; + } + + render () { + const { intl, multiColumn, isSearching } = this.props; + const { signedIn } = this.context.identity; + + return ( + <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> + <ColumnHeader + icon={isSearching ? 'search' : 'hashtag'} + title={intl.formatMessage(isSearching ? messages.searchResults : messages.title)} + onClick={this.handleHeaderClick} + multiColumn={multiColumn} + /> + + <div className='explore__search-header'> + <Search /> + </div> + + <div className='scrollable scrollable--flex'> + {isSearching ? ( + <SearchResults /> + ) : ( + <React.Fragment> + <div className='account__section-headline'> + <NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink> + <NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink> + <NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink> + {signedIn && <NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>} + </div> + + <Switch> + <Route path='/explore/tags' component={Tags} /> + <Route path='/explore/links' component={Links} /> + <Route path='/explore/suggestions' component={Suggestions} /> + <Route exact path={['/explore', '/explore/posts', '/search']} component={Statuses} componentParams={{ multiColumn }} /> + </Switch> + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content={isSearching ? 'noindex' : 'all'} /> + </Helmet> + </React.Fragment> + )} + </div> + </Column> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/explore/links.js b/app/javascript/flavours/glitch/features/explore/links.js new file mode 100644 index 000000000..092f86b29 --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/links.js @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Story from './components/story'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { connect } from 'react-redux'; +import { fetchTrendingLinks } from 'flavours/glitch/actions/trends'; +import { FormattedMessage } from 'react-intl'; +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; + +const mapStateToProps = state => ({ + links: state.getIn(['trends', 'links', 'items']), + isLoading: state.getIn(['trends', 'links', 'isLoading']), +}); + +export default @connect(mapStateToProps) +class Links extends React.PureComponent { + + static propTypes = { + links: ImmutablePropTypes.list, + isLoading: PropTypes.bool, + dispatch: PropTypes.func.isRequired, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchTrendingLinks()); + } + + render () { + const { isLoading, links } = this.props; + + const banner = ( + <DismissableBanner id='explore/links'> + <FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These news stories are being talked about by people on this and other servers of the decentralized network right now.' /> + </DismissableBanner> + ); + + if (!isLoading && links.isEmpty()) { + return ( + <div className='explore__links scrollable scrollable--flex'> + {banner} + + <div className='empty-column-indicator'> + <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> + </div> + </div> + ); + } + + return ( + <div className='explore__links'> + {banner} + + {isLoading ? (<LoadingIndicator />) : links.map(link => ( + <Story + key={link.get('id')} + url={link.get('url')} + title={link.get('title')} + publisher={link.get('provider_name')} + sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1} + thumbnail={link.get('image')} + blurhash={link.get('blurhash')} + /> + ))} + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/explore/results.js b/app/javascript/flavours/glitch/features/explore/results.js new file mode 100644 index 000000000..892980d95 --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/results.js @@ -0,0 +1,126 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { expandSearch } from 'flavours/glitch/actions/search'; +import Account from 'flavours/glitch/containers/account_container'; +import Status from 'flavours/glitch/containers/status_container'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; +import { List as ImmutableList } from 'immutable'; +import LoadMore from 'flavours/glitch/components/load_more'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { Helmet } from 'react-helmet'; + +const messages = defineMessages({ + title: { id: 'search_results.title', defaultMessage: 'Search for {q}' }, +}); + +const mapStateToProps = state => ({ + isLoading: state.getIn(['search', 'isLoading']), + results: state.getIn(['search', 'results']), + q: state.getIn(['search', 'searchTerm']), +}); + +const appendLoadMore = (id, list, onLoadMore) => { + if (list.size >= 5) { + return list.push(<LoadMore key={`${id}-load-more`} visible onClick={onLoadMore} />); + } else { + return list; + } +}; + +const renderAccounts = (results, onLoadMore) => appendLoadMore('accounts', results.get('accounts', ImmutableList()).map(item => ( + <Account key={`account-${item}`} id={item} /> +)), onLoadMore); + +const renderHashtags = (results, onLoadMore) => appendLoadMore('hashtags', results.get('hashtags', ImmutableList()).map(item => ( + <Hashtag key={`tag-${item.get('name')}`} hashtag={item} /> +)), onLoadMore); + +const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', results.get('statuses', ImmutableList()).map(item => ( + <Status key={`status-${item}`} id={item} /> +)), onLoadMore); + +export default @connect(mapStateToProps) +@injectIntl +class Results extends React.PureComponent { + + static propTypes = { + results: ImmutablePropTypes.map, + isLoading: PropTypes.bool, + multiColumn: PropTypes.bool, + dispatch: PropTypes.func.isRequired, + q: PropTypes.string, + intl: PropTypes.object, + }; + + state = { + type: 'all', + }; + + handleSelectAll = () => this.setState({ type: 'all' }); + handleSelectAccounts = () => this.setState({ type: 'accounts' }); + handleSelectHashtags = () => this.setState({ type: 'hashtags' }); + handleSelectStatuses = () => this.setState({ type: 'statuses' }); + handleLoadMoreAccounts = () => this.loadMore('accounts'); + handleLoadMoreStatuses = () => this.loadMore('statuses'); + handleLoadMoreHashtags = () => this.loadMore('hashtags'); + + loadMore (type) { + const { dispatch } = this.props; + dispatch(expandSearch(type)); + } + + render () { + const { intl, isLoading, q, results } = this.props; + const { type } = this.state; + + let filteredResults = ImmutableList(); + + if (!isLoading) { + switch(type) { + case 'all': + filteredResults = filteredResults.concat(renderAccounts(results, this.handleLoadMoreAccounts), renderHashtags(results, this.handleLoadMoreHashtags), renderStatuses(results, this.handleLoadMoreStatuses)); + break; + case 'accounts': + filteredResults = filteredResults.concat(renderAccounts(results, this.handleLoadMoreAccounts)); + break; + case 'hashtags': + filteredResults = filteredResults.concat(renderHashtags(results, this.handleLoadMoreHashtags)); + break; + case 'statuses': + filteredResults = filteredResults.concat(renderStatuses(results, this.handleLoadMoreStatuses)); + break; + } + + if (filteredResults.size === 0) { + filteredResults = ( + <div className='empty-column-indicator'> + <FormattedMessage id='search_results.nothing_found' defaultMessage='Could not find anything for these search terms' /> + </div> + ); + } + } + + return ( + <React.Fragment> + <div className='account__section-headline'> + <button onClick={this.handleSelectAll} className={type === 'all' && 'active'}><FormattedMessage id='search_results.all' defaultMessage='All' /></button> + <button onClick={this.handleSelectAccounts} className={type === 'accounts' && 'active'}><FormattedMessage id='search_results.accounts' defaultMessage='People' /></button> + <button onClick={this.handleSelectHashtags} className={type === 'hashtags' && 'active'}><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></button> + <button onClick={this.handleSelectStatuses} className={type === 'statuses' && 'active'}><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></button> + </div> + + <div className='explore__search-results'> + {isLoading ? <LoadingIndicator /> : filteredResults} + </div> + + <Helmet> + <title>{intl.formatMessage(messages.title, { q })}</title> + </Helmet> + </React.Fragment> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/explore/statuses.js b/app/javascript/flavours/glitch/features/explore/statuses.js new file mode 100644 index 000000000..0a5c9de23 --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/statuses.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import StatusList from 'flavours/glitch/components/status_list'; +import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends'; +import { debounce } from 'lodash'; +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; + +const mapStateToProps = state => ({ + statusIds: state.getIn(['status_lists', 'trending', 'items']), + isLoading: state.getIn(['status_lists', 'trending', 'isLoading'], true), + hasMore: !!state.getIn(['status_lists', 'trending', 'next']), +}); + +export default @connect(mapStateToProps) +class Statuses extends React.PureComponent { + + static propTypes = { + statusIds: ImmutablePropTypes.list, + isLoading: PropTypes.bool, + hasMore: PropTypes.bool, + multiColumn: PropTypes.bool, + dispatch: PropTypes.func.isRequired, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchTrendingStatuses()); + } + + handleLoadMore = debounce(() => { + const { dispatch } = this.props; + dispatch(expandTrendingStatuses()); + }, 300, { leading: true }) + + render () { + const { isLoading, hasMore, statusIds, multiColumn } = this.props; + + const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />; + + return ( + <> + <DismissableBanner id='explore/statuses'> + <FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These posts from this and other servers in the decentralized network are gaining traction on this server right now.' /> + </DismissableBanner> + + <StatusList + trackScroll + statusIds={statusIds} + scrollKey='explore-statuses' + hasMore={hasMore} + isLoading={isLoading} + onLoadMore={this.handleLoadMore} + emptyMessage={emptyMessage} + bindToDocument={!multiColumn} + withCounters + /> + </> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/explore/suggestions.js b/app/javascript/flavours/glitch/features/explore/suggestions.js new file mode 100644 index 000000000..52e5ce62b --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/suggestions.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import AccountCard from 'flavours/glitch/features/directory/components/account_card'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { connect } from 'react-redux'; +import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions'; +import { FormattedMessage } from 'react-intl'; + +const mapStateToProps = state => ({ + suggestions: state.getIn(['suggestions', 'items']), + isLoading: state.getIn(['suggestions', 'isLoading']), +}); + +export default @connect(mapStateToProps) +class Suggestions extends React.PureComponent { + + static propTypes = { + isLoading: PropTypes.bool, + suggestions: ImmutablePropTypes.list, + dispatch: PropTypes.func.isRequired, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchSuggestions(true)); + } + + handleDismiss = (accountId) => { + const { dispatch } = this.props; + dispatch(dismissSuggestion(accountId)); + } + + render () { + const { isLoading, suggestions } = this.props; + + if (!isLoading && suggestions.isEmpty()) { + return ( + <div className='explore__suggestions scrollable scrollable--flex'> + <div className='empty-column-indicator'> + <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> + </div> + </div> + ); + } + + return ( + <div className='explore__suggestions'> + {isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => ( + <AccountCard key={suggestion.get('account')} id={suggestion.get('account')} onDismiss={suggestion.get('source') === 'past_interactions' ? this.handleDismiss : null} /> + ))} + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/explore/tags.js b/app/javascript/flavours/glitch/features/explore/tags.js new file mode 100644 index 000000000..938036b64 --- /dev/null +++ b/app/javascript/flavours/glitch/features/explore/tags.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { connect } from 'react-redux'; +import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends'; +import { FormattedMessage } from 'react-intl'; +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; + +const mapStateToProps = state => ({ + hashtags: state.getIn(['trends', 'tags', 'items']), + isLoadingHashtags: state.getIn(['trends', 'tags', 'isLoading']), +}); + +export default @connect(mapStateToProps) +class Tags extends React.PureComponent { + + static propTypes = { + hashtags: ImmutablePropTypes.list, + isLoading: PropTypes.bool, + dispatch: PropTypes.func.isRequired, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(fetchTrendingHashtags()); + } + + render () { + const { isLoading, hashtags } = this.props; + + const banner = ( + <DismissableBanner id='explore/tags'> + <FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These hashtags are gaining traction among people on this and other servers of the decentralized network right now.' /> + </DismissableBanner> + ); + + if (!isLoading && hashtags.isEmpty()) { + return ( + <div className='explore__links scrollable scrollable--flex'> + {banner} + + <div className='empty-column-indicator'> + <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> + </div> + </div> + ); + } + + return ( + <div className='explore__links'> + {banner} + + {isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => ( + <Hashtag key={hashtag.get('name')} hashtag={hashtag} /> + ))} + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.js b/app/javascript/flavours/glitch/features/favourited_statuses/index.js index 4df3aaa64..a03e1a4eb 100644 --- a/app/javascript/flavours/glitch/features/favourited_statuses/index.js +++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.js @@ -1,15 +1,16 @@ -import React from 'react'; -import { connect } from 'react-redux'; +import { debounce } from 'lodash'; import PropTypes from 'prop-types'; +import React from 'react'; +import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { debounce } from 'lodash'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'flavours/glitch/actions/favourites'; -import Column from 'flavours/glitch/features/ui/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; -import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import StatusList from 'flavours/glitch/components/status_list'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import Column from 'flavours/glitch/features/ui/components/column'; const messages = defineMessages({ heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, @@ -95,6 +96,11 @@ class Favourites extends ImmutablePureComponent { emptyMessage={emptyMessage} bindToDocument={!multiColumn} /> + + <Helmet> + <title>{intl.formatMessage(messages.heading)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/favourites/index.js b/app/javascript/flavours/glitch/features/favourites/index.js index a51562e71..47c3279c4 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.js +++ b/app/javascript/flavours/glitch/features/favourites/index.js @@ -1,16 +1,17 @@ -import React from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import ColumnHeader from 'flavours/glitch/components/column_header'; +import Icon from 'flavours/glitch/components/icon'; import { fetchFavourites } from 'flavours/glitch/actions/interactions'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Column from 'flavours/glitch/features/ui/components/column'; -import Icon from 'flavours/glitch/components/icon'; -import ColumnHeader from 'flavours/glitch/components/column_header'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import ScrollableList from '../../components/scrollable_list'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.favourited_by', defaultMessage: 'Favourited by' }, @@ -91,6 +92,10 @@ class Favourites extends ImmutablePureComponent { <AccountContainer key={id} id={id} withNote={false} />, )} </ScrollableList> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/filters/added_to_filter.js b/app/javascript/flavours/glitch/features/filters/added_to_filter.js index f777ca429..becb170cd 100644 --- a/app/javascript/flavours/glitch/features/filters/added_to_filter.js +++ b/app/javascript/flavours/glitch/features/filters/added_to_filter.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; -import { toServerSideType } from 'flavours/glitch/util/filters'; +import { toServerSideType } from 'flavours/glitch/utils/filters'; import Button from 'flavours/glitch/components/button'; import { connect } from 'react-redux'; diff --git a/app/javascript/flavours/glitch/features/filters/select_filter.js b/app/javascript/flavours/glitch/features/filters/select_filter.js index 5321dbb96..5391766c9 100644 --- a/app/javascript/flavours/glitch/features/filters/select_filter.js +++ b/app/javascript/flavours/glitch/features/filters/select_filter.js @@ -2,8 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { toServerSideType } from 'flavours/glitch/util/filters'; -import { loupeIcon, deleteIcon } from 'flavours/glitch/util/icons'; +import { toServerSideType } from 'flavours/glitch/utils/filters'; +import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons'; import Icon from 'flavours/glitch/components/icon'; import fuzzysort from 'fuzzysort'; diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.js b/app/javascript/flavours/glitch/features/follow_recommendations/index.js index d050e3cc7..d9d962b7c 100644 --- a/app/javascript/flavours/glitch/features/follow_recommendations/index.js +++ b/app/javascript/flavours/glitch/features/follow_recommendations/index.js @@ -10,9 +10,9 @@ import { requestBrowserPermission } from 'flavours/glitch/actions/notifications' import { markAsPartial } from 'flavours/glitch/actions/timelines'; import Column from 'flavours/glitch/features/ui/components/column'; import Account from './components/account'; -import Logo from 'flavours/glitch/components/logo'; import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg'; import Button from 'flavours/glitch/components/button'; +import { Helmet } from 'react-helmet'; const mapStateToProps = state => ({ suggestions: state.getIn(['suggestions', 'items']), @@ -78,7 +78,10 @@ class FollowRecommendations extends ImmutablePureComponent { <Column> <div className='scrollable follow-recommendations-container'> <div className='column-title'> - <Logo /> + <svg viewBox='0 0 79 79' className='logo'> + <use xlinkHref='#logo-symbol-icon' /> + </svg> + <h3><FormattedMessage id='follow_recommendations.heading' defaultMessage="Follow people you'd like to see posts from! Here are some suggestions." /></h3> <p><FormattedMessage id='follow_recommendations.lead' defaultMessage="Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!" /></p> </div> @@ -102,6 +105,10 @@ class FollowRecommendations extends ImmutablePureComponent { </React.Fragment> )} </div> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.js b/app/javascript/flavours/glitch/features/follow_requests/index.js index 36a57d1d6..7b35e3ec9 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.js +++ b/app/javascript/flavours/glitch/features/follow_requests/index.js @@ -11,7 +11,8 @@ import { fetchFollowRequests, expandFollowRequests } from 'flavours/glitch/actio import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, @@ -88,6 +89,10 @@ class FollowRequests extends ImmutablePureComponent { <AccountAuthorizeContainer key={id} id={id} />, )} </ScrollableList> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index 27a63b3fd..7122c1905 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -21,9 +21,10 @@ import ScrollableList from 'flavours/glitch/components/scrollable_list'; import TimelineHint from 'flavours/glitch/components/timeline_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import { getAccountHidden } from 'flavours/glitch/selectors'; +import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; const mapStateToProps = (state, { params: { acct, id } }) => { - const accountId = id || state.getIn(['accounts_map', acct]); + const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); if (!accountId) { return { diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index aa187bf95..4ad670105 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -21,9 +21,10 @@ import ScrollableList from 'flavours/glitch/components/scrollable_list'; import TimelineHint from 'flavours/glitch/components/timeline_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import { getAccountHidden } from 'flavours/glitch/selectors'; +import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; const mapStateToProps = (state, { params: { acct, id } }) => { - const accountId = id || state.getIn(['accounts_map', acct]); + const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]); if (!accountId) { return { diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js index f7097e2ec..93f3c9428 100644 --- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js +++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js @@ -6,16 +6,16 @@ import PropTypes from 'prop-types'; 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, disableSwiping } from 'flavours/glitch/util/initial_state'; +import { autoPlayGif, reduceMotion, disableSwiping } from 'flavours/glitch/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 { mascot } from 'flavours/glitch/initial_state'; +import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; import classNames from 'classnames'; -import EmojiPickerDropdown from 'flavours/glitch/features/emoji_picker'; +import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; import AnimatedNumber from 'flavours/glitch/components/animated_number'; import TransitionMotion from 'react-motion/lib/TransitionMotion'; import spring from 'react-motion/lib/spring'; -import { assetHost } from 'flavours/glitch/util/config'; +import { assetHost } from 'flavours/glitch/utils/config'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js index 68568d169..d88dbbaf4 100644 --- a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js +++ b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js @@ -1,13 +1,13 @@ import { connect } from 'react-redux'; -import { fetchTrends } from 'flavours/glitch/actions/trends'; +import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends'; import Trends from '../components/trends'; const mapStateToProps = state => ({ - trends: state.getIn(['trends', 'items']), + trends: state.getIn(['trends', 'tags', 'items']), }); const mapDispatchToProps = dispatch => ({ - fetchTrends: () => dispatch(fetchTrends()), + fetchTrends: () => dispatch(fetchTrendingHashtags()), }); export default connect(mapStateToProps, mapDispatchToProps)(Trends); diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js index 56750fd88..f9d79013b 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ b/app/javascript/flavours/glitch/features/getting_started/index.js @@ -8,15 +8,16 @@ import { openModal } from 'flavours/glitch/actions/modal'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me, profile_directory, showTrends } from 'flavours/glitch/util/initial_state'; +import { me, showTrends } from 'flavours/glitch/initial_state'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { fetchLists } from 'flavours/glitch/actions/lists'; -import { preferencesLink } from 'flavours/glitch/util/backend_links'; +import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import NavigationBar from '../compose/components/navigation_bar'; import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; import TrendsContainer from './containers/trends_container'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -26,6 +27,7 @@ const messages = defineMessages({ navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' }, direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, @@ -37,7 +39,6 @@ const messages = defineMessages({ lists_subheading: { id: 'column_subheading.lists', defaultMessage: 'Lists' }, misc: { id: 'navigation_bar.misc', defaultMessage: 'Misc' }, menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - profile_directory: { id: 'getting_started.directory', defaultMessage: 'Profile directory' }, }); const makeMapStateToProps = () => { @@ -84,11 +85,12 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object, }; static propTypes = { intl: PropTypes.object.isRequired, - myAccount: ImmutablePropTypes.map.isRequired, + myAccount: ImmutablePropTypes.map, columns: ImmutablePropTypes.list, multiColumn: PropTypes.bool, fetchFollowRequests: PropTypes.func.isRequired, @@ -104,10 +106,10 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); } componentDidMount () { - const { fetchFollowRequests, multiColumn } = this.props; + const { fetchFollowRequests } = this.props; + const { signedIn } = this.context.identity; - if (!multiColumn && window.innerWidth >= NAVIGATION_PANEL_BREAKPOINT) { - this.context.router.history.replace('/home'); + if (!signedIn) { return; } @@ -116,73 +118,85 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); render () { const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; + const { signedIn } = this.context.identity; const navItems = []; let listItems = []; if (multiColumn) { - if (!columns.find(item => item.get('id') === 'HOME')) { - navItems.push(<ColumnLink key='0' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />); + if (signedIn && !columns.find(item => item.get('id') === 'HOME')) { + navItems.push(<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />); } if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) { - navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />); + navItems.push(<ColumnLink key='notifications' icon='bell' text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />); } if (!columns.find(item => item.get('id') === 'COMMUNITY')) { - navItems.push(<ColumnLink key='2' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />); + navItems.push(<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/public/local' />); } if (!columns.find(item => item.get('id') === 'PUBLIC')) { - navItems.push(<ColumnLink key='3' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />); + navItems.push(<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/public' />); } } - if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { - navItems.push(<ColumnLink key='4' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />); + if (showTrends) { + navItems.push(<ColumnLink key='explore' icon='hashtag' text={intl.formatMessage(messages.explore)} to='/explore' />); } - if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) { - navItems.push(<ColumnLink key='5' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />); - } + if (signedIn) { + if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { + navItems.push(<ColumnLink key='conversations' icon='envelope' text={intl.formatMessage(messages.direct)} to='/conversations' />); + } - if (myAccount.get('locked') || unreadFollowRequests > 0) { - navItems.push(<ColumnLink key='6' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); - } + if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) { + navItems.push(<ColumnLink key='bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />); + } - if (profile_directory) { - navItems.push(<ColumnLink key='7' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />); - } + if (myAccount.get('locked') || unreadFollowRequests > 0) { + navItems.push(<ColumnLink key='follow_requests' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); + } - navItems.push(<ColumnLink key='8' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />); + navItems.push(<ColumnLink key='getting_started' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />); - listItems = listItems.concat([ - <div key='9'> - <ColumnLink key='10' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' /> - {lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list => - <ColumnLink key={(11 + Number(list.get('id'))).toString()} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} /> - )} - </div>, - ]); + listItems = listItems.concat([ + <div key='9'> + <ColumnLink key='lists' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' /> + {lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list => + <ColumnLink key={`list-${list.get('id')}`} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} /> + )} + </div>, + ]); + } 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'> <div className='getting-started__wrapper'> - {!multiColumn && <NavigationBar account={myAccount} />} + {!multiColumn && signedIn && <NavigationBar account={myAccount} />} {multiColumn && <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />} {navItems} - <ColumnSubheading text={intl.formatMessage(messages.lists_subheading)} /> - {listItems} - <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} /> - { preferencesLink !== undefined && <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href={preferencesLink} /> } - <ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} /> + {signedIn && ( + <React.Fragment> + <ColumnSubheading text={intl.formatMessage(messages.lists_subheading)} /> + {listItems} + <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} /> + { preferencesLink !== undefined && <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href={preferencesLink} /> } + <ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={openSettings} /> + </React.Fragment> + )} </div> <LinkFooter /> </div> {multiColumn && showTrends && <TrendsContainer />} + + <Helmet> + <title>{intl.formatMessage(messages.menu)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js index 1cf527573..004856b04 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/containers/column_settings_container.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import ColumnSettings from '../components/column_settings'; import { changeColumnParams } from 'flavours/glitch/actions/columns'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; const mapStateToProps = (state, { columnId }) => { const columns = state.getIn(['settings', 'columns']); diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js index 87a52b269..4428fdecf 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js @@ -14,6 +14,7 @@ import { isEqual } from 'lodash'; import { fetchHashtag, followHashtag, unfollowHashtag } from 'flavours/glitch/actions/tags'; import Icon from 'flavours/glitch/components/icon'; import classNames from 'classnames'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' }, @@ -31,6 +32,10 @@ class HashtagTimeline extends React.PureComponent { disconnects = []; + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { params: PropTypes.object.isRequired, columnId: PropTypes.string, @@ -90,6 +95,12 @@ class HashtagTimeline extends React.PureComponent { } _subscribe (dispatch, id, tags = {}, local) { + const { signedIn } = this.context.identity; + + if (!signedIn) { + return; + } + let any = (tags.any || []).map(tag => tag.value); let all = (tags.all || []).map(tag => tag.value); let none = (tags.none || []).map(tag => tag.value); @@ -158,6 +169,11 @@ class HashtagTimeline extends React.PureComponent { handleFollow = () => { const { dispatch, params, tag } = this.props; const { id } = params; + const { signedIn } = this.context.identity; + + if (!signedIn) { + return; + } if (tag.get('following')) { dispatch(unfollowHashtag(id)); @@ -170,6 +186,7 @@ class HashtagTimeline extends React.PureComponent { const { hasUnread, columnId, multiColumn, tag, intl } = this.props; const { id, local } = this.props.params; const pinned = !!columnId; + const { signedIn } = this.context.identity; let followButton; @@ -177,7 +194,7 @@ class HashtagTimeline extends React.PureComponent { const following = tag.get('following'); followButton = ( - <button className={classNames('column-header__button')} onClick={this.handleFollow} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-pressed={following ? 'true' : 'false'}> + <button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}> <Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' /> </button> ); @@ -208,6 +225,11 @@ class HashtagTimeline extends React.PureComponent { emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} bindToDocument={!multiColumn} /> + + <Helmet> + <title>#{id}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js index 19551d6b8..5ed108ad2 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.js +++ b/app/javascript/flavours/glitch/features/home_timeline/index.js @@ -13,6 +13,8 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/act import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container'; import classNames from 'classnames'; import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; +import NotSignedInIndicator from 'flavours/glitch/components/not_signed_in_indicator'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ title: { id: 'column.home', defaultMessage: 'Home' }, @@ -26,12 +28,17 @@ const mapStateToProps = state => ({ hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(), unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')), showAnnouncements: state.getIn(['announcements', 'show']), + regex: state.getIn(['settings', 'home', 'regex', 'body']), }); export default @connect(mapStateToProps) @injectIntl class HomeTimeline extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -42,6 +49,7 @@ class HomeTimeline extends React.PureComponent { hasAnnouncements: PropTypes.bool, unreadAnnouncements: PropTypes.number, showAnnouncements: PropTypes.bool, + regex: PropTypes.string, }; handlePin = () => { @@ -113,6 +121,7 @@ class HomeTimeline extends React.PureComponent { render () { const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const pinned = !!columnId; + const { signedIn } = this.context.identity; let announcementsButton = null; @@ -122,7 +131,6 @@ class HomeTimeline extends React.PureComponent { className={classNames('column-header__button', { 'active': showAnnouncements })} title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)} aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)} - aria-pressed={showAnnouncements ? 'true' : 'false'} onClick={this.handleToggleAnnouncementsClick} > <IconWithBadge id='bullhorn' count={unreadAnnouncements} /> @@ -147,14 +155,22 @@ class HomeTimeline extends React.PureComponent { <ColumnSettingsContainer /> </ColumnHeader> - <StatusListContainer - trackScroll={!pinned} - scrollKey={`home_timeline-${columnId}`} - onLoadMore={this.handleLoadMore} - timelineId='home' - emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />} - bindToDocument={!multiColumn} - /> + {signedIn ? ( + <StatusListContainer + trackScroll={!pinned} + scrollKey={`home_timeline-${columnId}`} + onLoadMore={this.handleLoadMore} + timelineId='home' + emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />} + bindToDocument={!multiColumn} + regex={this.props.regex} + /> + ) : <NotSignedInIndicator />} + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/interaction_modal/index.js b/app/javascript/flavours/glitch/features/interaction_modal/index.js new file mode 100644 index 000000000..b71c041c9 --- /dev/null +++ b/app/javascript/flavours/glitch/features/interaction_modal/index.js @@ -0,0 +1,161 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { registrationsOpen } from 'flavours/glitch/initial_state'; +import { connect } from 'react-redux'; +import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; +import { openModal, closeModal } from 'flavours/glitch/actions/modal'; + +const mapStateToProps = (state, { accountId }) => ({ + displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']), +}); + +const mapDispatchToProps = (dispatch) => ({ + onSignupClick() { + dispatch(closeModal()); + dispatch(openModal('CLOSED_REGISTRATIONS')); + }, +}); + +class Copypaste extends React.PureComponent { + + static propTypes = { + value: PropTypes.string, + }; + + state = { + copied: false, + }; + + setRef = c => { + this.input = c; + } + + handleInputClick = () => { + this.setState({ copied: false }); + this.input.focus(); + this.input.select(); + this.input.setSelectionRange(0, this.input.value.length); + } + + handleButtonClick = () => { + const { value } = this.props; + navigator.clipboard.writeText(value); + this.input.blur(); + this.setState({ copied: true }); + this.timeout = setTimeout(() => this.setState({ copied: false }), 700); + } + + componentWillUnmount () { + if (this.timeout) clearTimeout(this.timeout); + } + + render () { + const { value } = this.props; + const { copied } = this.state; + + return ( + <div className={classNames('copypaste', { copied })}> + <input + type='text' + ref={this.setRef} + value={value} + readOnly + onClick={this.handleInputClick} + /> + + <button className='button' onClick={this.handleButtonClick}> + {copied ? <FormattedMessage id='copypaste.copied' defaultMessage='Copied' /> : <FormattedMessage id='copypaste.copy' defaultMessage='Copy' />} + </button> + </div> + ); + } + +} + +export default @connect(mapStateToProps, mapDispatchToProps) +class InteractionModal extends React.PureComponent { + + static propTypes = { + displayNameHtml: PropTypes.string, + url: PropTypes.string, + type: PropTypes.oneOf(['reply', 'reblog', 'favourite', 'follow']), + onSignupClick: PropTypes.func.isRequired, + }; + + handleSignupClick = () => { + this.props.onSignupClick(); + } + + render () { + const { url, type, displayNameHtml } = this.props; + + const name = <bdi dangerouslySetInnerHTML={{ __html: displayNameHtml }} />; + + let title, actionDescription, icon; + + switch(type) { + case 'reply': + icon = <Icon id='reply' />; + title = <FormattedMessage id='interaction_modal.title.reply' defaultMessage="Reply to {name}'s post" values={{ name }} />; + actionDescription = <FormattedMessage id='interaction_modal.description.reply' defaultMessage='With an account on Mastodon, you can respond to this post.' />; + break; + case 'reblog': + icon = <Icon id='retweet' />; + title = <FormattedMessage id='interaction_modal.title.reblog' defaultMessage="Boost {name}'s post" values={{ name }} />; + actionDescription = <FormattedMessage id='interaction_modal.description.reblog' defaultMessage='With an account on Mastodon, you can boost this post to share it with your own followers.' />; + break; + case 'favourite': + icon = <Icon id='star' />; + title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favourite {name}'s post" values={{ name }} />; + actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.' />; + break; + case 'follow': + icon = <Icon id='user-plus' />; + title = <FormattedMessage id='interaction_modal.title.follow' defaultMessage='Follow {name}' values={{ name }} />; + actionDescription = <FormattedMessage id='interaction_modal.description.follow' defaultMessage='With an account on Mastodon, you can follow {name} to receive their posts in your home feed.' values={{ name }} />; + break; + } + + let signupButton; + + if (registrationsOpen) { + signupButton = ( + <a href='/auth/sign_up' className='button button--block button-tertiary'> + <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> + </a> + ); + } else { + signupButton = ( + <button className='button button--block button-tertiary' onClick={this.handleSignupClick}> + <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> + </button> + ); + } + + return ( + <div className='modal-root__modal interaction-modal'> + <div className='interaction-modal__lead'> + <h3><span className='interaction-modal__icon'>{icon}</span> {title}</h3> + <p>{actionDescription} <FormattedMessage id='interaction_modal.preamble' defaultMessage="Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one." /></p> + </div> + + <div className='interaction-modal__choices'> + <div className='interaction-modal__choices__choice'> + <h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3> + <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> + {signupButton} + </div> + + <div className='interaction-modal__choices__choice'> + <h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3> + <p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.' /></p> + <Copypaste value={url} /> + </div> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js index 481f76763..2bc0116d4 100644 --- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js +++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.js @@ -1,10 +1,11 @@ import React from 'react'; -import Column from 'flavours/glitch/features/ui/components/column'; -import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; +import Column from 'flavours/glitch/components/column'; import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import ColumnHeader from 'flavours/glitch/components/column_header'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, @@ -28,8 +29,13 @@ class KeyboardShortcuts extends ImmutablePureComponent { const { intl, collapseEnabled, multiColumn } = this.props; return ( - <Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}> - <ColumnBackButtonSlim /> + <Column> + <ColumnHeader + title={intl.formatMessage(messages.heading)} + icon='question' + multiColumn={multiColumn} + /> + <div className='keyboard-shortcuts scrollable optionally-scrollable'> <table> <thead> @@ -132,6 +138,10 @@ class KeyboardShortcuts extends ImmutablePureComponent { </tbody> </table> </div> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/list_editor/index.js b/app/javascript/flavours/glitch/features/list_editor/index.js index 75b0de3d3..c2ca07053 100644 --- a/app/javascript/flavours/glitch/features/list_editor/index.js +++ b/app/javascript/flavours/glitch/features/list_editor/index.js @@ -8,7 +8,7 @@ import { setupListEditor, clearListSuggestions, resetListEditor } from 'flavours import AccountContainer from './containers/account_container'; import SearchContainer from './containers/search_container'; import EditListForm from './components/edit_list_form'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js index 9e231aab7..a94c05c56 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.js +++ b/app/javascript/flavours/glitch/features/list_timeline/index.js @@ -1,20 +1,22 @@ -import React from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import React from 'react'; +import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; -import Column from 'flavours/glitch/components/column'; -import ColumnHeader from 'flavours/glitch/components/column_header'; -import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; -import { connectListStream } from 'flavours/glitch/actions/streaming'; -import { expandListTimeline } from 'flavours/glitch/actions/timelines'; +import { connect } from 'react-redux'; +import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import { fetchList, deleteList, updateList } from 'flavours/glitch/actions/lists'; import { openModal } from 'flavours/glitch/actions/modal'; -import MissingIndicator from 'flavours/glitch/components/missing_indicator'; -import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import { connectListStream } from 'flavours/glitch/actions/streaming'; +import { expandListTimeline } from 'flavours/glitch/actions/timelines'; +import Column from 'flavours/glitch/components/column'; +import ColumnBackButton from 'flavours/glitch/components/column_back_button'; +import ColumnHeader from 'flavours/glitch/components/column_header'; import Icon from 'flavours/glitch/components/icon'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import MissingIndicator from 'flavours/glitch/components/missing_indicator'; import RadioButton from 'flavours/glitch/components/radio_button'; +import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, @@ -165,7 +167,7 @@ class ListTimeline extends React.PureComponent { } return ( - <Column ref={this.setRef} label={title}> + <Column bindToDocument={!multiColumn} ref={this.setRef} label={title}> <ColumnHeader icon='list-ul' active={hasUnread} @@ -175,7 +177,6 @@ class ListTimeline extends React.PureComponent { onClick={this.handleHeaderClick} pinned={pinned} multiColumn={multiColumn} - bindToDocument={!multiColumn} > <div className='column-settings__row column-header__links'> <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}> @@ -211,6 +212,11 @@ class ListTimeline extends React.PureComponent { emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />} bindToDocument={!multiColumn} /> + + <Helmet> + <title>{title}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/lists/index.js b/app/javascript/flavours/glitch/features/lists/index.js index b92389d82..8773be5e6 100644 --- a/app/javascript/flavours/glitch/features/lists/index.js +++ b/app/javascript/flavours/glitch/features/lists/index.js @@ -1,18 +1,19 @@ -import React from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import React from 'react'; +import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { fetchLists } from 'flavours/glitch/actions/lists'; +import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; +import ScrollableList from 'flavours/glitch/components/scrollable_list'; import Column from 'flavours/glitch/features/ui/components/column'; -import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; -import { fetchLists } from 'flavours/glitch/actions/lists'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; import NewListForm from './components/new_list_form'; -import { createSelector } from 'reselect'; -import ScrollableList from 'flavours/glitch/components/scrollable_list'; const messages = defineMessages({ heading: { id: 'column.lists', defaultMessage: 'Lists' }, @@ -76,6 +77,11 @@ class Lists extends ImmutablePureComponent { <ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />, )} </ScrollableList> + + <Helmet> + <title>{intl.formatMessage(messages.heading)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js index ab3a554bf..e618a981e 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js @@ -5,7 +5,7 @@ import { injectIntl, defineMessages } from 'react-intl'; // Our imports import LocalSettingsNavigationItem from './item'; -import { preferencesLink } from 'flavours/glitch/util/backend_links'; +import { preferencesLink } from 'flavours/glitch/utils/backend_links'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -61,34 +61,27 @@ class LocalSettingsNavigation extends React.PureComponent { active={index === 3} index={3} onNavigate={onNavigate} - icon='filter' - title={intl.formatMessage(messages.filters)} - /> - <LocalSettingsNavigationItem - active={index === 4} - index={4} - onNavigate={onNavigate} icon='angle-double-up' title={intl.formatMessage(messages.collapsed)} /> <LocalSettingsNavigationItem - active={index === 5} - index={5} + active={index === 4} + index={4} onNavigate={onNavigate} icon='image' title={intl.formatMessage(messages.media)} /> <LocalSettingsNavigationItem - active={index === 6} + active={index === 5} href={ preferencesLink } - index={6} + index={5} icon='cog' title={intl.formatMessage(messages.preferences)} /> <LocalSettingsNavigationItem - active={index === 7} + active={index === 6} className='close' - index={7} + index={6} onNavigate={onClose} icon='times' title={intl.formatMessage(messages.close)} diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js index 333b73b45..d01eec811 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/index.js +++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js @@ -5,8 +5,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; // Our imports -import { expandSpoilers, disableSwiping } from 'flavours/glitch/util/initial_state'; -import { preferenceLink } from 'flavours/glitch/util/backend_links'; +import { expandSpoilers, disableSwiping } from 'flavours/glitch/initial_state'; +import { preferenceLink } from 'flavours/glitch/utils/backend_links'; import LocalSettingsPageItem from './item'; import DeprecatedLocalSettingsPageItem from './deprecated_item'; @@ -20,7 +20,7 @@ const messages = defineMessages({ layout_mobile: { id: 'layout.single', defaultMessage: 'Mobile' }, layout_mobile_hint: { id: 'layout.hint.single', defaultMessage: 'Use single-column layout regardless of the โEnable advanced web interfaceโ setting or screen size.' }, side_arm_none: { id: 'settings.side_arm.none', defaultMessage: 'None' }, - side_arm_keep: { id: 'settings.side_arm_reply_mode.keep', defaultMessage: 'Keep secondary toot button to set privacy' }, + side_arm_keep: { id: 'settings.side_arm_reply_mode.keep', defaultMessage: 'Keep its set privacy' }, side_arm_copy: { id: 'settings.side_arm_reply_mode.copy', defaultMessage: 'Copy privacy setting of the toot being replied to' }, side_arm_restrict: { id: 'settings.side_arm_reply_mode.restrict', defaultMessage: 'Restrict privacy setting to that of the toot being replied to' }, regexp: { id: 'settings.content_warnings.regexp', defaultMessage: 'Regular expression' }, @@ -181,36 +181,6 @@ class LocalSettingsPage extends React.PureComponent { <FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' /> <span className='hint'><FormattedMessage id='settings.wide_view_hint' defaultMessage='Stretches columns to better fill the available space.' /></span> </LocalSettingsPageItem> - <LocalSettingsPageItem - settings={settings} - item={['navbar_under']} - id='mastodon-settings--navbar_under' - onChange={onChange} - > - <FormattedMessage id='settings.navbar_under' defaultMessage='Navbar at the bottom (Mobile only)' /> - </LocalSettingsPageItem> - <DeprecatedLocalSettingsPageItem - id='mastodon-settings--swipe_to_change_columns' - value={!disableSwiping} - > - <FormattedMessage id='settings.swipe_to_change_columns' defaultMessage='Allow swiping to change columns (Mobile only)' /> - <span className='hint'> - <FormattedMessage - id='settings.deprecated_setting' - defaultMessage="This setting is now controlled from Mastodon's {settings_page_link}" - values={{ - settings_page_link: ( - <a href={preferenceLink('user_setting_disable_swiping')}> - <FormattedMessage - id='settings.shared_settings_link' - defaultMessage='user preferences' - /> - </a> - ) - }} - /> - </span> - </DeprecatedLocalSettingsPageItem> </section> </div> ), @@ -292,7 +262,7 @@ class LocalSettingsPage extends React.PureComponent { ]} onChange={onChange} > - <FormattedMessage id='settings.side_arm_reply_mode' defaultMessage='When replying to a toot:' /> + <FormattedMessage id='settings.side_arm_reply_mode' defaultMessage='When replying to a toot, the secondary toot button should:' /> </LocalSettingsPageItem> </div> ), diff --git a/app/javascript/flavours/glitch/features/mutes/index.js b/app/javascript/flavours/glitch/features/mutes/index.js index 764cbef1a..8da106e47 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.js +++ b/app/javascript/flavours/glitch/features/mutes/index.js @@ -11,6 +11,7 @@ import AccountContainer from 'flavours/glitch/containers/account_container'; import { fetchMutes, expandMutes } from 'flavours/glitch/actions/mutes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, @@ -72,6 +73,10 @@ class Mutes extends ImmutablePureComponent { <AccountContainer key={id} id={id} defaultAction='mute' />, )} </ScrollableList> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js b/app/javascript/flavours/glitch/features/notifications/components/admin_report.js index 80beeb9da..4662bd953 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/admin_report.js +++ b/app/javascript/flavours/glitch/features/notifications/components/admin_report.js @@ -69,6 +69,10 @@ export default class AdminReport extends ImmutablePureComponent { render () { const { intl, account, notification, unread, report } = this.props; + if (!report) { + return null; + } + // Links to the display name. const displayName = account.get('display_name_html') || account.get('username'); const link = ( diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js index 42ab9de35..64fd98bd9 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js @@ -22,7 +22,7 @@ export default class ColumnSettings extends React.PureComponent { onRequestNotificationPermission: PropTypes.func, alertsEnabled: PropTypes.bool, browserSupport: PropTypes.bool, - browserPermission: PropTypes.bool, + browserPermission: PropTypes.string, }; onPushChange = (path, checked) => { @@ -171,7 +171,7 @@ export default class ColumnSettings extends React.PureComponent { </div> </div> - {(this.context.identity.permissions & PERMISSION_MANAGE_USERS === PERMISSION_MANAGE_USERS) && ( + {((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && ( <div role='group' aria-labelledby='notifications-admin-sign-up'> <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></span> @@ -184,7 +184,7 @@ export default class ColumnSettings extends React.PureComponent { </div> )} - {(this.context.identity.permissions & PERMISSION_MANAGE_REPORTS === PERMISSION_MANAGE_REPORTS) && ( + {((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && ( <div role='group' aria-labelledby='notifications-admin-report'> <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></span> diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index 075e729b1..fc42a4de4 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -26,8 +26,10 @@ import { debounce } from 'lodash'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; import LoadGap from 'flavours/glitch/components/load_gap'; import Icon from 'flavours/glitch/components/icon'; -import compareId from 'flavours/glitch/util/compare_id'; +import compareId from 'flavours/glitch/compare_id'; import NotificationsPermissionBanner from './components/notifications_permission_banner'; +import NotSignedInIndicator from 'flavours/glitch/components/not_signed_in_indicator'; +import { Helmet } from 'react-helmet'; import NotificationPurgeButtonsContainer from 'flavours/glitch/containers/notification_purge_buttons_container'; @@ -62,7 +64,7 @@ const mapStateToProps = state => ({ showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']), notifications: getNotifications(state), localSettings: state.get('local_settings'), - isLoading: state.getIn(['notifications', 'isLoading'], true), + isLoading: state.getIn(['notifications', 'isLoading'], 0) > 0, isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0, hasMore: state.getIn(['notifications', 'hasMore']), numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, @@ -94,6 +96,10 @@ export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class Notifications extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { columnId: PropTypes.string, notifications: ImmutablePropTypes.list.isRequired, @@ -224,10 +230,11 @@ class Notifications extends React.PureComponent { const { animatingNCD } = this.state; const pinned = !!columnId; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />; + const { signedIn } = this.context.identity; let scrollableContent = null; - const filterBarContainer = showFilterBar + const filterBarContainer = (signedIn && showFilterBar) ? (<FilterBarContainer />) : null; @@ -257,39 +264,46 @@ class Notifications extends React.PureComponent { this.scrollableContent = scrollableContent; - const scrollContainer = ( - <ScrollableList - scrollKey={`notifications-${columnId}`} - trackScroll={!pinned} - isLoading={isLoading} - showLoading={isLoading && notifications.size === 0} - hasMore={hasMore} - numPending={numPending} - prepend={needsNotificationPermission && <NotificationsPermissionBanner />} - alwaysPrepend - emptyMessage={emptyMessage} - onLoadMore={this.handleLoadOlder} - onLoadPending={this.handleLoadPending} - onScrollToTop={this.handleScrollToTop} - onScroll={this.handleScroll} - bindToDocument={!multiColumn} - > - {scrollableContent} - </ScrollableList> - ); + let scrollContainer; + + if (signedIn) { + scrollContainer = ( + <ScrollableList + scrollKey={`notifications-${columnId}`} + trackScroll={!pinned} + isLoading={isLoading} + showLoading={isLoading && notifications.size === 0} + hasMore={hasMore} + numPending={numPending} + prepend={needsNotificationPermission && <NotificationsPermissionBanner />} + alwaysPrepend + emptyMessage={emptyMessage} + onLoadMore={this.handleLoadOlder} + onLoadPending={this.handleLoadPending} + onScrollToTop={this.handleScrollToTop} + onScroll={this.handleScroll} + bindToDocument={!multiColumn} + > + {scrollableContent} + </ScrollableList> + ); + } else { + scrollContainer = <NotSignedInIndicator />; + } const extraButtons = []; if (canMarkAsRead) { extraButtons.push( <button + key='mark-as-read' aria-label={intl.formatMessage(messages.markAsRead)} title={intl.formatMessage(messages.markAsRead)} onClick={this.handleMarkAsRead} className='column-header__button' > <Icon id='check' /> - </button> + </button>, ); } @@ -306,13 +320,14 @@ class Notifications extends React.PureComponent { extraButtons.push( <button + key='notif-cleaning' aria-label={msgEnterNotifCleaning} title={msgEnterNotifCleaning} onClick={this.onEnterCleaningMode} className={notifCleaningButtonClassName} > <Icon id='eraser' /> - </button> + </button>, ); const notifCleaningDrawer = ( @@ -323,6 +338,12 @@ class Notifications extends React.PureComponent { </div> ); + const extraButton = ( + <> + {extraButtons} + </> + ); + return ( <Column bindToDocument={!multiColumn} @@ -341,13 +362,19 @@ class Notifications extends React.PureComponent { pinned={pinned} multiColumn={multiColumn} localSettings={this.props.localSettings} - extraButton={extraButtons} + extraButton={extraButton} appendContent={notifCleaningDrawer} > <ColumnSettingsContainer /> </ColumnHeader> + {filterBarContainer} {scrollContainer} + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js index 0408105ae..f05a763e0 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js @@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import IconButton from 'flavours/glitch/components/icon_button'; import classNames from 'classnames'; -import { me, boostModal } from 'flavours/glitch/util/initial_state'; +import { me, boostModal } from 'flavours/glitch/initial_state'; import { defineMessages, injectIntl } from 'react-intl'; import { replyCompose } from 'flavours/glitch/actions/compose'; import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions'; @@ -44,6 +44,7 @@ class Footer extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { @@ -69,26 +70,44 @@ class Footer extends ImmutablePureComponent { }; handleReplyClick = () => { - const { dispatch, askReplyConfirmation, intl } = this.props; - - if (askReplyConfirmation) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onConfirm: this._performReply, - })); + const { dispatch, askReplyConfirmation, status, intl } = this.props; + const { signedIn } = this.context.identity; + + if (signedIn) { + if (askReplyConfirmation) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onConfirm: this._performReply, + })); + } else { + this._performReply(); + } } else { - this._performReply(); + dispatch(openModal('INTERACTION', { + type: 'reply', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); } }; handleFavouriteClick = () => { const { dispatch, status } = this.props; - - if (status.get('favourited')) { - dispatch(unfavourite(status)); + const { signedIn } = this.context.identity; + + if (signedIn) { + if (status.get('favourited')) { + dispatch(unfavourite(status)); + } else { + dispatch(favourite(status)); + } } else { - dispatch(favourite(status)); + dispatch(openModal('INTERACTION', { + type: 'favourite', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); } }; @@ -99,13 +118,22 @@ class Footer extends ImmutablePureComponent { handleReblogClick = e => { const { dispatch, status } = this.props; - - if (status.get('reblogged')) { - dispatch(unreblog(status)); - } else if ((e && e.shiftKey) || !boostModal) { - this._performReblog(); + const { signedIn } = this.context.identity; + + if (signedIn) { + if (status.get('reblogged')) { + dispatch(unreblog(status)); + } else if ((e && e.shiftKey) || !boostModal) { + this._performReblog(); + } else { + dispatch(initBoostModal({ status, onReblog: this._performReblog })); + } } else { - dispatch(initBoostModal({ status, onReblog: this._performReblog })); + dispatch(openModal('INTERACTION', { + type: 'reblog', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); } }; @@ -179,8 +207,8 @@ class Footer extends ImmutablePureComponent { return ( <div className='picture-in-picture__footer'> {replyButton} - <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} /> - <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} /> + <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} /> + <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} /> {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={status.get('url')} />} </div> ); diff --git a/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js b/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js index 5f03c7e93..43ae0ec2f 100644 --- a/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js +++ b/app/javascript/flavours/glitch/features/pinned_accounts_editor/index.js @@ -7,7 +7,7 @@ import { injectIntl, FormattedMessage } from 'react-intl'; import { fetchPinnedAccounts, clearPinnedAccountsSuggestions, resetPinnedAccountsEditor } from 'flavours/glitch/actions/accounts'; import AccountContainer from './containers/account_container'; import SearchContainer from './containers/search_container'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from 'flavours/glitch/features/ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; const mapStateToProps = state => ({ diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.js b/app/javascript/flavours/glitch/features/pinned_statuses/index.js index 518d0294b..eeeab46ab 100644 --- a/app/javascript/flavours/glitch/features/pinned_statuses/index.js +++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.js @@ -8,6 +8,7 @@ import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_ import StatusList from 'flavours/glitch/components/status_list'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.pins', defaultMessage: 'Pinned post' }, @@ -54,6 +55,9 @@ class PinnedStatuses extends ImmutablePureComponent { hasMore={hasMore} bindToDocument={!multiColumn} /> + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/privacy_policy/index.js b/app/javascript/flavours/glitch/features/privacy_policy/index.js new file mode 100644 index 000000000..4618d9e32 --- /dev/null +++ b/app/javascript/flavours/glitch/features/privacy_policy/index.js @@ -0,0 +1,61 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from 'react-helmet'; +import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; +import Column from 'flavours/glitch/components/column'; +import api from 'flavours/glitch/api'; +import Skeleton from 'flavours/glitch/components/skeleton'; + +const messages = defineMessages({ + title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, +}); + +export default @injectIntl +class PrivacyPolicy extends React.PureComponent { + + static propTypes = { + intl: PropTypes.object, + multiColumn: PropTypes.bool, + }; + + state = { + content: null, + lastUpdated: null, + isLoading: true, + }; + + componentDidMount () { + api().get('/api/v1/instance/privacy_policy').then(({ data }) => { + this.setState({ content: data.content, lastUpdated: data.updated_at, isLoading: false }); + }).catch(() => { + this.setState({ isLoading: false }); + }); + } + + render () { + const { intl, multiColumn } = this.props; + const { isLoading, content, lastUpdated } = this.state; + + return ( + <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> + <div className='scrollable privacy-policy'> + <div className='column-title'> + <h3><FormattedMessage id='privacy_policy.title' defaultMessage='Privacy Policy' /></h3> + <p><FormattedMessage id='privacy_policy.last_updated' defaultMessage='Last updated {date}' values={{ date: isLoading ? <Skeleton width='10ch' /> : <FormattedDate value={lastUpdated} year='numeric' month='short' day='2-digit' /> }} /></p> + </div> + + <div + className='privacy-policy__body prose' + dangerouslySetInnerHTML={{ __html: content }} + /> + </div> + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='all' /> + </Helmet> + </Column> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js index e92681065..cfe821cfc 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js +++ b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.js @@ -1,8 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl, FormattedMessage } from 'react-intl'; -import SettingToggle from '../../notifications/components/setting_toggle'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import SettingText from 'flavours/glitch/components/setting_text'; +import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle'; + +const messages = defineMessages({ + filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, +}); export default @injectIntl class ColumnSettings extends React.PureComponent { @@ -15,7 +20,7 @@ class ColumnSettings extends React.PureComponent { }; render () { - const { settings, onChange } = this.props; + const { settings, onChange, intl } = this.props; return ( <div> @@ -24,6 +29,12 @@ class ColumnSettings extends React.PureComponent { <SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} /> {!settings.getIn(['other', 'onlyRemote']) && <SettingToggle settings={settings} settingPath={['other', 'allowLocalOnly']} onChange={onChange} label={<FormattedMessage id='community.column_settings.allow_local_only' defaultMessage='Show local-only toots' />} />} </div> + + <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span> + + <div className='column-settings__row'> + <SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} /> + </div> </div> ); } diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js index 848049965..a61a47de1 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.js +++ b/app/javascript/flavours/glitch/features/public_timeline/index.js @@ -9,6 +9,8 @@ import { expandPublicTimeline } from 'flavours/glitch/actions/timelines'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import ColumnSettingsContainer from './containers/column_settings_container'; import { connectPublicStream } from 'flavours/glitch/actions/streaming'; +import { Helmet } from 'react-helmet'; +import DismissableBanner from 'flavours/glitch/components/dismissable_banner'; const messages = defineMessages({ title: { id: 'column.public', defaultMessage: 'Federated timeline' }, @@ -21,6 +23,7 @@ const mapStateToProps = (state, { columnId }) => { const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']); const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']); const allowLocalOnly = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'allowLocalOnly']) : state.getIn(['settings', 'public', 'other', 'allowLocalOnly']); + const regex = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'regex', 'body']) : state.getIn(['settings', 'public', 'regex', 'body']); const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]); return { @@ -28,6 +31,7 @@ const mapStateToProps = (state, { columnId }) => { onlyMedia, onlyRemote, allowLocalOnly, + regex, }; }; @@ -41,6 +45,7 @@ class PublicTimeline extends React.PureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { @@ -52,6 +57,7 @@ class PublicTimeline extends React.PureComponent { onlyMedia: PropTypes.bool, onlyRemote: PropTypes.bool, allowLocalOnly: PropTypes.bool, + regex: PropTypes.string, }; handlePin = () => { @@ -75,18 +81,29 @@ class PublicTimeline extends React.PureComponent { componentDidMount () { const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; + const { signedIn } = this.context.identity; dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote, allowLocalOnly })); + if (signedIn) { + this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote, allowLocalOnly })); + } } componentDidUpdate (prevProps) { + const { signedIn } = this.context.identity; + if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote || prevProps.allowLocalOnly !== this.props.allowLocalOnly) { const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; - this.disconnect(); + if (this.disconnect) { + this.disconnect(); + } + dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly })); - this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote, allowLocalOnly })); + + if (signedIn) { + this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote, allowLocalOnly })); + } } } @@ -126,6 +143,10 @@ class PublicTimeline extends React.PureComponent { <ColumnSettingsContainer columnId={columnId} /> </ColumnHeader> + <DismissableBanner id='public_timeline'> + <FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' /> + </DismissableBanner> + <StatusListContainer timelineId={`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`} onLoadMore={this.handleLoadMore} @@ -133,7 +154,13 @@ class PublicTimeline extends React.PureComponent { scrollKey={`public_timeline-${columnId}`} emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />} bindToDocument={!multiColumn} + regex={this.props.regex} /> + + <Helmet> + <title>{intl.formatMessage(messages.title)}</title> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/reblogs/index.js b/app/javascript/flavours/glitch/features/reblogs/index.js index ed646c6ed..b097ff9d7 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.js +++ b/app/javascript/flavours/glitch/features/reblogs/index.js @@ -11,6 +11,7 @@ import ColumnHeader from 'flavours/glitch/components/column_header'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ heading: { id: 'column.reblogged_by', defaultMessage: 'Boosted by' }, @@ -92,6 +93,10 @@ class Reblogs extends ImmutablePureComponent { <AccountContainer key={id} id={id} withNote={false} />, )} </ScrollableList> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/report/category.js b/app/javascript/flavours/glitch/features/report/category.js index bea21b1b7..55c43577b 100644 --- a/app/javascript/flavours/glitch/features/report/category.js +++ b/app/javascript/flavours/glitch/features/report/category.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Button from 'flavours/glitch/components/button'; import Option from './components/option'; +import { List as ImmutableList } from 'immutable'; const messages = defineMessages({ dislike: { id: 'report.reasons.dislike', defaultMessage: 'I don\'t like it' }, @@ -20,7 +21,7 @@ const messages = defineMessages({ }); const mapStateToProps = state => ({ - rules: state.get('rules'), + rules: state.getIn(['server', 'server', 'rules'], ImmutableList()), }); export default @connect(mapStateToProps) diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.js b/app/javascript/flavours/glitch/features/report/components/status_check_box.js index 76bf0eb85..2231fc0ce 100644 --- a/app/javascript/flavours/glitch/features/report/components/status_check_box.js +++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.js @@ -7,6 +7,7 @@ import DisplayName from 'flavours/glitch/components/display_name'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import Option from './option'; import MediaAttachments from 'flavours/glitch/components/media_attachments'; +import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; export default class StatusCheckBox extends React.PureComponent { @@ -36,7 +37,7 @@ export default class StatusCheckBox extends React.PureComponent { <Avatar account={status.get('account')} size={46} /> </div> - <div><DisplayName account={status.get('account')} /> ยท <RelativeTimestamp timestamp={status.get('created_at')} /></div> + <div><DisplayName account={status.get('account')} /> ยท <VisibilityIcon visibility={status.get('visibility')} /><RelativeTimestamp timestamp={status.get('created_at')} /></div> </div> <StatusContent status={status} media={<MediaAttachments status={status} revealed={false} />} /> diff --git a/app/javascript/flavours/glitch/features/report/rules.js b/app/javascript/flavours/glitch/features/report/rules.js index 4772e04a2..efcdf1fcf 100644 --- a/app/javascript/flavours/glitch/features/report/rules.js +++ b/app/javascript/flavours/glitch/features/report/rules.js @@ -7,7 +7,7 @@ import Button from 'flavours/glitch/components/button'; import Option from './components/option'; const mapStateToProps = state => ({ - rules: state.get('rules'), + rules: state.getIn(['server', 'server', 'rules']), }); export default @connect(mapStateToProps) diff --git a/app/javascript/flavours/glitch/features/search/index.js b/app/javascript/flavours/glitch/features/search/index.js deleted file mode 100644 index b35c8ed49..000000000 --- a/app/javascript/flavours/glitch/features/search/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; -import SearchResultsContainer from 'flavours/glitch/features/compose/containers/search_results_container'; - -const Search = () => ( - <div className='column search-page'> - <SearchContainer /> - - <div className='drawer__pager'> - <div className='drawer__inner darker'> - <SearchResultsContainer /> - </div> - </div> - </div> -); - -export default Search; diff --git a/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js deleted file mode 100644 index 629f5c2ea..000000000 --- a/app/javascript/flavours/glitch/features/standalone/hashtag_timeline/index.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines'; -import Masonry from 'react-masonry-infinite'; -import { List as ImmutableList } from 'immutable'; -import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container'; -import { debounce } from 'lodash'; -import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; - -const mapStateToProps = (state, { hashtag }) => ({ - statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()), - isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false), - hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false), -}); - -export default @connect(mapStateToProps) -class HashtagTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - isLoading: PropTypes.bool.isRequired, - hasMore: PropTypes.bool.isRequired, - hashtag: PropTypes.string.isRequired, - local: PropTypes.bool.isRequired, - }; - - static defaultProps = { - local: false, - }; - - componentDidMount () { - const { dispatch, hashtag, local } = this.props; - - dispatch(expandHashtagTimeline(hashtag, { local })); - } - - handleLoadMore = () => { - const { dispatch, hashtag, local, statusIds } = this.props; - const maxId = statusIds.last(); - - if (maxId) { - dispatch(expandHashtagTimeline(hashtag, { maxId, local })); - } - } - - setRef = c => { - this.masonry = c; - } - - handleHeightChange = debounce(() => { - if (!this.masonry) { - return; - } - - this.masonry.forcePack(); - }, 50) - - render () { - const { statusIds, hasMore, isLoading } = this.props; - - const sizes = [ - { columns: 1, gutter: 0 }, - { mq: '415px', columns: 1, gutter: 10 }, - { mq: '640px', columns: 2, gutter: 10 }, - { mq: '960px', columns: 3, gutter: 10 }, - { mq: '1255px', columns: 3, gutter: 10 }, - ]; - - const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined; - - return ( - <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}> - {statusIds.map(statusId => ( - <div className='statuses-grid__item' key={statusId}> - <DetailedStatusContainer - id={statusId} - compact - measureHeight - onHeightChange={this.handleHeightChange} - /> - </div> - )).toArray()} - </Masonry> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js b/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js deleted file mode 100644 index 5f8a369ff..000000000 --- a/app/javascript/flavours/glitch/features/standalone/public_timeline/index.js +++ /dev/null @@ -1,100 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { expandPublicTimeline, expandCommunityTimeline } from 'flavours/glitch/actions/timelines'; -import Masonry from 'react-masonry-infinite'; -import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; -import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container'; -import { debounce } from 'lodash'; -import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; - -const mapStateToProps = (state, { local }) => { - const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap()); - - return { - statusIds: timeline.get('items', ImmutableList()), - isLoading: timeline.get('isLoading', false), - hasMore: timeline.get('hasMore', false), - }; -}; - -export default @connect(mapStateToProps) -class PublicTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - isLoading: PropTypes.bool.isRequired, - hasMore: PropTypes.bool.isRequired, - local: PropTypes.bool, - }; - - componentDidMount () { - this._connect(); - } - - componentDidUpdate (prevProps) { - if (prevProps.local !== this.props.local) { - this._disconnect(); - this._connect(); - } - } - - _connect () { - const { dispatch, local } = this.props; - - dispatch(local ? expandCommunityTimeline() : expandPublicTimeline()); - } - - handleLoadMore = () => { - const { dispatch, statusIds, local } = this.props; - const maxId = statusIds.last(); - - if (maxId) { - dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId })); - } - } - - setRef = c => { - this.masonry = c; - } - - handleHeightChange = debounce(() => { - if (!this.masonry) { - return; - } - - this.masonry.forcePack(); - }, 50) - - render () { - const { statusIds, hasMore, isLoading } = this.props; - - const sizes = [ - { columns: 1, gutter: 0 }, - { mq: '415px', columns: 1, gutter: 10 }, - { mq: '640px', columns: 2, gutter: 10 }, - { mq: '960px', columns: 3, gutter: 10 }, - { mq: '1255px', columns: 3, gutter: 10 }, - ]; - - const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined; - - return ( - <Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}> - {statusIds.map(statusId => ( - <div className='statuses-grid__item' key={statusId}> - <DetailedStatusContainer - id={statusId} - compact - measureHeight - onHeightChange={this.handleHeightChange} - /> - </div> - )).toArray()} - </Masonry> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js index ef0f0f2b7..b6f8a9877 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js @@ -4,8 +4,8 @@ import IconButton from 'flavours/glitch/components/icon_button'; import ImmutablePropTypes from 'react-immutable-proptypes'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; -import { me } from 'flavours/glitch/util/initial_state'; -import { accountAdminLink, statusAdminLink } from 'flavours/glitch/util/backend_links'; +import { me } from 'flavours/glitch/initial_state'; +import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import classNames from 'classnames'; import { PERMISSION_MANAGE_USERS } from 'flavours/glitch/permissions'; @@ -35,6 +35,7 @@ const messages = defineMessages({ admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to status' }, + openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' }, }); export default @injectIntl @@ -132,35 +133,27 @@ class ActionBar extends React.PureComponent { } handleCopy = () => { - const url = this.props.status.get('url'); - const textarea = document.createElement('textarea'); - - textarea.textContent = url; - textarea.style.position = 'fixed'; - - document.body.appendChild(textarea); - - try { - textarea.select(); - document.execCommand('copy'); - } catch (e) { - - } finally { - document.body.removeChild(textarea); - } + const url = this.props.status.get('url'); + navigator.clipboard.writeText(url); } render () { const { status, intl } = this.props; + const { signedIn, permissions } = this.context.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); const mutingConversation = status.get('muted'); const writtenByMe = status.getIn(['account', 'id']) === me; + const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']); let menu = []; if (publicStatus) { + if (isRemote) { + menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') }); + } + menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy }); menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); menu.push(null); @@ -174,7 +167,7 @@ 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.edit), action: this.handleEditClick }); + menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); } else { @@ -184,7 +177,7 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); - if ((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) { + if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS && (accountAdminLink || statusAdminLink)) { menu.push(null); if (accountAdminLink !== undefined) { menu.push({ @@ -224,7 +217,7 @@ class ActionBar extends React.PureComponent { <div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div> <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div> {shareButton} - <div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div> + <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div> <div className='detailed-status__action-bar-dropdown'> <DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' title={intl.formatMessage(messages.more)} /> diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js index 0ca2508e7..2d2e49eb8 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.js +++ b/app/javascript/flavours/glitch/features/status/components/card.js @@ -5,9 +5,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; import punycode from 'punycode'; import classnames from 'classnames'; -import { decode as decodeIDNA } from 'flavours/glitch/util/idna'; +import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; import Icon from 'flavours/glitch/components/icon'; -import { useBlurhash } from 'flavours/glitch/util/initial_state'; +import { useBlurhash } from 'flavours/glitch/initial_state'; import Blurhash from 'flavours/glitch/components/blurhash'; import { debounce } from 'lodash'; diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 91dc5ba20..46770930f 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -13,7 +13,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from 'flavours/glitch/features/video'; import Audio from 'flavours/glitch/features/audio'; import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; -import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task'; +import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import classNames from 'classnames'; import PollContainer from 'flavours/glitch/containers/poll_container'; import Icon from 'flavours/glitch/components/icon'; diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index 40e186569..e5e065987 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -27,7 +27,7 @@ import { initReport } from 'flavours/glitch/actions/reports'; import { initBoostModal } from 'flavours/glitch/actions/boosts'; import { openModal } from 'flavours/glitch/actions/modal'; import { defineMessages, injectIntl } from 'react-intl'; -import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state'; +import { boostModal, deleteModal } from 'flavours/glitch/initial_state'; import { showAlertForError } from 'flavours/glitch/actions/alerts'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 9c86d54db..aaa9c7928 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -7,6 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { createSelector } from 'reselect'; import { fetchStatus } from 'flavours/glitch/actions/statuses'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; +import LoadingIndicator from 'flavours/glitch/components/loading_indicator'; import DetailedStatus from './components/detailed_status'; import ActionBar from './components/action_bar'; import Column from 'flavours/glitch/features/ui/components/column'; @@ -47,11 +48,12 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; -import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/util/initial_state'; -import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen'; -import { autoUnfoldCW } from 'flavours/glitch/util/content_warning'; +import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/initial_state'; +import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; +import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import { textForScreenReader, defaultMediaVisibility } from 'flavours/glitch/components/status'; import Icon from 'flavours/glitch/components/icon'; +import { Helmet } from 'react-helmet'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -134,6 +136,7 @@ const makeMapStateToProps = () => { } return { + isLoading: state.getIn(['statuses', props.params.statusId, 'isLoading']), status, ancestorsIds, descendantsIds, @@ -147,18 +150,37 @@ const makeMapStateToProps = () => { return mapStateToProps; }; +const truncate = (str, num) => { + if (str.length > num) { + return str.slice(0, num) + 'โฆ'; + } else { + return str; + } +}; + +const titleFromStatus = status => { + const displayName = status.getIn(['account', 'display_name']); + const username = status.getIn(['account', 'username']); + const prefix = displayName.trim().length === 0 ? username : displayName; + const text = status.get('search_index'); + + return `${prefix}: "${truncate(text, 30)}"`; +}; + export default @injectIntl @connect(makeMapStateToProps) class Status extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, + identity: PropTypes.object, }; static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, status: ImmutablePropTypes.map, + isLoading: PropTypes.bool, settings: ImmutablePropTypes.map.isRequired, ancestorsIds: ImmutablePropTypes.list, descendantsIds: ImmutablePropTypes.list, @@ -245,14 +267,25 @@ class Status extends ImmutablePureComponent { } handleFavouriteClick = (status, e) => { - if (status.get('favourited')) { - this.props.dispatch(unfavourite(status)); - } else { - if ((e && e.shiftKey) || !favouriteModal) { - this.handleModalFavourite(status); + const { dispatch } = this.props; + const { signedIn } = this.context.identity; + + if (signedIn) { + if (status.get('favourited')) { + dispatch(unfavourite(status)); } else { - this.props.dispatch(openModal('FAVOURITE', { status, onFavourite: this.handleModalFavourite })); + if ((e && e.shiftKey) || !favouriteModal) { + this.handleModalFavourite(status); + } else { + dispatch(openModal('FAVOURITE', { status, onFavourite: this.handleModalFavourite })); + } } + } else { + dispatch(openModal('INTERACTION', { + type: 'favourite', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); } } @@ -265,16 +298,26 @@ class Status extends ImmutablePureComponent { } handleReplyClick = (status) => { - let { askReplyConfirmation, dispatch, intl } = this.props; - if (askReplyConfirmation) { - dispatch(openModal('CONFIRM', { - message: intl.formatMessage(messages.replyMessage), - confirm: intl.formatMessage(messages.replyConfirm), - onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)), - onConfirm: () => dispatch(replyCompose(status, this.context.router.history)), - })); + const { askReplyConfirmation, dispatch, intl } = this.props; + const { signedIn } = this.context.identity; + + if (signedIn) { + if (askReplyConfirmation) { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.replyMessage), + confirm: intl.formatMessage(messages.replyConfirm), + onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)), + onConfirm: () => dispatch(replyCompose(status, this.context.router.history)), + })); + } else { + dispatch(replyCompose(status, this.context.router.history)); + } } else { - dispatch(replyCompose(status, this.context.router.history)); + dispatch(openModal('INTERACTION', { + type: 'reply', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); } } @@ -290,13 +333,22 @@ class Status extends ImmutablePureComponent { handleReblogClick = (status, e) => { const { settings, dispatch } = this.props; + const { signedIn } = this.context.identity; - if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { - dispatch(initBoostModal({ status, onReblog: this.handleModalReblog, missingMediaDescription: true })); - } else if ((e && e.shiftKey) || !boostModal) { - this.handleModalReblog(status); + if (signedIn) { + if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { + dispatch(initBoostModal({ status, onReblog: this.handleModalReblog, missingMediaDescription: true })); + } else if ((e && e.shiftKey) || !boostModal) { + this.handleModalReblog(status); + } else { + dispatch(initBoostModal({ status, onReblog: this.handleModalReblog })); + } } else { - dispatch(initBoostModal({ status, onReblog: this.handleModalReblog })); + dispatch(openModal('INTERACTION', { + type: 'reblog', + accountId: status.getIn(['account', 'id']), + url: status.get('url'), + })); } } @@ -540,9 +592,17 @@ class Status extends ImmutablePureComponent { render () { let ancestors, descendants; - const { status, settings, ancestorsIds, descendantsIds, intl, domain, multiColumn, usingPiP } = this.props; + const { isLoading, status, settings, ancestorsIds, descendantsIds, intl, domain, multiColumn, usingPiP } = this.props; const { fullscreen } = this.state; + if (isLoading) { + return ( + <Column> + <LoadingIndicator /> + </Column> + ); + } + if (status === null) { return ( <Column> @@ -562,6 +622,9 @@ class Status extends ImmutablePureComponent { descendants = <div>{this.renderChildren(descendantsIds)}</div>; } + const isLocal = status.getIn(['account', 'acct'], '').indexOf('@') === -1; + const isIndexable = !status.getIn(['account', 'noindex']); + const handlers = { moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, @@ -585,7 +648,7 @@ class Status extends ImmutablePureComponent { showBackButton multiColumn={multiColumn} extraButton={( - <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><Icon id={!isExpanded ? 'eye-slash' : 'eye'} /></button> + <button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll}><Icon id={!isExpanded ? 'eye-slash' : 'eye'} /></button> )} /> @@ -633,6 +696,11 @@ class Status extends ImmutablePureComponent { {descendants} </div> </ScrollContainer> + + <Helmet> + <title>{titleFromStatus(status)}</title> + <meta name='robots' content={(isLocal && isIndexable) ? 'all' : 'noindex'} /> + </Helmet> </Column> ); } diff --git a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js new file mode 100644 index 000000000..fa69d82a4 --- /dev/null +++ b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.js @@ -0,0 +1,125 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable'; +import { languages as preloadedLanguages } from 'flavours/glitch/initial_state'; +import Option from 'flavours/glitch/features/report/components/option'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import IconButton from 'flavours/glitch/components/icon_button'; +import Button from 'flavours/glitch/components/button'; +import { followAccount } from 'flavours/glitch/actions/accounts'; + +const messages = defineMessages({ + close: { id: 'lightbox.close', defaultMessage: 'Close' }, +}); + +const getAccountLanguages = createSelector([ + (state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()), + state => state.get('statuses'), +], (statusIds, statuses) => + new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language')))); + +const mapStateToProps = (state, { accountId }) => ({ + acct: state.getIn(['accounts', accountId, 'acct']), + availableLanguages: getAccountLanguages(state, accountId), + selectedLanguages: ImmutableSet(state.getIn(['relationships', accountId, 'languages']) || ImmutableList()), +}); + +const mapDispatchToProps = (dispatch, { accountId }) => ({ + + onSubmit (languages) { + dispatch(followAccount(accountId, { languages })); + }, + +}); + +export default @connect(mapStateToProps, mapDispatchToProps) +@injectIntl +class SubscribedLanguagesModal extends ImmutablePureComponent { + + static propTypes = { + accountId: PropTypes.string.isRequired, + acct: PropTypes.string.isRequired, + availableLanguages: ImmutablePropTypes.setOf(PropTypes.string), + selectedLanguages: ImmutablePropTypes.setOf(PropTypes.string), + onClose: PropTypes.func.isRequired, + languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)), + intl: PropTypes.object.isRequired, + submit: PropTypes.func.isRequired, + }; + + static defaultProps = { + languages: preloadedLanguages, + }; + + state = { + selectedLanguages: this.props.selectedLanguages, + }; + + handleLanguageToggle = (value, checked) => { + const { selectedLanguages } = this.state; + + if (checked) { + this.setState({ selectedLanguages: selectedLanguages.add(value) }); + } else { + this.setState({ selectedLanguages: selectedLanguages.delete(value) }); + } + }; + + handleSubmit = () => { + this.props.onSubmit(this.state.selectedLanguages.toArray()); + this.props.onClose(); + } + + renderItem (value) { + const language = this.props.languages.find(language => language[0] === value); + const checked = this.state.selectedLanguages.includes(value); + + if (!language) { + return null; + } + + return ( + <Option + key={value} + name='languages' + value={value} + label={language[1]} + checked={checked} + onToggle={this.handleLanguageToggle} + multiple + /> + ); + } + + render () { + const { acct, availableLanguages, selectedLanguages, intl, onClose } = this.props; + + return ( + <div className='modal-root__modal report-dialog-modal'> + <div className='report-modal__target'> + <IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} /> + <FormattedMessage id='subscribed_languages.target' defaultMessage='Change subscribed languages for {target}' values={{ target: <strong>{acct}</strong> }} /> + </div> + + <div className='report-dialog-modal__container'> + <p className='report-dialog-modal__lead'><FormattedMessage id='subscribed_languages.lead' defaultMessage='Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.' /></p> + + <div> + {availableLanguages.union(selectedLanguages).delete(null).map(value => this.renderItem(value))} + </div> + + <div className='flex-spacer' /> + + <div className='report-dialog-modal__actions'> + <Button disabled={is(this.state.selectedLanguages, this.props.selectedLanguages)} onClick={this.handleSubmit}><FormattedMessage id='subscribed_languages.save' defaultMessage='Save changes' /></Button> + </div> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js index 3e979a250..7cbe1413d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js +++ b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.js @@ -1,44 +1,162 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import Column from 'flavours/glitch/components/column'; +import Button from 'flavours/glitch/components/button'; +import { Helmet } from 'react-helmet'; +import { Link } from 'react-router-dom'; +import classNames from 'classnames'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; -import Column from './column'; -import ColumnHeader from './column_header'; -import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim'; -import IconButton from 'flavours/glitch/components/icon_button'; +class GIF extends React.PureComponent { -const messages = defineMessages({ - title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' }, - body: { id: 'bundle_column_error.body', defaultMessage: 'Something went wrong while loading this component.' }, - retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' }, -}); + static propTypes = { + src: PropTypes.string.isRequired, + staticSrc: PropTypes.string.isRequired, + className: PropTypes.string, + animate: PropTypes.bool, + }; + + static defaultProps = { + animate: autoPlayGif, + }; + + state = { + hovering: false, + }; + + handleMouseEnter = () => { + const { animate } = this.props; + + if (!animate) { + this.setState({ hovering: true }); + } + } -class BundleColumnError extends React.Component { + handleMouseLeave = () => { + const { animate } = this.props; + + if (!animate) { + this.setState({ hovering: false }); + } + } + + render () { + const { src, staticSrc, className, animate } = this.props; + const { hovering } = this.state; + + return ( + <img + className={className} + src={(hovering || animate) ? src : staticSrc} + alt='' + role='presentation' + onMouseEnter={this.handleMouseEnter} + onMouseLeave={this.handleMouseLeave} + /> + ); + } + +} + +class CopyButton extends React.PureComponent { static propTypes = { - onRetry: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + children: PropTypes.node.isRequired, + value: PropTypes.string.isRequired, + }; + + state = { + copied: false, + }; + + handleClick = () => { + const { value } = this.props; + navigator.clipboard.writeText(value); + this.setState({ copied: true }); + this.timeout = setTimeout(() => this.setState({ copied: false }), 700); + } + + componentWillUnmount () { + if (this.timeout) clearTimeout(this.timeout); } + render () { + const { children } = this.props; + const { copied } = this.state; + + return ( + <Button onClick={this.handleClick} className={copied ? 'copied' : 'copyable'}>{copied ? <FormattedMessage id='copypaste.copied' defaultMessage='Copied' /> : children}</Button> + ); + } + +} + +export default @injectIntl +class BundleColumnError extends React.PureComponent { + + static propTypes = { + errorType: PropTypes.oneOf(['routing', 'network', 'error']), + onRetry: PropTypes.func, + intl: PropTypes.object.isRequired, + multiColumn: PropTypes.bool, + stacktrace: PropTypes.string, + }; + + static defaultProps = { + errorType: 'routing', + }; + handleRetry = () => { - this.props.onRetry(); + const { onRetry } = this.props; + + if (onRetry) { + onRetry(); + } } render () { - const { intl: { formatMessage } } = this.props; + const { errorType, multiColumn, stacktrace } = this.props; + + let title, body; + + switch(errorType) { + case 'routing': + title = <FormattedMessage id='bundle_column_error.routing.title' defaultMessage='404' />; + body = <FormattedMessage id='bundle_column_error.routing.body' defaultMessage='The requested page could not be found. Are you sure the URL in the address bar is correct?' />; + break; + case 'network': + title = <FormattedMessage id='bundle_column_error.network.title' defaultMessage='Network error' />; + body = <FormattedMessage id='bundle_column_error.network.body' defaultMessage='There was an error when trying to load this page. This could be due to a temporary problem with your internet connection or this server.' />; + break; + case 'error': + title = <FormattedMessage id='bundle_column_error.error.title' defaultMessage='Oh, no!' />; + body = <FormattedMessage id='bundle_column_error.error.body' defaultMessage='The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.' />; + break; + } return ( - <Column> - <ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} /> - <ColumnBackButtonSlim /> + <Column bindToDocument={!multiColumn}> <div className='error-column'> - <IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} /> - {formatMessage(messages.body)} + <GIF src='/oops.gif' staticSrc='/oops.png' className='error-column__image' /> + + <div className='error-column__message'> + <h1>{title}</h1> + <p>{body}</p> + + <div className='error-column__message__actions'> + {errorType === 'network' && <Button onClick={this.handleRetry}><FormattedMessage id='bundle_column_error.retry' defaultMessage='Try again' /></Button>} + {errorType === 'error' && <CopyButton value={stacktrace}><FormattedMessage id='bundle_column_error.copy_stacktrace' defaultMessage='Copy error report' /></CopyButton>} + <Link to='/' className={classNames('button', { 'button-tertiary': errorType !== 'routing' })}><FormattedMessage id='bundle_column_error.return' defaultMessage='Go back home' /></Link> + </div> + </div> </div> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> </Column> ); } } - -export default injectIntl(BundleColumnError); diff --git a/app/javascript/flavours/glitch/features/ui/components/column.js b/app/javascript/flavours/glitch/features/ui/components/column.js index ab78414e0..e9c1e2f87 100644 --- a/app/javascript/flavours/glitch/features/ui/components/column.js +++ b/app/javascript/flavours/glitch/features/ui/components/column.js @@ -2,8 +2,8 @@ import React from 'react'; import ColumnHeader from './column_header'; import PropTypes from 'prop-types'; import { debounce } from 'lodash'; -import { scrollTop } from 'flavours/glitch/util/scroll'; -import { isMobile } from 'flavours/glitch/util/is_mobile'; +import { scrollTop } from 'flavours/glitch/scroll'; +import { isMobile } from 'flavours/glitch/is_mobile'; export default class Column extends React.PureComponent { @@ -14,10 +14,11 @@ export default class Column extends React.PureComponent { active: PropTypes.bool, hideHeadingOnMobile: PropTypes.bool, name: PropTypes.string, + bindToDocument: PropTypes.bool, }; handleHeaderClick = () => { - const scrollable = this.node.querySelector('.scrollable'); + const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); if (!scrollable) { return; @@ -27,7 +28,7 @@ export default class Column extends React.PureComponent { } scrollTop () { - const scrollable = this.node.querySelector('.scrollable'); + const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable'); if (!scrollable) { return; diff --git a/app/javascript/flavours/glitch/features/ui/components/column_link.js b/app/javascript/flavours/glitch/features/ui/components/column_link.js index d04b869b6..bd1c20b47 100644 --- a/app/javascript/flavours/glitch/features/ui/components/column_link.js +++ b/app/javascript/flavours/glitch/features/ui/components/column_link.js @@ -1,26 +1,29 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Link } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; -const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => { +const ColumnLink = ({ icon, text, to, onClick, href, method, badge, transparent, ...other }) => { + const className = classNames('column-link', { 'column-link--transparent': transparent }); const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null; + const iconElement = typeof icon === 'string' ? <Icon id={icon} fixedWidth className='column-link__icon' /> : icon; if (href) { return ( - <a href={href} className='column-link' data-method={method}> - <Icon id={icon} fixedWidth className='column-link__icon' /> - {text} + <a href={href} className={className} data-method={method} title={text} {...other}> + {iconElement} + <span>{text}</span> {badgeElement} </a> ); } else if (to) { return ( - <Link to={to} className='column-link'> - <Icon id={icon} fixedWidth className='column-link__icon' /> - {text} + <NavLink to={to} className={className} title={text} {...other}> + {iconElement} + <span>{text}</span> {badgeElement} - </Link> + </NavLink> ); } else { const handleOnClick = (e) => { @@ -29,9 +32,9 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => { return onClick(e); } return ( - <a href='#' onClick={onClick && handleOnClick} className='column-link' tabIndex='0'> - <Icon id={icon} fixedWidth className='column-link__icon' /> - {text} + <a href='#' onClick={onClick && handleOnClick} className={className} title={text} {...other} tabIndex='0'> + {iconElement} + <span>{text}</span> {badgeElement} </a> ); @@ -39,13 +42,14 @@ const ColumnLink = ({ icon, text, to, onClick, href, method, badge }) => { }; ColumnLink.propTypes = { - icon: PropTypes.string.isRequired, + icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired, text: PropTypes.string.isRequired, to: PropTypes.string, onClick: PropTypes.func, href: PropTypes.string, method: PropTypes.string, badge: PropTypes.node, + transparent: PropTypes.bool, }; export default ColumnLink; diff --git a/app/javascript/flavours/glitch/features/ui/components/column_loading.js b/app/javascript/flavours/glitch/features/ui/components/column_loading.js index 22c00c915..b07385397 100644 --- a/app/javascript/flavours/glitch/features/ui/components/column_loading.js +++ b/app/javascript/flavours/glitch/features/ui/components/column_loading.js @@ -10,6 +10,7 @@ export default class ColumnLoading extends ImmutablePureComponent { static propTypes = { title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), icon: PropTypes.string, + multiColumn: PropTypes.bool, }; static defaultProps = { @@ -18,10 +19,11 @@ export default class ColumnLoading extends ImmutablePureComponent { }; render() { - let { title, icon } = this.props; + let { title, icon, multiColumn } = this.props; + return ( <Column> - <ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} placeholder /> + <ColumnHeader icon={icon} title={title} multiColumn={multiColumn} focusable={false} placeholder /> <div className='scrollable' /> </Column> ); diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js index bfb1ae405..bf3e79c24 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js @@ -1,15 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; - -import ReactSwipeableViews from 'react-swipeable-views'; -import TabsBar, { links, getIndex, getLink } from './tabs_bar'; -import { Link } from 'react-router-dom'; - -import { disableSwiping } from 'flavours/glitch/util/initial_state'; - import BundleContainer from '../containers/bundle_container'; import ColumnLoading from './column_loading'; import DrawerLoading from './drawer_loading'; @@ -26,13 +18,12 @@ import { BookmarkedStatuses, ListTimeline, Directory, -} from 'flavours/glitch/util/async-components'; -import Icon from 'flavours/glitch/components/icon'; +} from '../../ui/util/async-components'; import ComposePanel from './compose_panel'; import NavigationPanel from './navigation_panel'; import { supportsPassiveEvents } from 'detect-passive-events'; -import { scrollRight } from 'flavours/glitch/util/scroll'; +import { scrollRight } from 'flavours/glitch/scroll'; const componentMap = { 'COMPOSE': Compose, @@ -49,21 +40,13 @@ const componentMap = { 'DIRECTORY': Directory, }; -const shouldHideFAB = path => path.match(/^\/statuses\/|^\/@[^/]+\/\d+|^\/publish|^\/search|^\/getting-started|^\/start/); - -const messages = defineMessages({ - publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }, -}); - -export default @(component => injectIntl(component, { withRef: true })) -class ColumnsArea extends ImmutablePureComponent { +export default class ColumnsArea extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object.isRequired, }; static propTypes = { - intl: PropTypes.object.isRequired, columns: ImmutablePropTypes.list.isRequired, singleColumn: PropTypes.bool, children: PropTypes.node, @@ -71,20 +54,13 @@ class ColumnsArea extends ImmutablePureComponent { openSettings: PropTypes.func, }; - // Corresponds to (max-width: 600px + (285px * 1) + (10px * 1)) in SCSS - mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 895px)'); + // Corresponds to (max-width: $no-gap-breakpoint + 285px - 1px) in SCSS + mediaQuery = 'matchMedia' in window && window.matchMedia('(max-width: 1174px)'); state = { - shouldAnimate: false, renderComposePanel: !(this.mediaQuery && this.mediaQuery.matches), } - componentWillReceiveProps() { - if (typeof this.pendingIndex !== 'number' && this.lastIndex !== getIndex(this.context.router.history.location.pathname)) { - this.setState({ shouldAnimate: false }); - } - } - componentDidMount() { if (!this.props.singleColumn) { this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); @@ -99,10 +75,7 @@ class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !this.mediaQuery.matches }); } - this.lastIndex = getIndex(this.context.router.history.location.pathname); this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); - - this.setState({ shouldAnimate: true }); } componentWillUpdate(nextProps) { @@ -115,13 +88,6 @@ class ColumnsArea extends ImmutablePureComponent { if (this.props.singleColumn !== prevProps.singleColumn && !this.props.singleColumn) { this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); } - - const newIndex = getIndex(this.context.router.history.location.pathname); - - if (this.lastIndex !== newIndex) { - this.lastIndex = newIndex; - this.setState({ shouldAnimate: true }); - } } componentWillUnmount () { @@ -149,31 +115,6 @@ class ColumnsArea extends ImmutablePureComponent { this.setState({ renderComposePanel: !e.matches }); } - handleSwipe = (index) => { - this.pendingIndex = index; - - const nextLinkTranslationId = links[index].props['data-preview-title-id']; - const currentLinkSelector = '.tabs-bar__link.active'; - const nextLinkSelector = `.tabs-bar__link[data-preview-title-id="${nextLinkTranslationId}"]`; - - // HACK: Remove the active class from the current link and set it to the next one - // React-router does this for us, but too late, feeling laggy. - document.querySelector(currentLinkSelector).classList.remove('active'); - document.querySelector(nextLinkSelector).classList.add('active'); - - if (!this.state.shouldAnimate && typeof this.pendingIndex === 'number') { - this.context.router.history.push(getLink(this.pendingIndex)); - this.pendingIndex = null; - } - } - - handleAnimationEnd = () => { - if (typeof this.pendingIndex === 'number') { - this.context.router.history.push(getLink(this.pendingIndex)); - this.pendingIndex = null; - } - } - handleWheel = () => { if (typeof this._interruptScrollAnimation !== 'function') { return; @@ -186,47 +127,19 @@ class ColumnsArea extends ImmutablePureComponent { this.node = node; } - renderView = (link, index) => { - const columnIndex = getIndex(this.context.router.history.location.pathname); - const title = this.props.intl.formatMessage({ id: link.props['data-preview-title-id'] }); - const icon = link.props['data-preview-icon']; - - const view = (index === columnIndex) ? - React.cloneElement(this.props.children) : - <ColumnLoading title={title} icon={icon} />; - - return ( - <div className='columns-area columns-area--mobile' key={index}> - {view} - </div> - ); - } - renderLoading = columnId => () => { - return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading />; + return columnId === 'COMPOSE' ? <DrawerLoading /> : <ColumnLoading multiColumn />; } renderError = (props) => { - return <BundleColumnError {...props} />; + return <BundleColumnError multiColumn errorType='network' {...props} />; } render () { - const { columns, children, singleColumn, intl, navbarUnder, openSettings } = this.props; - const { shouldAnimate, renderComposePanel } = this.state; - - const columnIndex = getIndex(this.context.router.history.location.pathname); + const { columns, children, singleColumn, navbarUnder, openSettings } = this.props; + const { renderComposePanel } = this.state; if (singleColumn) { - const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>; - - const content = columnIndex !== -1 ? ( - <ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}> - {links.map(this.renderView)} - </ReactSwipeableViews> - ) : ( - <div key='content' className='columns-area columns-area--mobile'>{children}</div> - ); - return ( <div className='columns-area__panels'> <div className='columns-area__panels__pane columns-area__panels__pane--compositional'> @@ -235,10 +148,9 @@ class ColumnsArea extends ImmutablePureComponent { </div> </div> - <div className={`columns-area__panels__main ${floatingActionButton && 'with-fab'}`}> - {!navbarUnder && <TabsBar key='tabs' />} - {content} - {navbarUnder && <TabsBar key='tabs' />} + <div className='columns-area__panels__main'> + <div className='tabs-bar__wrapper'><div id='tabs-bar__portal' /></div> + <div className='columns-area columns-area--mobile'>{children}</div> </div> <div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'> @@ -246,8 +158,6 @@ class ColumnsArea extends ImmutablePureComponent { <NavigationPanel onOpenSettings={openSettings} /> </div> </div> - - {floatingActionButton} </div> ); } diff --git a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js index 8fd528da0..baf7f25be 100644 --- a/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/compare_history_modal.js @@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import { FormattedMessage } from 'react-intl'; import { closeModal } from 'flavours/glitch/actions/modal'; -import emojify from 'flavours/glitch/util/emoji'; +import emojify from 'flavours/glitch/features/emoji/emoji'; import escapeTextContentForBrowser from 'escape-html'; import InlineAccount from 'flavours/glitch/components/inline_account'; import IconButton from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js index 498f09ab6..dde252a61 100644 --- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.js @@ -1,16 +1,58 @@ import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; import NavigationContainer from 'flavours/glitch/features/compose/containers/navigation_container'; import LinkFooter from './link_footer'; +import ServerBanner from 'flavours/glitch/components/server_banner'; +import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose'; -const ComposePanel = () => ( - <div className='compose-panel'> - <SearchContainer openInRoute /> - <NavigationContainer /> - <ComposeFormContainer singleColumn /> - <LinkFooter withHotkeys /> - </div> -); +export default @connect() +class ComposePanel extends React.PureComponent { -export default ComposePanel; + static contextTypes = { + identity: PropTypes.object.isRequired, + }; + + static propTypes = { + dispatch: PropTypes.func.isRequired, + }; + + componentDidMount () { + const { dispatch } = this.props; + dispatch(mountCompose()); + } + + componentWillUnmount () { + const { dispatch } = this.props; + dispatch(unmountCompose()); + } + + render() { + const { signedIn } = this.context.identity; + + return ( + <div className='compose-panel'> + <SearchContainer openInRoute /> + + {!signedIn && ( + <React.Fragment> + <ServerBanner /> + <div className='flex-spacer' /> + </React.Fragment> + )} + + {signedIn && ( + <React.Fragment> + <NavigationContainer /> + <ComposeFormContainer singleColumn /> + </React.Fragment> + )} + + <LinkFooter /> + </div> + ); + } + +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js index 9cb5a30b9..68f04cb21 100644 --- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { preferenceLink } from 'flavours/glitch/util/backend_links'; +import { preferenceLink } from 'flavours/glitch/utils/backend_links'; import Button from 'flavours/glitch/components/button'; import Icon from 'flavours/glitch/components/icon'; import illustration from 'flavours/glitch/images/logo_warn_glitch.svg'; diff --git a/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js new file mode 100644 index 000000000..c861d4d81 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/disabled_account_banner.js @@ -0,0 +1,92 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Link } from 'react-router-dom'; +import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { disabledAccountId, movedToAccountId, domain } from 'flavours/glitch/initial_state'; +import { openModal } from 'flavours/glitch/actions/modal'; +import { logOut } from 'flavours/glitch/utils/log_out'; + +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 mapStateToProps = (state) => ({ + disabledAcct: state.getIn(['accounts', disabledAccountId, 'acct']), + movedToAcct: movedToAccountId ? state.getIn(['accounts', movedToAccountId, 'acct']) : undefined, +}); + +const mapDispatchToProps = (dispatch, { intl }) => ({ + onLogout () { + dispatch(openModal('CONFIRM', { + message: intl.formatMessage(messages.logoutMessage), + confirm: intl.formatMessage(messages.logoutConfirm), + closeWhenConfirm: false, + onConfirm: () => logOut(), + })); + }, +}); + +export default @injectIntl +@connect(mapStateToProps, mapDispatchToProps) +class DisabledAccountBanner extends React.PureComponent { + + static propTypes = { + disabledAcct: PropTypes.string.isRequired, + movedToAcct: PropTypes.string, + onLogout: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleLogOutClick = e => { + e.preventDefault(); + e.stopPropagation(); + + this.props.onLogout(); + + return false; + } + + render () { + const { disabledAcct, movedToAcct } = this.props; + + const disabledAccountLink = ( + <Link to={`/@${disabledAcct}`}> + {disabledAcct}@{domain} + </Link> + ); + + return ( + <div className='sign-in-banner'> + <p> + {movedToAcct ? ( + <FormattedMessage + id='moved_to_account_banner.text' + defaultMessage='Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.' + values={{ + disabledAccount: disabledAccountLink, + movedToAccount: <Link to={`/@${movedToAcct}`}>{movedToAcct.includes('@') ? movedToAcct : `${movedToAcct}@${domain}`}</Link>, + }} + /> + ) : ( + <FormattedMessage + id='disabled_account_banner.text' + defaultMessage='Your account {disabledAccount} is currently disabled.' + values={{ + disabledAccount: disabledAccountLink, + }} + /> + )} + </p> + <a href='/auth/edit' className='button button--block'> + <FormattedMessage id='disabled_account_banner.account_settings' defaultMessage='Account settings' /> + </a> + <button type='button' className='button button--block button-tertiary' onClick={this.handleLogOutClick}> + <FormattedMessage id='confirmations.logout.confirm' defaultMessage='Log out' /> + </button> + </div> + ); + } + +}; diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js b/app/javascript/flavours/glitch/features/ui/components/embed_modal.js index b6f5e628d..624b68f7e 100644 --- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/embed_modal.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import api from 'flavours/glitch/util/api'; +import api from 'flavours/glitch/api'; import IconButton from 'flavours/glitch/components/icon_button'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js index 4d02be29b..d7f671d58 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.js @@ -58,11 +58,11 @@ class FavouriteModal extends ImmutablePureComponent { const { status, intl } = this.props; return ( - <div className='modal-root__modal favourite-modal'> - <div className='favourite-modal__container'> + <div className='modal-root__modal boost-modal'> + <div className='boost-modal__container'> <div className={classNames('status', `status-${status.get('visibility')}`, 'light')}> - <div className='favourite-modal__status-header'> - <div className='favourite-modal__status-time'> + <div className='boost-modal__status-header'> + <div className='boost-modal__status-time'> <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'> <VisibilityIcon visibility={status.get('visibility')} /> <RelativeTimestamp timestamp={status.get('created_at')} /> @@ -90,7 +90,7 @@ class FavouriteModal extends ImmutablePureComponent { </div> </div> - <div className='favourite-modal__action-bar'> + <div className='boost-modal__action-bar'> <div><FormattedMessage id='favourite_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='star' /></span> }} /></div> <Button text={intl.formatMessage(messages.favourite)} onClick={this.handleFavourite} ref={this.setRef} /> </div> diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js index 5a4baa5a1..de330b3a1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js @@ -15,14 +15,14 @@ import Textarea from 'react-textarea-autosize'; import UploadProgress from 'flavours/glitch/features/compose/components/upload_progress'; import CharacterCounter from 'flavours/glitch/features/compose/components/character_counter'; import { length } from 'stringz'; -import { Tesseract as fetchTesseract } from 'flavours/glitch/util/async-components'; +import { Tesseract as fetchTesseract } from 'flavours/glitch/features/ui/util/async-components'; import GIFV from 'flavours/glitch/components/gifv'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; // eslint-disable-next-line import/no-extraneous-dependencies import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js'; // eslint-disable-next-line import/extensions import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js'; -import { assetHost } from 'flavours/glitch/util/config'; +import { assetHost } from 'flavours/glitch/utils/config'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, diff --git a/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js b/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.js index c30427896..301392a52 100644 --- a/app/javascript/flavours/glitch/features/ui/components/follow_requests_nav_link.js +++ b/app/javascript/flavours/glitch/features/ui/components/follow_requests_column_link.js @@ -2,22 +2,27 @@ import React from 'react'; import PropTypes from 'prop-types'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { connect } from 'react-redux'; -import { NavLink, withRouter } from 'react-router-dom'; +import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; import IconWithBadge from 'flavours/glitch/components/icon_with_badge'; import { List as ImmutableList } from 'immutable'; -import { FormattedMessage } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; + +const messages = defineMessages({ + text: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, +}); const mapStateToProps = state => ({ count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, }); -export default @withRouter +export default @injectIntl @connect(mapStateToProps) -class FollowRequestsNavLink extends React.Component { +class FollowRequestsColumnLink extends React.Component { static propTypes = { dispatch: PropTypes.func.isRequired, count: PropTypes.number.isRequired, + intl: PropTypes.object.isRequired, }; componentDidMount () { @@ -27,13 +32,20 @@ class FollowRequestsNavLink extends React.Component { } render () { - const { count } = this.props; + const { count, intl } = this.props; if (count === 0) { return null; } - return <NavLink className='column-link column-link--transparent' to='/follow_requests'><IconWithBadge className='column-link__icon' id='user-plus' count={count} /><FormattedMessage id='navigation_bar.follow_requests' defaultMessage='Follow requests' /></NavLink>; + return ( + <ColumnLink + transparent + to='/follow_requests' + icon={<IconWithBadge className='column-link__icon' id='user-plus' count={count} />} + text={intl.formatMessage(messages.text)} + /> + ); } } diff --git a/app/javascript/flavours/glitch/features/ui/components/header.js b/app/javascript/flavours/glitch/features/ui/components/header.js new file mode 100644 index 000000000..6c2fb40ba --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/header.js @@ -0,0 +1,63 @@ +import React from 'react'; +import Logo from 'flavours/glitch/components/logo'; +import { Link, withRouter } from 'react-router-dom'; +import { FormattedMessage } from 'react-intl'; +import { registrationsOpen, me } from 'flavours/glitch/initial_state'; +import Avatar from 'flavours/glitch/components/avatar'; +import Permalink from 'flavours/glitch/components/permalink'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +const Account = connect(state => ({ + account: state.getIn(['accounts', me]), +}))(({ account }) => ( + <Permalink href={account.get('url')} to={`/@${account.get('acct')}`} title={account.get('acct')}> + <Avatar account={account} size={35} /> + </Permalink> +)); + +export default @withRouter +class Header extends React.PureComponent { + + static contextTypes = { + identity: PropTypes.object, + }; + + static propTypes = { + location: PropTypes.object, + }; + + render () { + const { signedIn } = this.context.identity; + const { location } = this.props; + + let content; + + if (signedIn) { + content = ( + <> + {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish' defaultMessage='Publish' /></Link>} + <Account /> + </> + ); + } else { + content = ( + <> + <a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> + <a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a> + </> + ); + } + + return ( + <div className='ui__header'> + <Link to='/' className='ui__header__logo'><Logo /></Link> + + <div className='ui__header__links'> + {content} + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/image_modal.js b/app/javascript/flavours/glitch/features/ui/components/image_modal.js new file mode 100644 index 000000000..a792b9be7 --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/image_modal.js @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { defineMessages, injectIntl } from 'react-intl'; +import IconButton from 'flavours/glitch/components/icon_button'; +import ImageLoader from './image_loader'; + +const messages = defineMessages({ + close: { id: 'lightbox.close', defaultMessage: 'Close' }, +}); + +export default @injectIntl +class ImageModal extends React.PureComponent { + + static propTypes = { + src: PropTypes.string.isRequired, + alt: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + navigationHidden: false, + }; + + toggleNavigation = () => { + this.setState(prevState => ({ + navigationHidden: !prevState.navigationHidden, + })); + }; + + render () { + const { intl, src, alt, onClose } = this.props; + const { navigationHidden } = this.state; + + const navigationClassName = classNames('media-modal__navigation', { + 'media-modal__navigation--hidden': navigationHidden, + }); + + return ( + <div className='modal-root__modal media-modal'> + <div className='media-modal__closer' role='presentation' onClick={onClose} > + <ImageLoader + src={src} + width={400} + height={400} + alt={alt} + onClick={this.toggleNavigation} + /> + </div> + + <div className={navigationClassName}> + <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} /> + </div> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.js b/app/javascript/flavours/glitch/features/ui/components/link_footer.js index 040e967f2..075e04494 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.js +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.js @@ -3,9 +3,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { limitedFederationMode, 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 { domain, version, source_url, profile_directory as profileDirectory } from 'flavours/glitch/initial_state'; +import { logOut } from 'flavours/glitch/utils/log_out'; import { openModal } from 'flavours/glitch/actions/modal'; import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions'; @@ -43,34 +42,48 @@ class LinkFooter extends React.PureComponent { e.stopPropagation(); this.props.onLogout(); - + return false; } render () { + const { signedIn, permissions } = this.context.identity; + + const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); + const canProfileDirectory = profileDirectory; + return ( - <div className='getting-started__footer'> - <ul> - {((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) && <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>} - {!limitedFederationMode && <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> + <div className='link-footer'> + <p> + <strong>{domain}</strong>: + {' '} + <Link key='about' to='/about'><FormattedMessage id='footer.about' defaultMessage='About' /></Link> + {canInvite && ( + <> + {' ยท '} + <a key='invites' href='/invites' target='_blank'><FormattedMessage id='footer.invite' defaultMessage='Invite people' /></a> + </> + )} + {canProfileDirectory && ( + <> + {' ยท '} + <Link key='directory' to='/directory'><FormattedMessage id='footer.directory' defaultMessage='Profiles directory' /></Link> + </> + )} + {' ยท '} + <Link key='privacy-policy' to='/privacy-policy'><FormattedMessage id='footer.privacy_policy' defaultMessage='Privacy policy' /></Link> + </p> <p> - <FormattedMessage - id='getting_started.open_source_notice' - defaultMessage='GlitchCafรฉ is free open source software, based on {Glitchsoc} which is a friendly fork of {Mastodon}. You can see our source code on {github} and report bugs, request features, or contribute to the code by emailing {admin}.' - values={{ - github: <span><a href='https://git.starfall.systems/pluralcafe/mastodon/' rel='noopener noreferrer' target='_blank'>our Git repository</a> (v{version})</span>, - Glitchsoc: <a href='https://github.com/glitch-soc/mastodon' rel='noopener noreferrer' target='_blank'>glitch-soc/mastodon</a>, - Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener noreferrer' target='_blank'>Mastodon</a>, - admin: <a href='mailto://admin@plural.cafe' rel='noopener noreferrer'>admin@plural.cafe</a> }} - /> + <strong>Mastodon</strong>: + {' '} + <a href='https://joinmastodon.org' target='_blank'><FormattedMessage id='footer.about' defaultMessage='About Mastodon' /></a> + {' ยท '} + <Link to='/keyboard-shortcuts'><FormattedMessage id='footer.keyboard_shortcuts' defaultMessage='Keyboard shortcuts' /></Link> + {' ยท '} + <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a> + {' ยท '} + v{version} </p> </div> ); diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.js b/app/javascript/flavours/glitch/features/ui/components/list_panel.js index e61234283..dff830065 100644 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.js @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { createSelector } from 'reselect'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { fetchLists } from 'flavours/glitch/actions/lists'; import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { NavLink, withRouter } from 'react-router-dom'; -import Icon from 'flavours/glitch/components/icon'; +import { withRouter } from 'react-router-dom'; +import { fetchLists } from 'flavours/glitch/actions/lists'; +import ColumnLink from './column_link'; const getOrderedLists = createSelector([state => state.get('lists')], lists => { if (!lists) { @@ -42,11 +42,11 @@ class ListPanel extends ImmutablePureComponent { } return ( - <div> + <div className='list-panel'> <hr /> {lists.map(list => ( - <NavLink key={list.get('id')} className='column-link column-link--transparent' strict to={`/lists/${list.get('id')}`}><Icon className='column-link__icon' id='list-ul' fixedWidth />{list.get('title')}</NavLink> + <ColumnLink icon='list-ul' key={list.get('id')} strict text={list.get('title')} to={`/lists/${list.get('id')}`} transparent /> ))} </div> ); diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js index baa5ff275..ec3af857d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js @@ -12,7 +12,7 @@ import Icon from 'flavours/glitch/components/icon'; import GIFV from 'flavours/glitch/components/gifv'; import Footer from 'flavours/glitch/features/picture_in_picture/components/footer'; import { getAverageFromBlurhash } from 'flavours/glitch/blurhash'; -import { disableSwiping } from 'flavours/glitch/util/initial_state'; +import { disableSwiping } from 'flavours/glitch/initial_state'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.js b/app/javascript/flavours/glitch/features/ui/components/modal_root.js index 4df3a0dee..d2ee28948 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.js +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar'; +import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar'; import Base from 'flavours/glitch/components/modal_root'; import BundleContainer from '../containers/bundle_container'; import BundleModalError from './bundle_modal_error'; @@ -15,6 +15,7 @@ import DoodleModal from './doodle_modal'; import ConfirmationModal from './confirmation_modal'; import FocalPointModal from './focal_point_modal'; import DeprecatedSettingsModal from './deprecated_settings_modal'; +import ImageModal from './image_modal'; import { OnboardingModal, MuteModal, @@ -27,13 +28,18 @@ import { PinnedAccountsEditor, CompareHistoryModal, FilterModal, -} from 'flavours/glitch/util/async-components'; + InteractionModal, + SubscribedLanguagesModal, + ClosedRegistrationsModal, +} from 'flavours/glitch/features/ui/util/async-components'; +import { Helmet } from 'react-helmet'; const MODAL_COMPONENTS = { 'MEDIA': () => Promise.resolve({ default: MediaModal }), 'ONBOARDING': OnboardingModal, 'VIDEO': () => Promise.resolve({ default: VideoModal }), 'AUDIO': () => Promise.resolve({ default: AudioModal }), + 'IMAGE': () => Promise.resolve({ default: ImageModal }), 'BOOST': () => Promise.resolve({ default: BoostModal }), 'FAVOURITE': () => Promise.resolve({ default: FavouriteModal }), 'DOODLE': () => Promise.resolve({ default: DoodleModal }), @@ -51,6 +57,9 @@ const MODAL_COMPONENTS = { 'PINNED_ACCOUNTS_EDITOR': PinnedAccountsEditor, 'COMPARE_HISTORY': CompareHistoryModal, 'FILTER': FilterModal, + 'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal, + 'INTERACTION': InteractionModal, + 'CLOSED_REGISTRATIONS': ClosedRegistrationsModal, }; export default class ModalRoot extends React.PureComponent { @@ -115,9 +124,15 @@ export default class ModalRoot extends React.PureComponent { return ( <Base backgroundColor={backgroundColor} onClose={this.handleClose} noEsc={props ? props.noEsc : false} ignoreFocus={ignoreFocus}> {visible && ( - <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}> - {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />} - </BundleContainer> + <> + <BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}> + {(SpecificComponent) => <SpecificComponent {...props} onChangeBackgroundColor={this.setBackgroundColor} onClose={this.handleClose} ref={this.setModalRef} />} + </BundleContainer> + + <Helmet> + <meta name='robots' content='noindex' /> + </Helmet> + </> )} </Base> ); diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js index 2dcd535ca..3b46c6eec 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.js @@ -1,37 +1,104 @@ import React from 'react'; -import { NavLink, withRouter } from 'react-router-dom'; -import { FormattedMessage } from 'react-intl'; -import Icon from 'flavours/glitch/components/icon'; -import { profile_directory, showTrends } from 'flavours/glitch/util/initial_state'; -import { preferencesLink, relationshipsLink } from 'flavours/glitch/util/backend_links'; -import NotificationsCounterIcon from './notifications_counter_icon'; -import FollowRequestsNavLink from './follow_requests_nav_link'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; +import { Link } from 'react-router-dom'; +import { timelinePreview, showTrends } from 'flavours/glitch/initial_state'; +import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; +import DisabledAccountBanner from './disabled_account_banner'; +import FollowRequestsColumnLink from './follow_requests_column_link'; import ListPanel from './list_panel'; -import TrendsContainer from 'flavours/glitch/features/getting_started/containers/trends_container'; - -const NavigationPanel = ({ onOpenSettings }) => ( - <div className='navigation-panel'> - <NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink> - <FollowRequestsNavLink /> - <NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink> - <NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='envelope' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink> - {profile_directory && <NavLink className='column-link column-link--transparent' to='/directory'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></NavLink>} - <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink> - - <ListPanel /> - - <hr /> - - {!!preferencesLink && <a className='column-link column-link--transparent' href={preferencesLink} target='_blank'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>} - <a className='column-link column-link--transparent' href='#' onClick={onOpenSettings}><Icon className='column-link__icon' id='cogs' fixedWidth /><FormattedMessage id='navigation_bar.app_settings' defaultMessage='App settings' /></a> - {!!relationshipsLink && <a className='column-link column-link--transparent' href={relationshipsLink} target='_blank'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>} - - {showTrends && <div className='flex-spacer' />} - {showTrends && <TrendsContainer />} - </div> -); - -export default withRouter(NavigationPanel); +import NotificationsCounterIcon from './notifications_counter_icon'; +import SignInBanner from './sign_in_banner'; +import { preferencesLink, relationshipsLink } from 'flavours/glitch/utils/backend_links'; +import NavigationPortal from 'flavours/glitch/components/navigation_portal'; + +const messages = defineMessages({ + home: { id: 'tabs_bar.home', defaultMessage: 'Home' }, + notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, + explore: { id: 'explore.title', defaultMessage: 'Explore' }, + local: { id: 'tabs_bar.local_timeline', defaultMessage: 'Local' }, + federated: { id: 'tabs_bar.federated_timeline', defaultMessage: 'Federated' }, + direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, + favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, + bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, + lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, + followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' }, + about: { id: 'navigation_bar.about', defaultMessage: 'About' }, + search: { id: 'navigation_bar.search', defaultMessage: 'Search' }, + app_settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, +}); + +export default @injectIntl +class NavigationPanel extends React.Component { + + static contextTypes = { + router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, + }; + + static propTypes = { + onOpenSettings: PropTypes.func, + }; + + render() { + const { intl, onOpenSettings } = this.props; + const { signedIn, disabledAccountId } = this.context.identity; + + return ( + <div className='navigation-panel'> + {signedIn && ( + <React.Fragment> + <ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} /> + <ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} /> + <FollowRequestsColumnLink /> + </React.Fragment> + )} + + {showTrends ? ( + <ColumnLink transparent to='/explore' icon='hashtag' text={intl.formatMessage(messages.explore)} /> + ) : ( + <ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} /> + )} + + {(signedIn || timelinePreview) && ( + <> + <ColumnLink transparent to='/public/local' icon='users' text={intl.formatMessage(messages.local)} /> + <ColumnLink transparent exact to='/public' icon='globe' text={intl.formatMessage(messages.federated)} /> + </> + )} + + {!signedIn && ( + <div className='navigation-panel__sign-in-banner'> + <hr /> + { disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner /> } + </div> + )} + + {signedIn && ( + <React.Fragment> + <ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} /> + <ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} /> + <ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} /> + <ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} /> + + <ListPanel /> + + <hr /> + + {!!preferencesLink && <ColumnLink transparent href={preferencesLink} icon='cog' text={intl.formatMessage(messages.preferences)} />} + <ColumnLink transparent onClick={onOpenSettings} icon='cogs' text={intl.formatMessage(messages.app_settings)} /> + </React.Fragment> + )} + + <div className='navigation-panel__legal'> + <hr /> + <ColumnLink transparent to='/about' icon='ellipsis-h' text={intl.formatMessage(messages.about)} /> + </div> + + <NavigationPortal /> + </div> + ); + } + +} diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js index 82a1ee4a4..5ca003ee9 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js @@ -10,7 +10,7 @@ import ComposeForm from 'flavours/glitch/features/compose/components/compose_for import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar'; import Search from 'flavours/glitch/features/compose/components/search'; import ColumnHeader from './column_header'; -import { me, source_url } from 'flavours/glitch/util/initial_state'; +import { me, source_url } from 'flavours/glitch/initial_state'; const noop = () => { }; @@ -144,13 +144,13 @@ const PageSix = ({ admin, domain }) => { <p> <FormattedMessage id='onboarding.page_six.github' - defaultMessage='{domain} runs on GlitchCafรฉ, which is based on {Glitchsoc}, a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. GlitchCafรฉ is free open-source software. You can view the source code on {github} and report bugs, request features, or contribute to the code by emailing {admin}.' + defaultMessage='{domain} runs on GlitchCafรฉ, which is based on {Glitchsoc}, a friendly {fork} of {Mastodon}. GlitchCafรฉ is fully compatible with all Mastodon apps and instances. GlitchCafรฉ is free open-source software. You can view the source code on {git} and report bugs, request features, or contribute to the code by emailing {admin}.' values={{ domain, fork: <a href='https://en.wikipedia.org/wiki/Fork_(software_development)' target='_blank' rel='noopener'>fork</a>, - Mastodon: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>Mastodon</a>, + Mastodon: <a href='https://github.com/mastodon/mastodon' target='_blank' rel='noopener'>Mastodon</a>, Glitchsoc: <a href='https://github.com/glitch-soc/mastodon' target='_blank' rel='noopener'>GlitchSoc</a>, - github: <a href='https://git.starfall.systems/pluralcafe/mastodon/' rel='noopener noreferrer' target='_blank'>our Git repository</a>, + git: <a href='https://git.starfall.systems/pluralcafe/mastodon/' rel='noopener noreferrer' target='_blank'>our Git repository</a>, admin: <a href='mailto://admin@plural.cafe' rel='noopener noreferrer'>admin@plural.cafe</a> }} /> </p> diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js index dcbe94929..7b6a4a784 100644 --- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { submitReport } from 'flavours/glitch/actions/reports'; import { expandAccountTimeline } from 'flavours/glitch/actions/timelines'; -import { fetchRules } from 'flavours/glitch/actions/rules'; +import { fetchServer } from 'flavours/glitch/actions/server'; import { fetchRelationships } from 'flavours/glitch/actions/accounts'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -119,7 +119,7 @@ class ReportModal extends ImmutablePureComponent { dispatch(fetchRelationships([accountId])); dispatch(expandAccountTimeline(accountId, { withReplies: true })); - dispatch(fetchRules()); + dispatch(fetchServer()); } render () { diff --git a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js new file mode 100644 index 000000000..e8023803f --- /dev/null +++ b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.js @@ -0,0 +1,40 @@ +import React, { useCallback } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { useDispatch } from 'react-redux'; +import { registrationsOpen } from 'flavours/glitch/initial_state'; +import { openModal } from 'flavours/glitch/actions/modal'; + +const SignInBanner = () => { + const dispatch = useDispatch(); + + const openClosedRegistrationsModal = useCallback( + () => dispatch(openModal('CLOSED_REGISTRATIONS')), + [dispatch], + ); + + let signupButton; + + if (registrationsOpen) { + signupButton = ( + <a href='/auth/sign_up' className='button button--block button-tertiary'> + <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> + </a> + ); + } else { + signupButton = ( + <button className='button button--block button-tertiary' onClick={openClosedRegistrationsModal}> + <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> + </button> + ); + } + + return ( + <div className='sign-in-banner'> + <p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.' /></p> + <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> + {signupButton} + </div> + ); +}; + +export default SignInBanner; diff --git a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js b/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js deleted file mode 100644 index 55cc84f5e..000000000 --- a/app/javascript/flavours/glitch/features/ui/components/tabs_bar.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { NavLink, withRouter } from 'react-router-dom'; -import { FormattedMessage, injectIntl } from 'react-intl'; -import { debounce } from 'lodash'; -import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -import Icon from 'flavours/glitch/components/icon'; -import NotificationsCounterIcon from './notifications_counter_icon'; - -export const links = [ - <NavLink className='tabs-bar__link' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>, - <NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>, - <NavLink className='tabs-bar__link' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>, - <NavLink className='tabs-bar__link' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>, - <NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>, - <NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>, -]; - -export function getIndex (path) { - return links.findIndex(link => link.props.to === path); -} - -export function getLink (index) { - return links[index].props.to; -} - -export default @injectIntl -@withRouter -class TabsBar extends React.PureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - } - - setRef = ref => { - this.node = ref; - } - - handleClick = (e) => { - // Only apply optimization for touch devices, which we assume are slower - // We thus avoid the 250ms delay for non-touch devices and the lag for touch devices - if (isUserTouching()) { - e.preventDefault(); - e.persist(); - - requestAnimationFrame(() => { - const tabs = Array(...this.node.querySelectorAll('.tabs-bar__link')); - const currentTab = tabs.find(tab => tab.classList.contains('active')); - const nextTab = tabs.find(tab => tab.contains(e.target)); - const { props: { to } } = links[Array(...this.node.childNodes).indexOf(nextTab)]; - - - if (currentTab !== nextTab) { - if (currentTab) { - currentTab.classList.remove('active'); - } - - const listener = debounce(() => { - nextTab.removeEventListener('transitionend', listener); - this.props.history.push(to); - }, 50); - - nextTab.addEventListener('transitionend', listener); - nextTab.classList.add('active'); - } - }); - } - - } - - render () { - const { intl: { formatMessage } } = this.props; - - return ( - <div className='tabs-bar__wrapper'> - <nav className='tabs-bar' ref={this.setRef}> - {links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))} - </nav> - - <div id='tabs-bar__portal' /> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/ui/components/upload_area.js b/app/javascript/flavours/glitch/features/ui/components/upload_area.js index 11a10baf1..6958ba9df 100644 --- a/app/javascript/flavours/glitch/features/ui/components/upload_area.js +++ b/app/javascript/flavours/glitch/features/ui/components/upload_area.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from 'flavours/glitch/util/optional_motion'; +import Motion from '../../ui/util/optional_motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; diff --git a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js index abcbf13db..ddb991d54 100644 --- a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js +++ b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js @@ -4,10 +4,10 @@ import { scrollTopTimeline, loadPending } from 'flavours/glitch/actions/timeline import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { createSelector } from 'reselect'; import { debounce } from 'lodash'; -import { me } from 'flavours/glitch/util/initial_state'; +import { me } from 'flavours/glitch/initial_state'; const getRegex = createSelector([ - (state, { type }) => state.getIn(['settings', type, 'regex', 'body']), + (state, { regex }) => regex, ], (rawRegex) => { let regex = null; @@ -25,8 +25,6 @@ const makeGetStatusIds = (pending = false) => createSelector([ (state) => state.get('statuses'), getRegex, ], (columnSettings, statusIds, statuses, regex) => { - const rawRegex = columnSettings.getIn(['regex', 'body'], '').trim(); - return statusIds.filter(id => { if (id === null) return true; @@ -58,8 +56,8 @@ const makeMapStateToProps = () => { const getStatusIds = makeGetStatusIds(); const getPendingStatusIds = makeGetStatusIds(true); - const mapStateToProps = (state, { timelineId }) => ({ - statusIds: getStatusIds(state, { type: timelineId }), + const mapStateToProps = (state, { timelineId, regex }) => ({ + statusIds: getStatusIds(state, { type: timelineId, regex }), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: state.getIn(['timelines', timelineId, 'hasMore']), diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 2be6d9478..3d385eee2 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -4,16 +4,18 @@ import PropTypes from 'prop-types'; import LoadingBarContainer from './containers/loading_bar_container'; import ModalContainer from './containers/modal_container'; import { connect } from 'react-redux'; -import { Redirect, withRouter } from 'react-router-dom'; -import { isMobile } from 'flavours/glitch/util/is_mobile'; +import { Redirect, Route, withRouter } from 'react-router-dom'; +import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { debounce } from 'lodash'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose'; import { expandHomeTimeline } from 'flavours/glitch/actions/timelines'; import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications'; -import { fetchRules } from 'flavours/glitch/actions/rules'; +import { fetchServer } from 'flavours/glitch/actions/server'; import { clearHeight } from 'flavours/glitch/actions/height_cache'; +import { changeLayout } from 'flavours/glitch/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers'; -import { WrappedSwitch, WrappedRoute } from 'flavours/glitch/util/react_router_helpers'; +import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; +import BundleColumnError from './components/bundle_column_error'; import UploadArea from './components/upload_area'; import PermaLink from 'flavours/glitch/components/permalink'; import ColumnsAreaContainer from './containers/columns_area_container'; @@ -38,7 +40,6 @@ import { HashtagTimeline, Notifications, FollowRequests, - GenericNotFound, FavouritedStatuses, BookmarkedStatuses, ListTimeline, @@ -47,15 +48,19 @@ import { Mutes, PinnedStatuses, Lists, - Search, GettingStartedMisc, Directory, + Explore, FollowRecommendations, -} from 'flavours/glitch/util/async-components'; + About, + PrivacyPolicy, +} from './util/async-components'; import { HotKeys } from 'react-hotkeys'; -import { me } from 'flavours/glitch/util/initial_state'; +import initialState, { me, owner, singleUserMode, showTrends } from '../../initial_state'; import { closeOnboarding, INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding'; import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { Helmet } from 'react-helmet'; +import Header from './components/header'; // Dummy import, to make sure that <Status /> ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. @@ -66,10 +71,12 @@ const messages = defineMessages({ }); const mapStateToProps = state => ({ + layout: state.getIn(['meta', 'layout']), hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0, hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0, canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4, - layout: state.getIn(['local_settings', 'layout']), + layout: state.getIn(['meta', 'layout']), + layout_local_setting: state.getIn(['local_settings', 'layout']), isWide: state.getIn(['local_settings', 'stretch']), navbarUnder: state.getIn(['local_settings', 'navbar_under']), dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null, @@ -118,28 +125,19 @@ const keyMap = { class SwitchingColumnsArea extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object, + }; + static propTypes = { children: PropTypes.node, - layout: PropTypes.string, location: PropTypes.object, navbarUnder: PropTypes.bool, - onLayoutChange: PropTypes.func.isRequired, - }; - - state = { - mobile: isMobile(window.innerWidth, this.props.layout), + mobile: PropTypes.bool, }; - componentWillReceiveProps (nextProps) { - if (nextProps.layout !== this.props.layout) { - this.setState({ mobile: isMobile(window.innerWidth, nextProps.layout) }); - } - } - componentWillMount () { - window.addEventListener('resize', this.handleResize, { passive: true }); - - if (this.state.mobile) { + if (this.props.mobile) { document.body.classList.toggle('layout-single-column', true); document.body.classList.toggle('layout-multiple-columns', false); } else { @@ -148,57 +146,52 @@ class SwitchingColumnsArea extends React.PureComponent { } } - componentDidUpdate (prevProps, prevState) { + componentDidUpdate (prevProps) { if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) { this.node.handleChildrenContentChange(); } - if (prevState.mobile !== this.state.mobile) { - document.body.classList.toggle('layout-single-column', this.state.mobile); - document.body.classList.toggle('layout-multiple-columns', !this.state.mobile); - } - } - - componentWillUnmount () { - window.removeEventListener('resize', this.handleResize); - } - - handleLayoutChange = debounce(() => { - // The cached heights are no longer accurate, invalidate - this.props.onLayoutChange(); - }, 500, { - trailing: true, - }) - - handleResize = () => { - const mobile = isMobile(window.innerWidth, this.props.layout); - - if (mobile !== this.state.mobile) { - this.handleLayoutChange.cancel(); - this.props.onLayoutChange(); - this.setState({ mobile }); - } else { - this.handleLayoutChange(); + if (prevProps.mobile !== this.props.mobile) { + document.body.classList.toggle('layout-single-column', this.props.mobile); + document.body.classList.toggle('layout-multiple-columns', !this.props.mobile); } } setRef = c => { if (c) { - this.node = c.getWrappedInstance(); + this.node = c; } } render () { - const { children, navbarUnder } = this.props; - const singleColumn = this.state.mobile; - const redirect = singleColumn ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />; + const { children, mobile, navbarUnder } = this.props; + const { signedIn } = this.context.identity; + + let redirect; + + if (signedIn) { + if (mobile) { + redirect = <Redirect from='/' to='/home' exact />; + } else { + redirect = <Redirect from='/' to='/getting-started' exact />; + } + } else if (singleUserMode && owner && initialState?.accounts[owner]) { + redirect = <Redirect from='/' to={`/@${initialState.accounts[owner].username}`} exact />; + } else if (showTrends) { + redirect = <Redirect from='/' to='/explore' exact />; + } else { + redirect = <Redirect from='/' to='/about' exact />; + } return ( - <ColumnsAreaContainer ref={this.setRef} singleColumn={singleColumn} navbarUnder={navbarUnder}> + <ColumnsAreaContainer ref={this.setRef} singleColumn={mobile} navbarUnder={navbarUnder}> <WrappedSwitch> {redirect} + <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> <WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} /> + <WrappedRoute path='/about' component={About} content={children} /> + <WrappedRoute path='/privacy-policy' component={PrivacyPolicy} content={children} /> <WrappedRoute path={['/home', '/timelines/home']} component={HomeTimeline} content={children} /> <WrappedRoute path={['/public', '/timelines/public']} exact component={PublicTimeline} content={children} /> @@ -213,14 +206,15 @@ class SwitchingColumnsArea extends React.PureComponent { <WrappedRoute path='/pinned' component={PinnedStatuses} content={children} /> <WrappedRoute path='/start' component={FollowRecommendations} content={children} /> - <WrappedRoute path='/search' component={Search} content={children} /> <WrappedRoute path='/directory' component={Directory} content={children} /> + <WrappedRoute path={['/explore', '/search']} component={Explore} content={children} /> <WrappedRoute path={['/publish', '/statuses/new']} component={Compose} content={children} /> <WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} /> + <WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} /> <WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} /> - <WrappedRoute path={['/@:acct/followers', '/accounts/:id/followers']} component={Followers} content={children} /> - <WrappedRoute path={['/@:acct/following', '/accounts/:id/following']} component={Following} content={children} /> + <WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} /> + <WrappedRoute path={['/accounts/:id/following', '/users/:acct/following', '/@:acct/following']} component={Following} content={children} /> <WrappedRoute path={['/@:acct/media', '/accounts/:id/media']} component={AccountGallery} content={children} /> <WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} /> <WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} /> @@ -240,7 +234,7 @@ class SwitchingColumnsArea extends React.PureComponent { <WrappedRoute path='/lists' component={Lists} content={children} /> <WrappedRoute path='/getting-started-misc' component={GettingStartedMisc} content={children} /> - <WrappedRoute component={GenericNotFound} content={children} /> + <Route component={BundleColumnError} /> </WrappedSwitch> </ColumnsAreaContainer> ); @@ -253,10 +247,14 @@ export default @connect(mapStateToProps) @withRouter class UI extends React.Component { + static contextTypes = { + identity: PropTypes.object.isRequired, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, children: PropTypes.node, - layout: PropTypes.string, + layout_local_setting: PropTypes.string, isWide: PropTypes.bool, systemFontUi: PropTypes.bool, navbarUnder: PropTypes.bool, @@ -272,6 +270,7 @@ class UI extends React.Component { unreadNotifications: PropTypes.number, showFaviconBadge: PropTypes.bool, moved: PropTypes.map, + layout: PropTypes.string.isRequired, firstLaunch: PropTypes.bool, username: PropTypes.string, }; @@ -293,11 +292,6 @@ class UI extends React.Component { } } - handleLayoutChange = () => { - // The cached heights are no longer accurate, invalidate - this.props.dispatch(clearHeight()); - } - handleDragEnter = (e) => { e.preventDefault(); @@ -378,8 +372,29 @@ class UI extends React.Component { } } - componentWillMount () { + handleLayoutChange = debounce(() => { + this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate + }, 500, { + trailing: true, + }); + + handleResize = () => { + const layout = layoutFromWindow(this.props.layout_local_setting); + + if (layout !== this.props.layout) { + this.handleLayoutChange.cancel(); + this.props.dispatch(changeLayout(layout)); + } else { + this.handleLayoutChange(); + } + } + + componentDidMount () { + const { signedIn } = this.context.identity; + window.addEventListener('beforeunload', this.handleBeforeUnload, false); + window.addEventListener('resize', this.handleResize, { passive: true }); + document.addEventListener('dragenter', this.handleDragEnter, false); document.addEventListener('dragover', this.handleDragOver, false); document.addEventListener('drop', this.handleDrop, false); @@ -393,19 +408,19 @@ class UI extends React.Component { this.favicon = new Favico({ animation:"none" }); // On first launch, redirect to the follow recommendations page - if (this.props.firstLaunch) { + if (signedIn && this.props.firstLaunch) { this.context.router.history.replace('/start'); this.props.dispatch(closeOnboarding()); } - this.props.dispatch(fetchMarkers()); - this.props.dispatch(expandHomeTimeline()); - this.props.dispatch(expandNotifications()); + if (signedIn) { + this.props.dispatch(fetchMarkers()); + this.props.dispatch(expandHomeTimeline()); + this.props.dispatch(expandNotifications()); - setTimeout(() => this.props.dispatch(fetchRules()), 3000); - } + setTimeout(() => this.props.dispatch(fetchServer()), 3000); + } - componentDidMount () { this.hotkeys.__mousetrap__.stopCallback = (e, element) => { return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName); }; @@ -427,6 +442,19 @@ class UI extends React.Component { } } + componentWillReceiveProps (nextProps) { + if (nextProps.layout_local_setting !== this.props.layout_local_setting) { + const layout = layoutFromWindow(nextProps.layout_local_setting); + + if (layout !== this.props.layout) { + this.handleLayoutChange.cancel(); + this.props.dispatch(changeLayout(layout)); + } else { + this.handleLayoutChange(); + } + } + } + componentDidUpdate (prevProps) { if (this.props.unreadNotifications != prevProps.unreadNotifications || this.props.showFaviconBadge != prevProps.showFaviconBadge) { @@ -446,6 +474,8 @@ class UI extends React.Component { } window.removeEventListener('beforeunload', this.handleBeforeUnload); + window.removeEventListener('resize', this.handleResize); + document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragover', this.handleDragOver); document.removeEventListener('drop', this.handleDrop); @@ -576,7 +606,7 @@ class UI extends React.Component { render () { const { draggingOver } = this.state; - const { children, layout, isWide, navbarUnder, location, dropdownMenuIsOpen, moved } = this.props; + const { children, isWide, navbarUnder, location, dropdownMenuIsOpen, layout, moved } = this.props; const columnsClass = layout => { switch (layout) { @@ -632,11 +662,14 @@ class UI extends React.Component { )}} /> </div>)} - <SwitchingColumnsArea location={location} layout={layout} navbarUnder={navbarUnder} onLayoutChange={this.handleLayoutChange}> + + <Header /> + + <SwitchingColumnsArea location={location} mobile={layout === 'mobile' || layout === 'single-column'} navbarUnder={navbarUnder}> {children} </SwitchingColumnsArea> - <PictureInPicture /> + {layout !== 'mobile' && <PictureInPicture />} <NotificationsContainer /> <LoadingBarContainer className='loading-bar' /> <ModalContainer /> diff --git a/app/javascript/flavours/glitch/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index 86bb7be36..025b22e61 100644 --- a/app/javascript/flavours/glitch/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -1,5 +1,5 @@ export function EmojiPicker () { - return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/util/emoji/emoji_picker'); + return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'flavours/glitch/features/emoji/emoji_picker'); } export function Compose () { @@ -158,10 +158,6 @@ export function ListAdder () { return import(/* webpackChunkName: "features/glitch/async/list_adder" */'flavours/glitch/features/list_adder'); } -export function Search () { - return import(/*webpackChunkName: "features/glitch/async/search" */'flavours/glitch/features/search'); -} - export function Tesseract () { return import(/*webpackChunkName: "tesseract" */'tesseract.js'); } @@ -181,3 +177,27 @@ export function CompareHistoryModal () { export function FilterModal () { return import(/*webpackChunkName: "flavours/glitch/async/filter_modal" */'flavours/glitch/features/ui/components/filter_modal'); } + +export function Explore () { + return import(/* webpackChunkName: "flavours/glitch/async/explore" */'flavours/glitch/features/explore'); +} + +export function InteractionModal () { + return import(/*webpackChunkName: "flavours/glitch/async/modals/interaction_modal" */'flavours/glitch/features/interaction_modal'); +} + +export function SubscribedLanguagesModal () { + return import(/*webpackChunkName: "flavours/glitch/async/modals/subscribed_languages_modal" */'flavours/glitch/features/subscribed_languages_modal'); +} + +export function ClosedRegistrationsModal () { + return import(/*webpackChunkName: "flavours/glitch/async/modals/closed_registrations_modal" */'flavours/glitch/features/closed_registrations_modal'); +} + +export function About () { + return import(/*webpackChunkName: "features/glitch/async/about" */'flavours/glitch/features/about'); +} + +export function PrivacyPolicy () { + return import(/*webpackChunkName: "features/glitch/async/privacy_policy" */'flavours/glitch/features/privacy_policy'); +} diff --git a/app/javascript/flavours/glitch/util/fullscreen.js b/app/javascript/flavours/glitch/features/ui/util/fullscreen.js index cf5d0cf98..cf5d0cf98 100644 --- a/app/javascript/flavours/glitch/util/fullscreen.js +++ b/app/javascript/flavours/glitch/features/ui/util/fullscreen.js diff --git a/app/javascript/flavours/glitch/util/get_rect_from_entry.js b/app/javascript/flavours/glitch/features/ui/util/get_rect_from_entry.js index c266cd7dc..c266cd7dc 100644 --- a/app/javascript/flavours/glitch/util/get_rect_from_entry.js +++ b/app/javascript/flavours/glitch/features/ui/util/get_rect_from_entry.js diff --git a/app/javascript/flavours/glitch/util/intersection_observer_wrapper.js b/app/javascript/flavours/glitch/features/ui/util/intersection_observer_wrapper.js index 2b24c6583..2b24c6583 100644 --- a/app/javascript/flavours/glitch/util/intersection_observer_wrapper.js +++ b/app/javascript/flavours/glitch/features/ui/util/intersection_observer_wrapper.js diff --git a/app/javascript/flavours/glitch/util/optional_motion.js b/app/javascript/flavours/glitch/features/ui/util/optional_motion.js index eecb6634e..a7fbe6310 100644 --- a/app/javascript/flavours/glitch/util/optional_motion.js +++ b/app/javascript/flavours/glitch/features/ui/util/optional_motion.js @@ -1,4 +1,4 @@ -import { reduceMotion } from 'flavours/glitch/util/initial_state'; +import { reduceMotion } from 'flavours/glitch/initial_state'; import ReducedMotion from './reduced_motion'; import Motion from 'react-motion/lib/Motion'; diff --git a/app/javascript/flavours/glitch/util/react_router_helpers.js b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js index e36c512f3..8946c8252 100644 --- a/app/javascript/flavours/glitch/util/react_router_helpers.js +++ b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Switch, Route } from 'react-router-dom'; - +import StackTrace from 'stacktrace-js'; import ColumnLoading from 'flavours/glitch/features/ui/components/column_loading'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; import BundleContainer from 'flavours/glitch/features/ui/containers/bundle_container'; @@ -42,8 +42,38 @@ export class WrappedRoute extends React.Component { componentParams: {}, }; + static getDerivedStateFromError () { + return { + hasError: true, + }; + }; + + state = { + hasError: false, + stacktrace: '', + }; + + componentDidCatch (error) { + StackTrace.fromError(error).then(stackframes => { + this.setState({ stacktrace: error.toString() + '\n' + stackframes.map(frame => frame.toString()).join('\n') }); + }).catch(err => { + console.error(err); + }); + } + renderComponent = ({ match }) => { const { component, content, multiColumn, componentParams } = this.props; + const { hasError, stacktrace } = this.state; + + if (hasError) { + return ( + <BundleColumnError + stacktrace={stacktrace} + multiColumn={multiColumn} + errorType='error' + /> + ); + } return ( <BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> @@ -53,11 +83,13 @@ export class WrappedRoute extends React.Component { } renderLoading = () => { - return <ColumnLoading />; + const { multiColumn } = this.props; + + return <ColumnLoading multiColumn={multiColumn} />; } renderError = (props) => { - return <BundleColumnError {...props} />; + return <BundleColumnError {...props} errorType='network' />; } render () { diff --git a/app/javascript/flavours/glitch/util/reduced_motion.js b/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js index 95519042b..95519042b 100644 --- a/app/javascript/flavours/glitch/util/reduced_motion.js +++ b/app/javascript/flavours/glitch/features/ui/util/reduced_motion.js diff --git a/app/javascript/flavours/glitch/util/schedule_idle_task.js b/app/javascript/flavours/glitch/features/ui/util/schedule_idle_task.js index b04d4a8ee..b04d4a8ee 100644 --- a/app/javascript/flavours/glitch/util/schedule_idle_task.js +++ b/app/javascript/flavours/glitch/features/ui/util/schedule_idle_task.js diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js index 25c94bb2c..cb4655f7f 100644 --- a/app/javascript/flavours/glitch/features/video/index.js +++ b/app/javascript/flavours/glitch/features/video/index.js @@ -4,8 +4,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { is } from 'immutable'; import { throttle, debounce } from 'lodash'; import classNames from 'classnames'; -import { isFullscreen, requestFullscreen, exitFullscreen } from 'flavours/glitch/util/fullscreen'; -import { displayMedia, useBlurhash } from 'flavours/glitch/util/initial_state'; +import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; +import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state'; import Icon from 'flavours/glitch/components/icon'; import Blurhash from 'flavours/glitch/components/blurhash'; diff --git a/app/javascript/flavours/glitch/initial_state.js b/app/javascript/flavours/glitch/initial_state.js new file mode 100644 index 000000000..5be177ced --- /dev/null +++ b/app/javascript/flavours/glitch/initial_state.js @@ -0,0 +1,149 @@ +// @ts-check + +/** + * @typedef Emoji + * @property {string} shortcode + * @property {string} static_url + * @property {string} url + */ + +/** + * @typedef AccountField + * @property {string} name + * @property {string} value + * @property {string} verified_at + */ + +/** + * @typedef Account + * @property {string} acct + * @property {string} avatar + * @property {string} avatar_static + * @property {boolean} bot + * @property {string} created_at + * @property {boolean=} discoverable + * @property {string} display_name + * @property {Emoji[]} emojis + * @property {AccountField[]} fields + * @property {number} followers_count + * @property {number} following_count + * @property {boolean} group + * @property {string} header + * @property {string} header_static + * @property {string} id + * @property {string=} last_status_at + * @property {boolean} locked + * @property {string} note + * @property {number} statuses_count + * @property {string} url + * @property {string} username + */ + +/** + * @typedef {[code: string, name: string, localName: string]} InitialStateLanguage + */ + +/** + * @typedef InitialStateMeta + * @property {string} access_token + * @property {boolean=} advanced_layout + * @property {boolean} auto_play_gif + * @property {boolean} activity_api_enabled + * @property {string} admin + * @property {boolean=} boost_modal + * @property {boolean} crop_images + * @property {boolean=} delete_modal + * @property {boolean=} disable_swiping + * @property {string=} disabled_account_id + * @property {boolean} display_media + * @property {string} domain + * @property {boolean=} expand_spoilers + * @property {boolean} limited_federation_mode + * @property {string} locale + * @property {string | null} mascot + * @property {string=} me + * @property {string=} moved_to_account_id + * @property {string=} owner + * @property {boolean} profile_directory + * @property {boolean} registrations_open + * @property {boolean} reduce_motion + * @property {string} repository + * @property {boolean} search_enabled + * @property {boolean} single_user_mode + * @property {string} source_url + * @property {string} streaming_api_base_url + * @property {boolean} timeline_preview + * @property {string} title + * @property {boolean} trends + * @property {boolean} unfollow_modal + * @property {boolean} use_blurhash + * @property {boolean=} use_pending_items + * @property {string} version + * @property {object} local_settings + */ + +/** + * @typedef InitialState + * @property {Record<string, Account>} accounts + * @property {InitialStateLanguage[]} languages + * @property {InitialStateMeta} meta + */ + +const element = document.getElementById('initial-state'); +/** @type {InitialState | undefined} */ +const initialState = element?.textContent && JSON.parse(element.textContent); + +// Glitch-soc-specific โlocal settingsโ +try { + initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); +} catch (e) { + initialState.local_settings = {}; +} + +/** + * @template {keyof InitialStateMeta} K + * @param {K} prop + * @returns {InitialStateMeta[K] | undefined} + */ +const getMeta = (prop) => initialState?.meta && initialState.meta[prop]; + +export const activityApiEnabled = getMeta('activity_api_enabled'); +export const autoPlayGif = getMeta('auto_play_gif'); +export const boostModal = getMeta('boost_modal'); +export const cropImages = getMeta('crop_images'); +export const deleteModal = getMeta('delete_modal'); +export const disableSwiping = getMeta('disable_swiping'); +export const disabledAccountId = getMeta('disabled_account_id'); +export const displayMedia = getMeta('display_media'); +export const domain = getMeta('domain'); +export const expandSpoilers = getMeta('expand_spoilers'); +export const forceSingleColumn = !getMeta('advanced_layout'); +export const limitedFederationMode = getMeta('limited_federation_mode'); +export const mascot = getMeta('mascot'); +export const me = getMeta('me'); +export const movedToAccountId = getMeta('moved_to_account_id'); +export const owner = getMeta('owner'); +export const profile_directory = getMeta('profile_directory'); +export const reduceMotion = getMeta('reduce_motion'); +export const registrationsOpen = getMeta('registrations_open'); +export const repository = getMeta('repository'); +export const searchEnabled = getMeta('search_enabled'); +export const showTrends = getMeta('trends'); +export const singleUserMode = getMeta('single_user_mode'); +export const source_url = getMeta('source_url'); +export const timelinePreview = getMeta('timeline_preview'); +export const title = getMeta('title'); +export const unfollowModal = getMeta('unfollow_modal'); +export const useBlurhash = getMeta('use_blurhash'); +export const usePendingItems = getMeta('use_pending_items'); +export const version = getMeta('version'); +export const languages = initialState?.languages; + +// Glitch-soc-specific settings +export const maxChars = (initialState && initialState.max_toot_chars) || 500; +export const favouriteModal = getMeta('favourite_modal'); +export const pollLimits = (initialState && initialState.poll_limits); +export const defaultContentType = getMeta('default_content_type'); +export const useSystemEmojiFont = getMeta('system_emoji_font'); + +export default initialState; diff --git a/app/javascript/flavours/glitch/is_mobile.js b/app/javascript/flavours/glitch/is_mobile.js new file mode 100644 index 000000000..31944d89b --- /dev/null +++ b/app/javascript/flavours/glitch/is_mobile.js @@ -0,0 +1,55 @@ +// @ts-check + +import { supportsPassiveEvents } from 'detect-passive-events'; +import { forceSingleColumn } from 'flavours/glitch/initial_state'; + +const LAYOUT_BREAKPOINT = 630; + +/** + * @param {number} width + * @returns {boolean} + */ +export const isMobile = width => width <= LAYOUT_BREAKPOINT; + +/** + * @param {string} layout_local_setting + * @returns {string} + */ +export const layoutFromWindow = (layout_local_setting) => { + switch (layout_local_setting) { + case 'multiple': + return 'multi-column'; + case 'single': + if (isMobile(window.innerWidth)) { + return 'mobile'; + } else { + return 'single-column'; + } + default: + if (isMobile(window.innerWidth)) { + return 'mobile'; + } else if (forceSingleColumn) { + return 'single-column'; + } else { + return 'multi-column'; + } + } +}; + +const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; + +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; + +let userTouching = false; + +const touchListener = () => { + userTouching = true; + + window.removeEventListener('touchstart', touchListener, listenerOptions); +}; + +window.addEventListener('touchstart', touchListener, listenerOptions); + +export const isUserTouching = () => userTouching; + +export const isIOS = () => iOS; diff --git a/app/javascript/flavours/glitch/util/load_keyboard_extensions.js b/app/javascript/flavours/glitch/load_keyboard_extensions.js index 2dd0e45fa..2dd0e45fa 100644 --- a/app/javascript/flavours/glitch/util/load_keyboard_extensions.js +++ b/app/javascript/flavours/glitch/load_keyboard_extensions.js diff --git a/app/javascript/flavours/glitch/util/load_polyfills.js b/app/javascript/flavours/glitch/load_polyfills.js index 73eedc9dc..cc5bcd18f 100644 --- a/app/javascript/flavours/glitch/util/load_polyfills.js +++ b/app/javascript/flavours/glitch/load_polyfills.js @@ -26,6 +26,7 @@ function loadPolyfills() { // Edge does not have requestIdleCallback and object-fit CSS property. // This avoids shipping them all the polyfills. const needsExtraPolyfills = !( + window.AbortController && window.IntersectionObserver && window.IntersectionObserverEntry && 'isIntersecting' in IntersectionObserverEntry.prototype && diff --git a/app/javascript/flavours/glitch/locales/es.js b/app/javascript/flavours/glitch/locales/es.js index 086873881..f22062977 100644 --- a/app/javascript/flavours/glitch/locales/es.js +++ b/app/javascript/flavours/glitch/locales/es.js @@ -93,7 +93,7 @@ const messages = { 'settings.side_arm_reply_mode.copy': 'Copiar opciรณn de privacidad del toot al que estรกs respondiendo', 'settings.side_arm_reply_mode.keep': 'Conservar opciรณn de privacidad', 'settings.side_arm_reply_mode.restrict': 'Restringir la opciรณn de privacidad a la misma del toot al que estรกs respondiendo', - 'settings.side_arm_reply_mode': 'Al responder a un toot:', + 'settings.side_arm_reply_mode': 'Al responder a un toot, el botรณn de toot secundario debe:', 'settings.side_arm.none': 'Ninguno', 'settings.side_arm': 'Botรณn secundario:', 'settings.swipe_to_change_columns': 'Permitir deslizar para cambiar columnas (Sรณlo en mรณvil)', diff --git a/app/javascript/flavours/glitch/locales/ja.js b/app/javascript/flavours/glitch/locales/ja.js index 0ca5e5fc7..52aeed3d6 100644 --- a/app/javascript/flavours/glitch/locales/ja.js +++ b/app/javascript/flavours/glitch/locales/ja.js @@ -8,6 +8,7 @@ const messages = { 'layout.single': 'ใขใใคใซ', 'navigation_bar.app_settings': 'ใขใใช่จญๅฎ', 'navigation_bar.featured_users': '็ดนไปใใฆใใใขใซใฆใณใ', + 'navigation_bar.misc': 'ใใฎไป', 'getting_started.onboarding': '่งฃ่ชฌใ่กจ็คบ', 'onboarding.page_one.federation': '{domain}ใฏMastodonใฎใคใณในใฟใณในใงใใMastodonใจใฏใ็ฌ็ซใใใตใผใใ้ฃๆบใใฆไฝใใฝใผใทใฃใซใใใใฏใผใฏใงใใใใใใฎใตใผใใผใใคใณในใฟใณในใจๅผใณใพใใ', 'onboarding.page_one.welcome': '{domain}ใธใใใใ๏ผ', diff --git a/app/javascript/flavours/glitch/main.js b/app/javascript/flavours/glitch/main.js new file mode 100644 index 000000000..f1e10df34 --- /dev/null +++ b/app/javascript/flavours/glitch/main.js @@ -0,0 +1,49 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications'; +import Mastodon, { store } from 'flavours/glitch/containers/mastodon'; +import ready from 'flavours/glitch/ready'; + +const perf = require('flavours/glitch/performance'); + +/** + * @returns {Promise<void>} + */ +function main() { + perf.start('main()'); + + return ready(async () => { + const mountNode = document.getElementById('mastodon'); + const props = JSON.parse(mountNode.getAttribute('data-props')); + + ReactDOM.render(<Mastodon {...props} />, mountNode); + store.dispatch(setupBrowserNotifications()); + + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + const [{ Workbox }, { me }] = await Promise.all([ + import('workbox-window'), + import('flavours/glitch/initial_state'), + ]); + + const wb = new Workbox('/sw.js'); + + try { + await wb.register(); + } catch (err) { + console.error(err); + + return; + } + + if (me) { + const registerPushNotifications = await import('flavours/glitch/actions/push_notifications'); + + store.dispatch(registerPushNotifications.register()); + } + } + + perf.stop('main()'); + }); +} + +export default main; diff --git a/app/javascript/flavours/glitch/packs/about.js b/app/javascript/flavours/glitch/packs/about.js deleted file mode 100644 index 2e2cce501..000000000 --- a/app/javascript/flavours/glitch/packs/about.js +++ /dev/null @@ -1,23 +0,0 @@ -import 'packs/public-path'; -import loadPolyfills from 'flavours/glitch/util/load_polyfills'; - -function loaded() { - const TimelineContainer = require('flavours/glitch/containers/timeline_container').default; - const React = require('react'); - const ReactDOM = require('react-dom'); - const mountNode = document.getElementById('mastodon-timeline'); - - if (mountNode !== null) { - const props = JSON.parse(mountNode.getAttribute('data-props')); - ReactDOM.render(<TimelineContainer {...props} />, mountNode); - } -} - -function main() { - const ready = require('flavours/glitch/util/ready').default; - ready(loaded); -} - -loadPolyfills().then(main).catch(error => { - console.error(error); -}); diff --git a/app/javascript/flavours/glitch/packs/admin.js b/app/javascript/flavours/glitch/packs/admin.js index 4c09ddb05..56cdfc30a 100644 --- a/app/javascript/flavours/glitch/packs/admin.js +++ b/app/javascript/flavours/glitch/packs/admin.js @@ -1,5 +1,5 @@ import 'packs/public-path'; -import ready from 'flavours/glitch/util/ready'; +import ready from 'flavours/glitch/ready'; ready(() => { const React = require('react'); diff --git a/app/javascript/flavours/glitch/packs/error.js b/app/javascript/flavours/glitch/packs/error.js index 9f692ad37..f13e32149 100644 --- a/app/javascript/flavours/glitch/packs/error.js +++ b/app/javascript/flavours/glitch/packs/error.js @@ -1,5 +1,5 @@ import 'packs/public-path'; -import ready from 'flavours/glitch/util/ready'; +import ready from 'flavours/glitch/ready'; ready(() => { const image = document.querySelector('img'); diff --git a/app/javascript/flavours/glitch/packs/home.js b/app/javascript/flavours/glitch/packs/home.js index d06688985..ace9dc3c4 100644 --- a/app/javascript/flavours/glitch/packs/home.js +++ b/app/javascript/flavours/glitch/packs/home.js @@ -1,8 +1,10 @@ import 'packs/public-path'; -import loadPolyfills from 'flavours/glitch/util/load_polyfills'; +import loadPolyfills from 'flavours/glitch/load_polyfills'; -loadPolyfills().then(() => { - require('flavours/glitch/util/main').default(); +loadPolyfills().then(async () => { + const { default: main } = await import('flavours/glitch/main'); + + return main(); }).catch(e => { console.error(e); }); diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index 84ec9fce7..843fd5163 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -1,18 +1,17 @@ import 'packs/public-path'; -import loadPolyfills from 'flavours/glitch/util/load_polyfills'; -import ready from 'flavours/glitch/util/ready'; -import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extensions'; +import loadPolyfills from 'flavours/glitch/load_polyfills'; +import ready from 'flavours/glitch/ready'; +import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; function main() { const IntlMessageFormat = require('intl-messageformat').default; const { timeAgoString } = require('flavours/glitch/components/relative_timestamp'); const { delegate } = require('@rails/ujs'); - const emojify = require('flavours/glitch/util/emoji').default; + const emojify = require('flavours/glitch/features/emoji/emoji').default; const { getLocale } = require('locales'); const { messages } = getLocale(); const React = require('react'); const ReactDOM = require('react-dom'); - const Rellax = require('rellax'); const { createBrowserHistory } = require('history'); const scrollToDetailedStatus = () => { @@ -90,12 +89,6 @@ function main() { scrollToDetailedStatus(); } - const parallaxComponents = document.querySelectorAll('.parallax'); - - if (parallaxComponents.length > 0 ) { - new Rellax('.parallax', { speed: -1 }); - } - delegate(document, '#registration_user_password_confirmation,#registration_user_password', 'input', () => { const password = document.getElementById('registration_user_password'); const confirmation = document.getElementById('registration_user_password_confirmation'); @@ -146,8 +139,31 @@ function main() { }); }); + const toggleSidebar = () => { + const sidebar = document.querySelector('.sidebar ul'); + const toggleButton = document.querySelector('.sidebar__toggle__icon'); + + if (sidebar.classList.contains('visible')) { + document.body.style.overflow = null; + toggleButton.setAttribute('aria-expanded', false); + } else { + document.body.style.overflow = 'hidden'; + toggleButton.setAttribute('aria-expanded', true); + } + + toggleButton.classList.toggle('active'); + sidebar.classList.toggle('visible'); + }; + delegate(document, '.sidebar__toggle__icon', 'click', () => { - document.querySelector('.sidebar ul').classList.toggle('visible'); + toggleSidebar(); + }); + + delegate(document, '.sidebar__toggle__icon', 'keydown', e => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + toggleSidebar(); + } }); // Empty the honeypot fields in JS in case something like an extension diff --git a/app/javascript/flavours/glitch/packs/settings.js b/app/javascript/flavours/glitch/packs/settings.js index de88d4f52..31c88b2b5 100644 --- a/app/javascript/flavours/glitch/packs/settings.js +++ b/app/javascript/flavours/glitch/packs/settings.js @@ -1,14 +1,37 @@ import 'packs/public-path'; -import loadPolyfills from 'flavours/glitch/util/load_polyfills'; -import ready from 'flavours/glitch/util/ready'; -import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extensions'; +import loadPolyfills from 'flavours/glitch/load_polyfills'; +import ready from 'flavours/glitch/ready'; +import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; import 'cocoon-js-vanilla'; function main() { const { delegate } = require('@rails/ujs'); + const toggleSidebar = () => { + const sidebar = document.querySelector('.sidebar ul'); + const toggleButton = document.querySelector('.sidebar__toggle__icon'); + + if (sidebar.classList.contains('visible')) { + document.body.style.overflow = null; + toggleButton.setAttribute('aria-expanded', false); + } else { + document.body.style.overflow = 'hidden'; + toggleButton.setAttribute('aria-expanded', true); + } + + toggleButton.classList.toggle('active'); + sidebar.classList.toggle('visible'); + }; + delegate(document, '.sidebar__toggle__icon', 'click', () => { - document.querySelector('.sidebar ul').classList.toggle('visible'); + toggleSidebar(); + }); + + delegate(document, '.sidebar__toggle__icon', 'keydown', e => { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + toggleSidebar(); + } }); } diff --git a/app/javascript/flavours/glitch/packs/share.js b/app/javascript/flavours/glitch/packs/share.js index f4a97e201..e5a79849a 100644 --- a/app/javascript/flavours/glitch/packs/share.js +++ b/app/javascript/flavours/glitch/packs/share.js @@ -1,5 +1,5 @@ import 'packs/public-path'; -import loadPolyfills from 'flavours/glitch/util/load_polyfills'; +import loadPolyfills from 'flavours/glitch/load_polyfills'; function loaded() { const ComposeContainer = require('flavours/glitch/containers/compose_container').default; @@ -14,7 +14,7 @@ function loaded() { } function main() { - const ready = require('flavours/glitch/util/ready').default; + const ready = require('flavours/glitch/ready').default; ready(loaded); } diff --git a/app/javascript/flavours/glitch/util/performance.js b/app/javascript/flavours/glitch/performance.js index 450a90626..450a90626 100644 --- a/app/javascript/flavours/glitch/util/performance.js +++ b/app/javascript/flavours/glitch/performance.js diff --git a/app/javascript/flavours/glitch/ready.js b/app/javascript/flavours/glitch/ready.js new file mode 100644 index 000000000..e769cc756 --- /dev/null +++ b/app/javascript/flavours/glitch/ready.js @@ -0,0 +1,32 @@ +// @ts-check + +/** + * @param {(() => void) | (() => Promise<void>)} callback + * @returns {Promise<void>} + */ +export default function ready(callback) { + return new Promise((resolve, reject) => { + function loaded() { + let result; + try { + result = callback(); + } catch (err) { + reject(err); + + return; + } + + if (typeof result?.then === 'function') { + result.then(resolve).catch(reject); + } else { + resolve(); + } + } + + if (['interactive', 'complete'].includes(document.readyState)) { + loaded(); + } else { + document.addEventListener('DOMContentLoaded', loaded); + } + }); +} diff --git a/app/javascript/flavours/glitch/reducers/accounts_map.js b/app/javascript/flavours/glitch/reducers/accounts_map.js index e0d42e9cd..53e08c8fb 100644 --- a/app/javascript/flavours/glitch/reducers/accounts_map.js +++ b/app/javascript/flavours/glitch/reducers/accounts_map.js @@ -1,14 +1,16 @@ import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; import { Map as ImmutableMap } from 'immutable'; +export const normalizeForLookup = str => str.toLowerCase(); + const initialState = ImmutableMap(); export default function accountsMap(state = initialState, action) { switch(action.type) { case ACCOUNT_IMPORT: - return state.set(action.account.acct, action.account.id); + return state.set(normalizeForLookup(action.account.acct), action.account.id); case ACCOUNTS_IMPORT: - return state.withMutations(map => action.accounts.forEach(account => map.set(account.acct, account.id))); + return state.withMutations(map => action.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id))); default: return state; } diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 2ef08b2a6..460af3955 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -15,6 +15,7 @@ import { COMPOSE_UPLOAD_FAIL, COMPOSE_UPLOAD_UNDO, COMPOSE_UPLOAD_PROGRESS, + COMPOSE_UPLOAD_PROCESSING, THUMBNAIL_UPLOAD_REQUEST, THUMBNAIL_UPLOAD_SUCCESS, THUMBNAIL_UPLOAD_FAIL, @@ -53,12 +54,12 @@ import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines'; import { STORE_HYDRATE } from 'flavours/glitch/actions/store'; import { REDRAFT } from 'flavours/glitch/actions/statuses'; import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; -import uuid from 'flavours/glitch/util/uuid'; -import { privacyPreference } from 'flavours/glitch/util/privacy_preference'; -import { me, defaultContentType } from 'flavours/glitch/util/initial_state'; -import { overwrite } from 'flavours/glitch/util/js_helpers'; -import { unescapeHTML } from 'flavours/glitch/util/html'; -import { recoverHashtags } from 'flavours/glitch/util/hashtag'; +import uuid from '../uuid'; +import { privacyPreference } from 'flavours/glitch/utils/privacy_preference'; +import { me, defaultContentType } from 'flavours/glitch/initial_state'; +import { overwrite } from 'flavours/glitch/utils/js_helpers'; +import { unescapeHTML } from 'flavours/glitch/utils/html'; +import { recoverHashtags } from 'flavours/glitch/utils/hashtag'; const totalElefriends = 3; @@ -223,6 +224,7 @@ function appendMedia(state, media, file) { } map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); + map.set('is_processing', false); map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); map.set('idempotencyKey', uuid()); map.update('pending_media_attachments', n => n - 1); @@ -465,10 +467,12 @@ export default function compose(state = initialState, action) { return state.set('is_changing_upload', false); case COMPOSE_UPLOAD_REQUEST: return state.set('is_uploading', true).update('pending_media_attachments', n => n + 1); + case COMPOSE_UPLOAD_PROCESSING: + return state.set('is_processing', true); case COMPOSE_UPLOAD_SUCCESS: return appendMedia(state, fromJS(action.media), action.file); case COMPOSE_UPLOAD_FAIL: - return state.set('is_uploading', false).update('pending_media_attachments', n => n - 1); + return state.set('is_uploading', false).set('is_processing', false).update('pending_media_attachments', n => n - 1); case COMPOSE_UPLOAD_UNDO: return removeMedia(state, action.media_id); case COMPOSE_UPLOAD_PROGRESS: @@ -569,6 +573,7 @@ export default function compose(state = initialState, action) { 'advanced_options', map => map.merge(new ImmutableMap({ do_not_federate })) ); + map.set('id', null); if (action.status.get('spoiler_text').length > 0) { map.set('spoiler', true); diff --git a/app/javascript/flavours/glitch/reducers/contexts.js b/app/javascript/flavours/glitch/reducers/contexts.js index 73b25fe3f..a0fcc4158 100644 --- a/app/javascript/flavours/glitch/reducers/contexts.js +++ b/app/javascript/flavours/glitch/reducers/contexts.js @@ -5,7 +5,7 @@ import { import { CONTEXT_FETCH_SUCCESS } from 'flavours/glitch/actions/statuses'; import { TIMELINE_DELETE, TIMELINE_UPDATE } from 'flavours/glitch/actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import compareId from 'flavours/glitch/util/compare_id'; +import compareId from '../compare_id'; const initialState = ImmutableMap({ inReplyTos: ImmutableMap(), diff --git a/app/javascript/flavours/glitch/reducers/conversations.js b/app/javascript/flavours/glitch/reducers/conversations.js index fba0308bc..4407dcf04 100644 --- a/app/javascript/flavours/glitch/reducers/conversations.js +++ b/app/javascript/flavours/glitch/reducers/conversations.js @@ -11,7 +11,7 @@ import { } from '../actions/conversations'; import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'flavours/glitch/actions/accounts'; import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks'; -import compareId from 'flavours/glitch/util/compare_id'; +import compareId from '../compare_id'; const initialState = ImmutableMap({ items: ImmutableList(), diff --git a/app/javascript/flavours/glitch/reducers/custom_emojis.js b/app/javascript/flavours/glitch/reducers/custom_emojis.js index 90e3040a4..f490d0db1 100644 --- a/app/javascript/flavours/glitch/reducers/custom_emojis.js +++ b/app/javascript/flavours/glitch/reducers/custom_emojis.js @@ -1,7 +1,7 @@ import { List as ImmutableList, fromJS as ConvertToImmutable } from 'immutable'; import { CUSTOM_EMOJIS_FETCH_SUCCESS } from 'flavours/glitch/actions/custom_emojis'; -import { search as emojiSearch } from 'flavours/glitch/util/emoji/emoji_mart_search_light'; -import { buildCustomEmojis } from 'flavours/glitch/util/emoji'; +import { search as emojiSearch } from 'flavours/glitch/features/emoji/emoji_mart_search_light'; +import { buildCustomEmojis } from 'flavours/glitch/features/emoji/emoji'; const initialState = ImmutableList([]); diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index 991b4aa79..09c08a362 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -17,7 +17,7 @@ import push_notifications from './push_notifications'; import status_lists from './status_lists'; import mutes from './mutes'; import blocks from './blocks'; -import rules from './rules'; +import server from './server'; import boosts from './boosts'; import contexts from './contexts'; import compose from './compose'; @@ -64,7 +64,7 @@ const reducers = { push_notifications, mutes, blocks, - rules, + server, boosts, contexts, compose, diff --git a/app/javascript/flavours/glitch/reducers/meta.js b/app/javascript/flavours/glitch/reducers/meta.js index 0f3ab3b84..b1482777a 100644 --- a/app/javascript/flavours/glitch/reducers/meta.js +++ b/app/javascript/flavours/glitch/reducers/meta.js @@ -1,16 +1,25 @@ import { STORE_HYDRATE } from 'flavours/glitch/actions/store'; +import { APP_LAYOUT_CHANGE } from 'flavours/glitch/actions/app'; import { Map as ImmutableMap } from 'immutable'; +import { layoutFromWindow } from 'flavours/glitch/is_mobile'; const initialState = ImmutableMap({ streaming_api_base_url: null, access_token: null, + layout: layoutFromWindow(), permissions: '0', }); export default function meta(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions'])); + return state.merge( + action.state.get('meta')) + .set('permissions', action.state.getIn(['role', 'permissions'])) + .set('layout', layoutFromWindow(action.state.getIn(['local_settings', 'layout'])) + ); + case APP_LAYOUT_CHANGE: + return state.set('layout', action.layout); default: return state; } diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 51d7886d7..18610e758 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -32,7 +32,7 @@ import { import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks'; import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines'; import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import compareId from 'flavours/glitch/util/compare_id'; +import compareId from '../compare_id'; const initialState = ImmutableMap({ pendingItems: ImmutableList(), @@ -52,20 +52,26 @@ const initialState = ImmutableMap({ markNewForDelete: false, }); -const notificationToMap = (state, notification) => ImmutableMap({ +const notificationToMap = (notification, markForDelete) => ImmutableMap({ id: notification.id, type: notification.type, account: notification.account.id, - markedForDelete: state.get('markNewForDelete'), + markedForDelete: markForDelete, status: notification.status ? notification.status.id : null, report: notification.report ? fromJS(notification.report) : null, }); const normalizeNotification = (state, notification, usePendingItems) => { + const markNewForDelete = state.get('markNewForDelete'); const top = state.get('top'); + // Under currently unknown conditions, the client may receive duplicates from the server + if (state.get('pendingItems').some((item) => item?.get('id') === notification.id) || state.get('items').some((item) => item?.get('id') === notification.id)) { + return state; + } + if (usePendingItems || !state.get('pendingItems').isEmpty()) { - return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))).update('unread', unread => unread + 1); + return state.update('pendingItems', list => list.unshift(notificationToMap(notification, markNewForDelete))).update('unread', unread => unread + 1); } if (shouldCountUnreadNotifications(state)) { @@ -79,32 +85,79 @@ const normalizeNotification = (state, notification, usePendingItems) => { list = list.take(20); } - return list.unshift(notificationToMap(state, notification)); + return list.unshift(notificationToMap(notification, markNewForDelete)); }); }; -const expandNormalizedNotifications = (state, notifications, next, isLoadingRecent, usePendingItems) => { - const lastReadId = state.get('lastReadId'); - let items = ImmutableList(); +const expandNormalizedNotifications = (state, notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) => { + // This method is pretty tricky because: + // - existing notifications might be out of order + // - the existing notifications may have gaps, most often explicitly noted with a `null` item + // - ideally, we don't want it to reorder existing items + // - `notifications` may include items that are already included + // - this function can be called either to fill in a gap, or load newer items - notifications.forEach((n, i) => { - items = items.set(i, notificationToMap(state, n)); - }); + const markNewForDelete = state.get('markNewForDelete'); + const lastReadId = state.get('lastReadId'); + const newItems = ImmutableList(notifications.map((notification) => notificationToMap(notification, markNewForDelete))); return state.withMutations(mutable => { - if (!items.isEmpty()) { + if (!newItems.isEmpty()) { usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('pendingItems').isEmpty()); - mutable.update(usePendingItems ? 'pendingItems' : 'items', list => { - const lastIndex = 1 + list.findLastIndex( - item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')), - ); - - const firstIndex = 1 + list.take(lastIndex).findLastIndex( - item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0, + mutable.update(usePendingItems ? 'pendingItems' : 'items', oldItems => { + // If called to poll *new* notifications, we just need to add them on top without duplicates + if (isLoadingRecent) { + const idsToCheck = oldItems.map(item => item?.get('id')).toSet(); + const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id'))); + return insertedItems.concat(oldItems); + } + + // If called to expand more (presumably older than any known to the WebUI), we just have to + // add them to the bottom without duplicates + if (isLoadingMore) { + const idsToCheck = oldItems.map(item => item?.get('id')).toSet(); + const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id'))); + return oldItems.concat(insertedItems); + } + + // Now this gets tricky, as we don't necessarily know for sure where the gap to fill is, + // and some items in the timeline may not be properly ordered. + + // However, we know that `newItems.last()` is the oldest item that was requested and that + // there is no โholeโ between `newItems.last()` and `newItems.first()`. + + // First, find the furthest (if properly sorted, oldest) item in the notifications that is + // newer than the oldest fetched one, as it's most likely that it delimits the gap. + // Start the gap *after* that item. + const lastIndex = oldItems.findLastIndex(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) >= 0) + 1; + + // Then, try to find the furthest (if properly sorted, oldest) item in the notifications that + // is newer than the most recent fetched one, as it delimits a section comprised of only + // items older or within `newItems` (or that were deleted from the server, so should be removed + // anyway). + // Stop the gap *after* that item. + const firstIndex = oldItems.take(lastIndex).findLastIndex(item => item !== null && compareId(item.get('id'), newItems.first().get('id')) > 0) + 1; + + // At this point: + // - no `oldItems` after `firstIndex` is newer than any of the `newItems` + // - all `oldItems` after `lastIndex` are older than every of the `newItems` + // - it is possible for items in the replaced slice to be older than every `newItems` + // - it is possible for items before `firstIndex` to be in the `newItems` range + // Therefore: + // - to avoid losing items, items from the replaced slice that are older than `newItems` + // should be added in the back. + // - to avoid duplicates, `newItems` should be checked the first `firstIndex` items of + // `oldItems` + const idsToCheck = oldItems.take(firstIndex).map(item => item?.get('id')).toSet(); + const insertedItems = newItems.filterNot(item => idsToCheck.includes(item.get('id'))); + const olderItems = oldItems.slice(firstIndex, lastIndex).filter(item => item !== null && compareId(item.get('id'), newItems.last().get('id')) < 0); + + return oldItems.take(firstIndex).concat( + insertedItems, + olderItems, + oldItems.skip(lastIndex), ); - - return list.take(firstIndex).concat(items, list.skip(lastIndex)); }); } @@ -115,7 +168,7 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece if (shouldCountUnreadNotifications(state)) { mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), lastReadId) > 0)); } else { - const mostRecent = items.find(item => item !== null); + const mostRecent = newItems.find(item => item !== null); if (mostRecent && compareId(lastReadId, mostRecent.get('id')) < 0) { mutable.set('lastReadId', mostRecent.get('id')); } @@ -162,7 +215,9 @@ const deleteByStatus = (state, statusId) => { const markForDelete = (state, notificationId, yes) => { return state.update('items', list => list.map(item => { - if(item.get('id') === notificationId) { + if (item === null) { + return null; + } else if(item.get('id') === notificationId) { return item.set('markedForDelete', yes); } else { return item; @@ -172,7 +227,9 @@ const markForDelete = (state, notificationId, yes) => { const markAllForDelete = (state, yes) => { return state.update('items', list => list.map(item => { - if(yes !== null) { + if (item === null) { + return null; + } else if(yes !== null) { return item.set('markedForDelete', yes); } else { return item.set('markedForDelete', !item.get('markedForDelete')); @@ -181,11 +238,11 @@ const markAllForDelete = (state, yes) => { }; const unmarkAllForDelete = (state) => { - return state.update('items', list => list.map(item => item.set('markedForDelete', false))); + return state.update('items', list => list.map(item => item === null ? item : item.set('markedForDelete', false))); }; const deleteMarkedNotifs = (state) => { - return state.update('items', list => list.filterNot(item => item.get('markedForDelete'))); + return state.update('items', list => list.filterNot(item => item === null ? item : item.get('markedForDelete'))); }; const updateMounted = (state) => { @@ -249,10 +306,10 @@ export default function notifications(state = initialState, action) { return state.update('items', list => state.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0); case NOTIFICATIONS_EXPAND_REQUEST: case NOTIFICATIONS_DELETE_MARKED_REQUEST: - return state.set('isLoading', (nbLoading) => nbLoading + 1); + return state.update('isLoading', (nbLoading) => nbLoading + 1); case NOTIFICATIONS_DELETE_MARKED_FAIL: case NOTIFICATIONS_EXPAND_FAIL: - return state.set('isLoading', (nbLoading) => nbLoading - 1); + return state.update('isLoading', (nbLoading) => nbLoading - 1); case NOTIFICATIONS_FILTER_SET: return state.set('items', ImmutableList()).set('hasMore', true); case NOTIFICATIONS_SCROLL_TOP: @@ -260,7 +317,7 @@ export default function notifications(state = initialState, action) { case NOTIFICATIONS_UPDATE: return normalizeNotification(state, action.notification, action.usePendingItems); case NOTIFICATIONS_EXPAND_SUCCESS: - return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingRecent, action.usePendingItems); + return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingMore, action.isLoadingRecent, action.usePendingItems); case ACCOUNT_BLOCK_SUCCESS: return filterNotifications(state, [action.relationship.id]); case ACCOUNT_MUTE_SUCCESS: @@ -287,7 +344,7 @@ export default function notifications(state = initialState, action) { return markForDelete(state, action.id, action.yes); case NOTIFICATIONS_DELETE_MARKED_SUCCESS: - return deleteMarkedNotifs(state).set('isLoading', (nbLoading) => nbLoading - 1); + return deleteMarkedNotifs(state).update('isLoading', (nbLoading) => nbLoading - 1); case NOTIFICATIONS_ENTER_CLEARING_MODE: st = state.set('cleaningMode', action.yes); diff --git a/app/javascript/flavours/glitch/reducers/rules.js b/app/javascript/flavours/glitch/reducers/rules.js deleted file mode 100644 index 6cc2230bc..000000000 --- a/app/javascript/flavours/glitch/reducers/rules.js +++ /dev/null @@ -1,13 +0,0 @@ -import { RULES_FETCH_SUCCESS } from 'flavours/glitch/actions/rules'; -import { List as ImmutableList, fromJS } from 'immutable'; - -const initialState = ImmutableList(); - -export default function rules(state = initialState, action) { - switch (action.type) { - case RULES_FETCH_SUCCESS: - return fromJS(action.rules); - default: - return state; - } -} diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js index c346e958b..4b8913e96 100644 --- a/app/javascript/flavours/glitch/reducers/search.js +++ b/app/javascript/flavours/glitch/reducers/search.js @@ -1,6 +1,8 @@ import { SEARCH_CHANGE, SEARCH_CLEAR, + SEARCH_FETCH_REQUEST, + SEARCH_FETCH_FAIL, SEARCH_FETCH_SUCCESS, SEARCH_SHOW, SEARCH_EXPAND_SUCCESS, @@ -17,6 +19,7 @@ const initialState = ImmutableMap({ submitted: false, hidden: false, results: ImmutableMap(), + isLoading: false, searchTerm: '', }); @@ -37,12 +40,24 @@ export default function search(state = initialState, action) { case COMPOSE_MENTION: case COMPOSE_DIRECT: return state.set('hidden', true); + case SEARCH_FETCH_REQUEST: + return state.withMutations(map => { + map.set('isLoading', true); + map.set('submitted', true); + }); + case SEARCH_FETCH_FAIL: + return state.set('isLoading', false); case SEARCH_FETCH_SUCCESS: - return state.set('results', ImmutableMap({ - accounts: ImmutableList(action.results.accounts.map(item => item.id)), - statuses: ImmutableList(action.results.statuses.map(item => item.id)), - hashtags: fromJS(action.results.hashtags), - })).set('submitted', true).set('searchTerm', action.searchTerm); + return state.withMutations(map => { + map.set('results', ImmutableMap({ + accounts: ImmutableList(action.results.accounts.map(item => item.id)), + statuses: ImmutableList(action.results.statuses.map(item => item.id)), + hashtags: fromJS(action.results.hashtags), + })); + + map.set('searchTerm', action.searchTerm); + map.set('isLoading', false); + }); case SEARCH_EXPAND_SUCCESS: const results = action.searchType === 'hashtags' ? fromJS(action.results.hashtags) : action.results[action.searchType].map(item => item.id); return state.updateIn(['results', action.searchType], list => list.concat(results)); diff --git a/app/javascript/flavours/glitch/reducers/server.js b/app/javascript/flavours/glitch/reducers/server.js new file mode 100644 index 000000000..cc5798fb3 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/server.js @@ -0,0 +1,53 @@ +import { + SERVER_FETCH_REQUEST, + SERVER_FETCH_SUCCESS, + SERVER_FETCH_FAIL, + EXTENDED_DESCRIPTION_REQUEST, + EXTENDED_DESCRIPTION_SUCCESS, + EXTENDED_DESCRIPTION_FAIL, + SERVER_DOMAIN_BLOCKS_FETCH_REQUEST, + SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS, + SERVER_DOMAIN_BLOCKS_FETCH_FAIL, +} from 'flavours/glitch/actions/server'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; + +const initialState = ImmutableMap({ + server: ImmutableMap({ + isLoading: true, + }), + + extendedDescription: ImmutableMap({ + isLoading: true, + }), + + domainBlocks: ImmutableMap({ + isLoading: true, + isAvailable: true, + items: ImmutableList(), + }), +}); + +export default function server(state = initialState, action) { + switch (action.type) { + case SERVER_FETCH_REQUEST: + return state.setIn(['server', 'isLoading'], true); + case SERVER_FETCH_SUCCESS: + return state.set('server', fromJS(action.server)).setIn(['server', 'isLoading'], false); + case SERVER_FETCH_FAIL: + return state.setIn(['server', 'isLoading'], false); + case EXTENDED_DESCRIPTION_REQUEST: + return state.setIn(['extendedDescription', 'isLoading'], true); + case EXTENDED_DESCRIPTION_SUCCESS: + return state.set('extendedDescription', fromJS(action.description)).setIn(['extendedDescription', 'isLoading'], false); + case EXTENDED_DESCRIPTION_FAIL: + return state.setIn(['extendedDescription', 'isLoading'], false); + case SERVER_DOMAIN_BLOCKS_FETCH_REQUEST: + return state.setIn(['domainBlocks', 'isLoading'], true); + case SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS: + return state.setIn(['domainBlocks', 'items'], fromJS(action.blocks)).setIn(['domainBlocks', 'isLoading'], false).setIn(['domainBlocks', 'isAvailable'], action.isAvailable); + case SERVER_DOMAIN_BLOCKS_FETCH_FAIL: + return state.setIn(['domainBlocks', 'isLoading'], false); + default: + return state; + } +} diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js index 1d99441a1..82927f7cd 100644 --- a/app/javascript/flavours/glitch/reducers/settings.js +++ b/app/javascript/flavours/glitch/reducers/settings.js @@ -6,7 +6,7 @@ import { EMOJI_USE } from 'flavours/glitch/actions/emojis'; import { LANGUAGE_USE } from 'flavours/glitch/actions/languages'; import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists'; import { Map as ImmutableMap, fromJS } from 'immutable'; -import uuid from 'flavours/glitch/util/uuid'; +import uuid from '../uuid'; const initialState = ImmutableMap({ saved: true, diff --git a/app/javascript/flavours/glitch/reducers/status_lists.js b/app/javascript/flavours/glitch/reducers/status_lists.js index 241833bfe..ada0484f4 100644 --- a/app/javascript/flavours/glitch/reducers/status_lists.js +++ b/app/javascript/flavours/glitch/reducers/status_lists.js @@ -17,6 +17,14 @@ import { import { PINNED_STATUSES_FETCH_SUCCESS, } from 'flavours/glitch/actions/pin_statuses'; +import { + TRENDS_STATUSES_FETCH_REQUEST, + TRENDS_STATUSES_FETCH_SUCCESS, + TRENDS_STATUSES_FETCH_FAIL, + TRENDS_STATUSES_EXPAND_REQUEST, + TRENDS_STATUSES_EXPAND_SUCCESS, + TRENDS_STATUSES_EXPAND_FAIL, +} from 'flavours/glitch/actions/trends'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { FAVOURITE_SUCCESS, @@ -26,6 +34,10 @@ import { PIN_SUCCESS, UNPIN_SUCCESS, } from 'flavours/glitch/actions/interactions'; +import { + ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS, +} from 'flavours/glitch/actions/accounts'; const initialState = ImmutableMap({ favourites: ImmutableMap({ @@ -43,6 +55,11 @@ const initialState = ImmutableMap({ loaded: false, items: ImmutableList(), }), + trending: ImmutableMap({ + next: null, + loaded: false, + items: ImmutableList(), + }), }); const normalizeList = (state, listType, statuses, next) => { @@ -96,6 +113,16 @@ export default function statusLists(state = initialState, action) { return normalizeList(state, 'bookmarks', action.statuses, action.next); case BOOKMARKED_STATUSES_EXPAND_SUCCESS: return appendToList(state, 'bookmarks', action.statuses, action.next); + case TRENDS_STATUSES_FETCH_REQUEST: + case TRENDS_STATUSES_EXPAND_REQUEST: + return state.setIn(['trending', 'isLoading'], true); + case TRENDS_STATUSES_FETCH_FAIL: + case TRENDS_STATUSES_EXPAND_FAIL: + return state.setIn(['trending', 'isLoading'], false); + case TRENDS_STATUSES_FETCH_SUCCESS: + return normalizeList(state, 'trending', action.statuses, action.next); + case TRENDS_STATUSES_EXPAND_SUCCESS: + return appendToList(state, 'trending', action.statuses, action.next); case FAVOURITE_SUCCESS: return prependOneToList(state, 'favourites', action.status); case UNFAVOURITE_SUCCESS: @@ -110,6 +137,9 @@ export default function statusLists(state = initialState, action) { return prependOneToList(state, 'pins', action.status); case UNPIN_SUCCESS: return removeOneFromList(state, 'pins', action.status); + case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: + return state.updateIn(['trending', 'items'], ImmutableList(), list => list.filterNot(statusId => action.statuses.getIn([statusId, 'account']) === action.relationship.id)); default: return state; } diff --git a/app/javascript/flavours/glitch/reducers/statuses.js b/app/javascript/flavours/glitch/reducers/statuses.js index 333e4b45c..b47155c5f 100644 --- a/app/javascript/flavours/glitch/reducers/statuses.js +++ b/app/javascript/flavours/glitch/reducers/statuses.js @@ -13,6 +13,8 @@ import { STATUS_REVEAL, STATUS_HIDE, STATUS_COLLAPSE, + STATUS_FETCH_REQUEST, + STATUS_FETCH_FAIL, } from 'flavours/glitch/actions/statuses'; import { TIMELINE_DELETE, @@ -37,6 +39,10 @@ const initialState = ImmutableMap(); export default function statuses(state = initialState, action) { switch(action.type) { + case STATUS_FETCH_REQUEST: + return state.setIn([action.id, 'isLoading'], true); + case STATUS_FETCH_FAIL: + return state.delete(action.id); case STATUS_IMPORT: return importStatus(state, action.status); case STATUSES_IMPORT: diff --git a/app/javascript/flavours/glitch/reducers/tags.js b/app/javascript/flavours/glitch/reducers/tags.js index d24098e39..266b21177 100644 --- a/app/javascript/flavours/glitch/reducers/tags.js +++ b/app/javascript/flavours/glitch/reducers/tags.js @@ -4,7 +4,7 @@ import { HASHTAG_FOLLOW_FAIL, HASHTAG_UNFOLLOW_REQUEST, HASHTAG_UNFOLLOW_FAIL, -} from 'mastodon/actions/tags'; +} from 'flavours/glitch/actions/tags'; import { Map as ImmutableMap, fromJS } from 'immutable'; const initialState = ImmutableMap(); diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index afd9d12cb..407293c62 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -17,7 +17,7 @@ import { ACCOUNT_UNFOLLOW_SUCCESS, } from 'flavours/glitch/actions/accounts'; import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; -import compareId from 'flavours/glitch/util/compare_id'; +import compareId from '../compare_id'; const initialState = ImmutableMap(); diff --git a/app/javascript/flavours/glitch/reducers/trends.js b/app/javascript/flavours/glitch/reducers/trends.js index 5cecc8fca..e2bac6199 100644 --- a/app/javascript/flavours/glitch/reducers/trends.js +++ b/app/javascript/flavours/glitch/reducers/trends.js @@ -1,22 +1,45 @@ -import { TRENDS_FETCH_REQUEST, TRENDS_FETCH_SUCCESS, TRENDS_FETCH_FAIL } from '../actions/trends'; +import { + TRENDS_TAGS_FETCH_REQUEST, + TRENDS_TAGS_FETCH_SUCCESS, + TRENDS_TAGS_FETCH_FAIL, + TRENDS_LINKS_FETCH_REQUEST, + TRENDS_LINKS_FETCH_SUCCESS, + TRENDS_LINKS_FETCH_FAIL, +} from 'flavours/glitch/actions/trends'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const initialState = ImmutableMap({ - items: ImmutableList(), - isLoading: false, + tags: ImmutableMap({ + items: ImmutableList(), + isLoading: false, + }), + + links: ImmutableMap({ + items: ImmutableList(), + isLoading: false, + }), }); export default function trendsReducer(state = initialState, action) { switch(action.type) { - case TRENDS_FETCH_REQUEST: - return state.set('isLoading', true); - case TRENDS_FETCH_SUCCESS: + case TRENDS_TAGS_FETCH_REQUEST: + return state.setIn(['tags', 'isLoading'], true); + case TRENDS_TAGS_FETCH_SUCCESS: + return state.withMutations(map => { + map.setIn(['tags', 'items'], fromJS(action.trends)); + map.setIn(['tags', 'isLoading'], false); + }); + case TRENDS_TAGS_FETCH_FAIL: + return state.setIn(['tags', 'isLoading'], false); + case TRENDS_LINKS_FETCH_REQUEST: + return state.setIn(['links', 'isLoading'], true); + case TRENDS_LINKS_FETCH_SUCCESS: return state.withMutations(map => { - map.set('items', fromJS(action.trends)); - map.set('isLoading', false); + map.setIn(['links', 'items'], fromJS(action.trends)); + map.setIn(['links', 'isLoading'], false); }); - case TRENDS_FETCH_FAIL: - return state.set('isLoading', false); + case TRENDS_LINKS_FETCH_FAIL: + return state.setIn(['links', 'isLoading'], false); default: return state; } diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js index bfddbd246..0a75e85c1 100644 --- a/app/javascript/flavours/glitch/reducers/user_lists.js +++ b/app/javascript/flavours/glitch/reducers/user_lists.js @@ -51,7 +51,12 @@ import { DIRECTORY_EXPAND_SUCCESS, DIRECTORY_EXPAND_FAIL, } from 'flavours/glitch/actions/directory'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { + FEATURED_TAGS_FETCH_REQUEST, + FEATURED_TAGS_FETCH_SUCCESS, + FEATURED_TAGS_FETCH_FAIL, +} from 'flavours/glitch/actions/featured_tags'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; const initialListState = ImmutableMap({ next: null, @@ -67,6 +72,7 @@ const initialState = ImmutableMap({ follow_requests: initialListState, blocks: initialListState, mutes: initialListState, + featured_tags: initialListState, }); const normalizeList = (state, path, accounts, next) => { @@ -89,6 +95,18 @@ const normalizeFollowRequest = (state, notification) => { }); }; +const normalizeFeaturedTag = (featuredTags, accountId) => { + const normalizeFeaturedTag = { ...featuredTags, accountId: accountId }; + return fromJS(normalizeFeaturedTag); +}; + +const normalizeFeaturedTags = (state, path, featuredTags, accountId) => { + return state.setIn(path, ImmutableMap({ + items: ImmutableList(featuredTags.map(featuredTag => normalizeFeaturedTag(featuredTag, accountId)).sort((a, b) => b.get('statuses_count') - a.get('statuses_count'))), + isLoading: false, + })); +}; + export default function userLists(state = initialState, action) { switch(action.type) { case FOLLOWERS_FETCH_SUCCESS: @@ -160,6 +178,12 @@ export default function userLists(state = initialState, action) { case DIRECTORY_FETCH_FAIL: case DIRECTORY_EXPAND_FAIL: return state.setIn(['directory', 'isLoading'], false); + case FEATURED_TAGS_FETCH_SUCCESS: + return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id); + case FEATURED_TAGS_FETCH_REQUEST: + return state.setIn(['featured_tags', action.id, 'isLoading'], true); + case FEATURED_TAGS_FETCH_FAIL: + return state.setIn(['featured_tags', action.id, 'isLoading'], false); default: return state; } diff --git a/app/javascript/flavours/glitch/util/scroll.js b/app/javascript/flavours/glitch/scroll.js index 84fe58269..84fe58269 100644 --- a/app/javascript/flavours/glitch/util/scroll.js +++ b/app/javascript/flavours/glitch/scroll.js diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js index 377805f16..df46b58a8 100644 --- a/app/javascript/flavours/glitch/selectors/index.js +++ b/app/javascript/flavours/glitch/selectors/index.js @@ -1,8 +1,8 @@ import escapeTextContentForBrowser from 'escape-html'; import { createSelector } from 'reselect'; import { List as ImmutableList } from 'immutable'; -import { toServerSideType } from 'flavours/glitch/util/filters'; -import { me } from 'flavours/glitch/util/initial_state'; +import { toServerSideType } from 'flavours/glitch/utils/filters'; +import { me } from 'flavours/glitch/initial_state'; const getAccountBase = (state, id) => state.getIn(['accounts', id], null); const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); @@ -42,7 +42,7 @@ export const makeGetStatus = () => { ], (statusBase, statusReblog, accountBase, accountReblog, filters) => { - if (!statusBase) { + if (!statusBase || statusBase.get('isLoading')) { return null; } diff --git a/app/javascript/flavours/glitch/util/settings.js b/app/javascript/flavours/glitch/settings.js index 7643a508e..46cfadfa3 100644 --- a/app/javascript/flavours/glitch/util/settings.js +++ b/app/javascript/flavours/glitch/settings.js @@ -45,3 +45,4 @@ export default class Settings { export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); export const tagHistory = new Settings('mastodon_tag_history'); +export const bannerSettings = new Settings('mastodon_banner_settings'); diff --git a/app/javascript/flavours/glitch/util/stream.js b/app/javascript/flavours/glitch/stream.js index c6d12cd6f..c6d12cd6f 100644 --- a/app/javascript/flavours/glitch/util/stream.js +++ b/app/javascript/flavours/glitch/stream.js diff --git a/app/javascript/flavours/glitch/styles/_mixins.scss b/app/javascript/flavours/glitch/styles/_mixins.scss index c92bc8608..9f6314f3f 100644 --- a/app/javascript/flavours/glitch/styles/_mixins.scss +++ b/app/javascript/flavours/glitch/styles/_mixins.scss @@ -60,6 +60,7 @@ font-family: inherit; background: $ui-base-color; color: $darker-text-color; + border-radius: 4px; font-size: 14px; margin: 0; } diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss index 1843129a0..0183c43d5 100644 --- a/app/javascript/flavours/glitch/styles/about.scss +++ b/app/javascript/flavours/glitch/styles/about.scss @@ -1,7 +1,5 @@ $maximum-width: 1235px; $fluid-breakpoint: $maximum-width + 20px; -$column-breakpoint: 700px; -$small-breakpoint: 960px; .container { box-sizing: border-box; @@ -15,894 +13,44 @@ $small-breakpoint: 960px; } } -.rich-formatting { - font-family: $font-sans-serif, sans-serif; - font-size: 14px; - font-weight: 400; - line-height: 1.7; - word-wrap: break-word; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - - &:hover, - &:focus, - &:active { - text-decoration: none; - } - } - - p, - li { - color: $darker-text-color; - } - - p { - margin-top: 0; - margin-bottom: .85em; - - &:last-child { - margin-bottom: 0; - } - } - - strong { - font-weight: 700; - color: $secondary-text-color; - } - - em { - font-style: italic; - color: $secondary-text-color; - } - - code { - font-size: 0.85em; - background: darken($ui-base-color, 8%); - border-radius: 4px; - padding: 0.2em 0.3em; - } - - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: $font-display, sans-serif; - margin-top: 1.275em; - margin-bottom: .85em; - font-weight: 500; - color: $secondary-text-color; - } - - h1 { - font-size: 2em; - } - - h2 { - font-size: 1.75em; - } - - h3 { - font-size: 1.5em; - } - - h4 { - font-size: 1.25em; - } - - h5, - h6 { - font-size: 1em; - } - - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - ul, - ol { - margin: 0; - padding: 0; - padding-left: 2em; - margin-bottom: 0.85em; - - &[type='a'] { - list-style-type: lower-alpha; - } - - &[type='i'] { - list-style-type: lower-roman; - } - } - - hr { - width: 100%; - height: 0; - border: 0; - border-bottom: 1px solid lighten($ui-base-color, 4%); - margin: 1.7em 0; - - &.spacer { - height: 1px; - border: 0; - } - } - - table { - width: 100%; - border-collapse: collapse; - break-inside: auto; - margin-top: 24px; - margin-bottom: 32px; - - thead tr, - tbody tr { - border-bottom: 1px solid lighten($ui-base-color, 4%); - font-size: 1em; - line-height: 1.625; - font-weight: 400; - text-align: left; - color: $darker-text-color; - } - - thead tr { - border-bottom-width: 2px; - line-height: 1.5; - font-weight: 500; - color: $dark-text-color; - } - - th, - td { - padding: 8px; - align-self: start; - align-items: start; - word-break: break-all; - - &.nowrap { - width: 25%; - position: relative; - - &::before { - content: ' '; - visibility: hidden; - } - - span { - position: absolute; - left: 8px; - right: 8px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - } - - & > :first-child { - margin-top: 0; - } +.brand { + position: relative; + text-decoration: none; } -.information-board { - background: darken($ui-base-color, 4%); - padding: 20px 0; - - .container-alt { - position: relative; - padding-right: 280px + 15px; - } - - &__sections { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - } - - &__section { - flex: 1 0 0; - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - line-height: 28px; - color: $primary-text-color; - text-align: right; - padding: 10px 15px; - - span, - strong { - display: block; - } - - span { - &:last-child { - color: $secondary-text-color; - } - } - - strong { - font-family: $font-display, sans-serif; - font-weight: 500; - font-size: 32px; - line-height: 48px; - } - - @media screen and (max-width: $column-breakpoint) { - text-align: center; - } - } - - .panel { - position: absolute; - width: 280px; - box-sizing: border-box; - background: darken($ui-base-color, 8%); - padding: 20px; - padding-top: 10px; - border-radius: 4px 4px 0 0; - right: 0; - bottom: -40px; - - .panel-header { - font-family: $font-display, sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - color: $darker-text-color; - padding-bottom: 5px; - margin-bottom: 15px; - border-bottom: 1px solid lighten($ui-base-color, 4%); - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - - a, - span { - font-weight: 400; - color: darken($darker-text-color, 10%); - } - - a { - text-decoration: none; - } - } - } - - .owner { - text-align: center; - - .avatar { - width: 80px; - height: 80px; - @include avatar-size(80px); - margin: 0 auto; - margin-bottom: 15px; - - img { - display: block; - width: 80px; - height: 80px; - border-radius: 48px; - @include avatar-radius(); - } - } - - .name { - font-size: 14px; - - a { - display: block; - color: $primary-text-color; - text-decoration: none; - - &:hover { - .display_name { - text-decoration: underline; - } - } - } - - .username { - display: block; - color: $darker-text-color; - } - } - } -} +.rules-list { + font-size: 15px; + line-height: 22px; + color: $primary-text-color; + counter-reset: list-counter; -.landing-page { - p, li { - font-family: $font-sans-serif, sans-serif; - font-size: 16px; - font-weight: 400; - line-height: 30px; - margin-bottom: 12px; - color: $darker-text-color; - - a { - color: $highlight-text-color; - text-decoration: underline; - } - } - - em { - display: inline; - margin: 0; - padding: 0; - font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; - color: lighten($darker-text-color, 10%); - } - - h1 { - font-family: $font-display, sans-serif; - font-size: 26px; - line-height: 30px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - - small { - font-family: $font-sans-serif, sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: lighten($darker-text-color, 10%); - } - } - - h2 { - font-family: $font-display, sans-serif; - font-size: 22px; - line-height: 26px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h3 { - font-family: $font-display, sans-serif; - font-size: 18px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h4 { - font-family: $font-display, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h5 { - font-family: $font-display, sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - h6 { - font-family: $font-display, sans-serif; - font-size: 12px; - line-height: 24px; + position: relative; + border-bottom: 1px solid lighten($ui-base-color, 8%); + padding: 1em 1.75em; + padding-left: 3em; font-weight: 500; - margin-bottom: 20px; - color: $secondary-text-color; - } - - ul, - ol { - margin-left: 20px; - - &[type='a'] { - list-style-type: lower-alpha; - } - - &[type='i'] { - list-style-type: lower-roman; - } - } - - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - li > ol, - li > ul { - margin-top: 6px; - } - - hr { - width: 100%; - height: 0; - border: 0; - border-bottom: 1px solid rgba($ui-base-lighter-color, .6); - margin: 20px 0; - - &.spacer { - height: 1px; - border: 0; - } - } - - &__information, - &__forms { - padding: 20px; - } - - &__call-to-action { - background: $ui-base-color; - border-radius: 4px; - padding: 25px 40px; - overflow: hidden; - box-sizing: border-box; - - .row { - width: 100%; - display: flex; - flex-direction: row-reverse; - flex-wrap: nowrap; - justify-content: space-between; - align-items: center; - } - - .row__information-board { - display: flex; - justify-content: flex-end; - align-items: flex-end; - - .information-board__section { - flex: 1 0 auto; - padding: 0 10px; - } - - @media screen and (max-width: $no-gap-breakpoint) { - width: 100%; - justify-content: space-between; - } - } - - .row__mascot { - flex: 1; - margin: 10px -50px 0 0; - - @media screen and (max-width: $no-gap-breakpoint) { - display: none; - } - } - } - - &__logo { - margin-right: 20px; - - img { - height: 50px; - width: auto; - mix-blend-mode: lighten; - } - } - - &__information { - padding: 45px 40px; - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - - strong { + counter-increment: list-counter; + + &::before { + content: counter(list-counter); + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + background: $highlight-text-color; + color: $ui-base-color; + border-radius: 50%; + width: 4ch; + height: 4ch; font-weight: 500; - color: lighten($darker-text-color, 10%); - } - - .account { - border-bottom: 0; - padding: 0; - - &__display-name { - align-items: center; - display: flex; - margin-right: 5px; - } - - div.account__display-name { - &:hover { - .display-name strong { - text-decoration: none; - } - } - - .account__avatar { - cursor: default; - } - } - - &__avatar-wrapper { - margin-left: 0; - flex: 0 0 auto; - } - - .display-name { - font-size: 15px; - - &__account { - font-size: 14px; - } - } - } - - @media screen and (max-width: $small-breakpoint) { - .contact { - margin-top: 30px; - } - } - - @media screen and (max-width: $column-breakpoint) { - padding: 25px 20px; - } - } - - &__information, - &__forms, - #mastodon-timeline { - box-sizing: border-box; - background: $ui-base-color; - border-radius: 4px; - box-shadow: 0 0 6px rgba($black, 0.1); - } - - &__mascot { - height: 104px; - position: relative; - left: -40px; - bottom: 25px; - - img { - height: 190px; - width: auto; - } - } - - &__short-description { - .row { display: flex; - flex-wrap: wrap; + justify-content: center; align-items: center; - margin-bottom: 40px; - } - - @media screen and (max-width: $column-breakpoint) { - .row { - margin-bottom: 20px; - } - } - - p a { - color: $secondary-text-color; - } - - h1 { - font-weight: 500; - color: $primary-text-color; - margin-bottom: 0; - - small { - color: $darker-text-color; - - span { - color: $secondary-text-color; - } - } - } - - p:last-child { - margin-bottom: 0; - } - } - - &__hero { - margin-bottom: 10px; - - img { - display: block; - margin: 0; - max-width: 100%; - height: auto; - border-radius: 4px; - } - } - - @media screen and (max-width: 840px) { - .information-board { - .container-alt { - padding-right: 20px; - } - - .panel { - position: static; - margin-top: 20px; - width: 100%; - border-radius: 4px; - - .panel-header { - text-align: center; - } - } - } - } - - @media screen and (max-width: 675px) { - .header-wrapper { - padding-top: 0; - - &.compact { - padding-bottom: 0; - } - - &.compact .hero .heading { - text-align: initial; - } } - .header .container-alt, - .features .container-alt { - display: block; - } - } - - .cta { - margin: 20px; - } -} - -.landing { - margin-bottom: 100px; - - @media screen and (max-width: 738px) { - margin-bottom: 0; - } - - &__brand { - display: flex; - justify-content: center; - align-items: center; - padding: 50px; - - .logo { - fill: $primary-text-color; - height: 52px; - } - - @media screen and (max-width: $no-gap-breakpoint) { - padding: 0; - margin-bottom: 30px; - } - } - - .directory { - margin-top: 30px; - background: transparent; - box-shadow: none; - border-radius: 0; - } - - .hero-widget { - margin-top: 30px; - margin-bottom: 0; - - h4 { - padding: 10px; - text-transform: uppercase; - font-weight: 700; - font-size: 13px; - color: $darker-text-color; - } - - &__text { - border-radius: 0; - padding-bottom: 0; - } - - &__footer { - background: $ui-base-color; - padding: 10px; - border-radius: 0 0 4px 4px; - display: flex; - - &__column { - flex: 1 1 50%; - overflow-x: hidden; - } - } - - .account { - padding: 10px 0; - border-bottom: 0; - - .account__display-name { - display: flex; - align-items: center; - } - } - - &__counters__wrapper { - display: flex; - } - - &__counter { - padding: 10px; - width: 50%; - - strong { - font-family: $font-display, sans-serif; - font-size: 15px; - font-weight: 700; - display: block; - } - - span { - font-size: 14px; - color: $darker-text-color; - } - } - } - - .simple_form .user_agreement .label_input > label { - font-weight: 400; - color: $darker-text-color; - } - - .simple_form p.lead { - color: $darker-text-color; - font-size: 15px; - line-height: 20px; - font-weight: 400; - margin-bottom: 25px; - } - - &__grid { - max-width: 960px; - margin: 0 auto; - display: grid; - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - grid-gap: 30px; - - @media screen and (max-width: 738px) { - grid-template-columns: minmax(0, 100%); - grid-gap: 10px; - - &__column-login { - grid-row: 1; - display: flex; - flex-direction: column; - - .box-widget { - order: 2; - flex: 0 0 auto; - } - - .hero-widget { - margin-top: 0; - margin-bottom: 10px; - order: 1; - flex: 0 0 auto; - } - } - - &__column-registration { - grid-row: 2; - } - - .directory { - margin-top: 10px; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - - .hero-widget { - display: block; - margin-bottom: 0; - box-shadow: none; - - &__img, - &__img img, - &__footer { - border-radius: 0; - } - } - - .hero-widget, - .box-widget, - .directory__tag { - border-bottom: 1px solid lighten($ui-base-color, 8%); - } - - .directory { - margin-top: 0; - - &__tag { - margin-bottom: 0; - - & > a, - & > div { - border-radius: 0; - box-shadow: none; - } - - &:last-child { - border-bottom: 0; - } - } - } - } - } -} - -.brand { - position: relative; - text-decoration: none; -} - -.brand__tagline { - display: block; - position: absolute; - bottom: -10px; - left: 50px; - width: 300px; - color: $ui-primary-color; - text-decoration: none; - font-size: 14px; - - @media screen and (max-width: $no-gap-breakpoint) { - position: static; - width: auto; - margin-top: 20px; - color: $dark-text-color; - } -} - -.rules-list { - background: darken($ui-base-color, 2%); - border: 1px solid darken($ui-base-color, 8%); - border-radius: 4px; - padding: 0.5em 2.5em !important; - margin-top: 1.85em !important; - - li { - border-bottom: 1px solid lighten($ui-base-color, 4%); - color: $dark-text-color; - padding: 1em; - &:last-child { border-bottom: 0; } } - - &__text { - color: $primary-text-color; - } } diff --git a/app/javascript/flavours/glitch/styles/accessibility.scss b/app/javascript/flavours/glitch/styles/accessibility.scss index 9b36bfd8d..7bffb2e26 100644 --- a/app/javascript/flavours/glitch/styles/accessibility.scss +++ b/app/javascript/flavours/glitch/styles/accessibility.scss @@ -29,22 +29,22 @@ $emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange' .hicolor-privacy-icons { .status__visibility-icon.fa-globe, - .composer--options--dropdown--content--item .fa-globe { + .privacy-dropdown__option .fa-globe { color: #1976d2; } .status__visibility-icon.fa-unlock, - .composer--options--dropdown--content--item .fa-unlock { + .privacy-dropdown__option .fa-unlock { color: #388e3c; } .status__visibility-icon.fa-lock, - .composer--options--dropdown--content--item .fa-lock { + .privacy-dropdown__option .fa-lock { color: #ffa000; } .status__visibility-icon.fa-envelope, - .composer--options--dropdown--content--item .fa-envelope { + .privacy-dropdown__option .fa-envelope { color: #d32f2f; } } diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 77890c467..c2426944b 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -31,23 +31,17 @@ $content-width: 840px; &__toggle { display: none; - background: lighten($ui-base-color, 8%); - height: 48px; + background: darken($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 4%); + align-items: center; &__logo { flex: 1 1 auto; a { - display: inline-block; + display: block; padding: 15px; } - - svg { - fill: $primary-text-color; - height: 20px; - position: relative; - bottom: -2px; - } } &__icon { @@ -55,15 +49,27 @@ $content-width: 840px; color: $darker-text-color; text-decoration: none; flex: 0 0 auto; - font-size: 20px; - padding: 15px; - } + font-size: 18px; + padding: 10px; + margin: 5px 10px; + border-radius: 4px; - a { - &:hover, - &:focus, - &:active { - background: lighten($ui-base-color, 12%); + &:focus { + background: $ui-base-color; + } + + .fa-times { + display: none; + } + + &.active { + .fa-times { + display: block; + } + + .fa-bars { + display: none; + } } } } @@ -79,7 +85,7 @@ $content-width: 840px; display: inherit; margin: inherit; width: inherit; - height: 20px; + height: 25px; } @media screen and (max-width: $no-columns-breakpoint) { @@ -188,24 +194,65 @@ $content-width: 840px; padding-top: 30px; } - &-heading { - display: flex; + &__heading { + margin-bottom: 45px; - padding-bottom: 36px; - border-bottom: 1px solid lighten($ui-base-color, 8%); + &__row { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + margin: -15px -15px 0 0; + + & > * { + margin-top: 15px; + margin-right: 15px; + } + } - margin: -15px -15px 40px 0; + &__tabs { + margin-top: 30px; + width: 100%; - flex-wrap: wrap; - align-items: center; - justify-content: space-between; + & > div { + display: flex; + flex-wrap: wrap; + gap: 5px; + } - & > * { - margin-top: 15px; - margin-right: 15px; + a { + font-size: 14px; + display: inline-flex; + align-items: center; + padding: 7px 10px; + border-radius: 4px; + color: $darker-text-color; + text-decoration: none; + font-weight: 500; + gap: 5px; + white-space: nowrap; + + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 4%); + } + + &.selected { + font-weight: 700; + color: $primary-text-color; + background: $ui-highlight-color; + + &:hover, + &:focus, + &:active { + background: lighten($ui-highlight-color, 4%); + } + } + } } - &-actions { + &__actions { display: inline-flex; & > :not(:first-child) { @@ -231,11 +278,7 @@ $content-width: 840px; color: $secondary-text-color; font-size: 24px; line-height: 36px; - font-weight: 400; - - @media screen and (max-width: $no-columns-breakpoint) { - font-weight: 700; - } + font-weight: 700; } h3 { @@ -340,6 +383,14 @@ $content-width: 840px; &.visible { display: block; + position: fixed; + z-index: 10; + width: 100%; + height: calc(100vh - 56px); + left: 0; + bottom: 0; + overflow-y: auto; + background: $ui-base-color; } } @@ -440,6 +491,11 @@ body, } } + & > div { + display: flex; + gap: 5px; + } + strong { font-weight: 500; text-transform: uppercase; @@ -1162,7 +1218,7 @@ a.name-tag, path:first-child { fill: rgba($highlight-text-color, 0.25) !important; - fill-opacity: 1 !important; + fill-opacity: 100% !important; } path:last-child { @@ -1721,3 +1777,67 @@ a.sparkline { } } } + +.history { + counter-reset: step 0; + font-size: 15px; + line-height: 22px; + + li { + counter-increment: step 1; + padding-left: 2.5rem; + padding-bottom: 8px; + position: relative; + margin-bottom: 8px; + + &::before { + position: absolute; + content: counter(step); + font-size: 0.625rem; + font-weight: 500; + left: 0; + display: flex; + justify-content: center; + align-items: center; + width: calc(1.375rem + 1px); + height: calc(1.375rem + 1px); + background: $ui-base-color; + border: 1px solid $highlight-text-color; + color: $highlight-text-color; + border-radius: 8px; + } + + &::after { + position: absolute; + content: ""; + width: 1px; + background: $highlight-text-color; + bottom: 0; + top: calc(1.875rem + 1px); + left: 0.6875rem; + } + + &:last-child { + margin-bottom: 0; + + &::after { + display: none; + } + } + } + + &__entry { + h5 { + font-weight: 500; + color: $primary-text-color; + line-height: 25px; + margin-bottom: 16px; + } + + .status { + border: 1px solid lighten($ui-base-color, 4%); + background: $ui-base-color; + border-radius: 4px; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/compact_header.scss b/app/javascript/flavours/glitch/styles/compact_header.scss deleted file mode 100644 index 4980ab5f1..000000000 --- a/app/javascript/flavours/glitch/styles/compact_header.scss +++ /dev/null @@ -1,34 +0,0 @@ -.compact-header { - h1 { - font-size: 24px; - line-height: 28px; - color: $darker-text-color; - font-weight: 500; - margin-bottom: 20px; - padding: 0 10px; - word-wrap: break-word; - - @media screen and (max-width: 740px) { - text-align: center; - padding: 20px 10px 0; - } - - a { - color: inherit; - text-decoration: none; - } - - small { - font-weight: 400; - color: $secondary-text-color; - } - - img { - display: inline-block; - margin-bottom: -5px; - margin-right: 15px; - width: 36px; - height: 36px; - } - } -} diff --git a/app/javascript/flavours/glitch/styles/components/about.scss b/app/javascript/flavours/glitch/styles/components/about.scss new file mode 100644 index 000000000..c6cc6c615 --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/about.scss @@ -0,0 +1,274 @@ +.image { + position: relative; + overflow: hidden; + + &__preview { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + + &.loaded &__preview { + display: none; + } + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + border: 0; + background: transparent; + opacity: 0; + } + + &.loaded img { + opacity: 1; + } +} + +.link-footer { + flex: 0 0 auto; + padding: 10px; + padding-top: 20px; + z-index: 1; + font-size: 13px; + + p { + color: $dark-text-color; + margin-bottom: 20px; + + strong { + font-weight: 500; + } + + a { + color: $dark-text-color; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + } +} + +.about { + padding: 20px; + + @media screen and (min-width: $no-gap-breakpoint) { + border-radius: 4px; + } + + &__footer { + color: $dark-text-color; + text-align: center; + font-size: 15px; + line-height: 22px; + margin-top: 20px; + } + + &__header { + margin-bottom: 30px; + + &__hero { + width: 100%; + height: auto; + aspect-ratio: 1.9; + background: lighten($ui-base-color, 4%); + border-radius: 8px; + margin-bottom: 30px; + } + + h1, + p { + text-align: center; + } + + h1 { + font-size: 24px; + line-height: 1.5; + font-weight: 700; + margin-bottom: 10px; + } + + p { + font-size: 16px; + line-height: 24px; + font-weight: 400; + color: $darker-text-color; + } + } + + &__meta { + background: lighten($ui-base-color, 4%); + border-radius: 4px; + display: flex; + margin-bottom: 30px; + font-size: 15px; + + &__column { + box-sizing: border-box; + width: 50%; + padding: 20px; + } + + &__divider { + width: 0; + border: 0; + border-style: solid; + border-color: lighten($ui-base-color, 8%); + border-left-width: 1px; + min-height: calc(100% - 60px); + flex: 0 0 auto; + } + + h4 { + font-size: 15px; + text-transform: uppercase; + color: $darker-text-color; + font-weight: 500; + margin-bottom: 20px; + } + + @media screen and (max-width: 600px) { + display: block; + + h4 { + text-align: center; + } + + &__column { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + } + + &__divider { + min-height: 0; + width: 100%; + border-left-width: 0; + border-top-width: 1px; + } + } + + .layout-multiple-columns & { + display: block; + + h4 { + text-align: center; + } + + &__column { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + } + + &__divider { + min-height: 0; + width: 100%; + border-left-width: 0; + border-top-width: 1px; + } + } + } + + &__mail { + color: $primary-text-color; + text-decoration: none; + font-weight: 500; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + .link-footer { + padding: 0; + margin-top: 60px; + text-align: center; + font-size: 15px; + line-height: 22px; + + @media screen and (min-width: $no-gap-breakpoint) { + display: none; + } + } + + .account { + padding: 0; + border: 0; + } + + .account__avatar-wrapper { + margin-left: 0; + } + + .account__relationship { + display: none; + } + + &__section { + margin-bottom: 10px; + + &__title { + font-size: 17px; + font-weight: 600; + line-height: 22px; + padding: 20px; + border-radius: 4px; + background: lighten($ui-base-color, 4%); + color: $highlight-text-color; + cursor: pointer; + } + + &.active &__title { + border-radius: 4px 4px 0 0; + } + + &__body { + border: 1px solid lighten($ui-base-color, 4%); + border-top: 0; + padding: 20px; + font-size: 15px; + line-height: 22px; + } + } + + &__domain-blocks { + margin-top: 30px; + width: 100%; + border-collapse: collapse; + break-inside: auto; + + th { + text-align: left; + font-weight: 500; + color: $darker-text-color; + } + + thead tr, + tbody tr { + border-bottom: 1px solid lighten($ui-base-color, 8%); + } + + tbody tr:last-child { + border-bottom: 0; + } + + th, + td { + padding: 8px; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 4e912b18b..00519adf1 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -541,6 +541,7 @@ &__buttons { display: flex; align-items: center; + gap: 8px; padding-top: 55px; overflow: hidden; @@ -550,14 +551,6 @@ box-sizing: content-box; padding: 2px; } - - & > .icon-button { - margin-right: 8px; - } - - .button { - margin: 0 8px; - } } &__name { diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss index 1440682f3..c61815e07 100644 --- a/app/javascript/flavours/glitch/styles/components/columns.scss +++ b/app/javascript/flavours/glitch/styles/components/columns.scss @@ -42,27 +42,62 @@ &__main { box-sizing: border-box; width: 100%; - max-width: 600px; flex: 0 0 auto; display: flex; flex-direction: column; @media screen and (min-width: $no-gap-breakpoint) { padding: 0 10px; + max-width: 600px; } } } } +$ui-header-height: 55px; + +.ui__header { + display: none; + box-sizing: border-box; + height: $ui-header-height; + position: sticky; + top: 0; + z-index: 2; + justify-content: space-between; + align-items: center; + + &__logo { + display: inline-flex; + padding: 15px; + + .logo { + height: $ui-header-height - 30px; + width: auto; + } + } + + &__links { + display: flex; + align-items: center; + gap: 10px; + padding: 0 10px; + + .button { + flex: 0 0 auto; + } + } +} + .tabs-bar__wrapper { background: darken($ui-base-color, 8%); position: sticky; - top: 0; + top: $ui-header-height; z-index: 2; padding-top: 0; @media screen and (min-width: $no-gap-breakpoint) { padding-top: 10px; + top: 0; } .tabs-bar { @@ -117,6 +152,7 @@ box-sizing: border-box; width: 100%; background: lighten($ui-base-color, 4%); + border-radius: 4px 4px 0 0; color: $highlight-text-color; cursor: pointer; flex: 0 0 auto; @@ -178,6 +214,8 @@ font-size: 16px; padding: 15px; text-decoration: none; + overflow: hidden; + white-space: nowrap; &:hover, &:focus, @@ -204,6 +242,17 @@ color: $highlight-text-color; } } + + &--logo { + background: transparent; + padding: 10px; + + &:hover, + &:focus, + &:active { + background: transparent; + } + } } .column-link__icon { @@ -255,6 +304,7 @@ display: flex; font-size: 16px; background: lighten($ui-base-color, 4%); + border-radius: 4px 4px 0 0; flex: 0 0 auto; cursor: pointer; position: relative; @@ -309,6 +359,8 @@ > .scrollable { background: $ui-base-color; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; } } @@ -352,6 +404,11 @@ &:focus { text-shadow: 0 0 4px darken($ui-highlight-color, 5%); } + + &:disabled { + color: $dark-text-color; + cursor: default; + } } .column-header__notif-cleaning-buttons { @@ -570,7 +627,6 @@ } .empty-column-indicator, -.error-column, .follow_requests-unlocked_explanation { color: $dark-text-color; background: $ui-base-color; @@ -607,7 +663,48 @@ } .error-column { + padding: 20px; + background: $ui-base-color; + border-radius: 4px; + display: flex; + flex: 1 1 auto; + align-items: center; + justify-content: center; flex-direction: column; + cursor: default; + + &__image { + width: 70%; + max-width: 350px; + margin-top: -50px; + } + + &__message { + text-align: center; + color: $darker-text-color; + font-size: 15px; + line-height: 22px; + + h1 { + font-size: 28px; + line-height: 33px; + font-weight: 700; + margin-bottom: 15px; + color: $primary-text-color; + } + + p { + max-width: 48ch; + } + + &__actions { + margin-top: 30px; + display: flex; + gap: 10px; + align-items: center; + justify-content: center; + } + } } // more fixes for the navbar-under mode @@ -849,7 +946,7 @@ .column-actions { display: flex; - align-items: start; + align-items: flex-start; justify-content: center; padding: 40px; padding-top: 40px; @@ -881,3 +978,28 @@ color: $darker-text-color; } } + +.dismissable-banner { + background: $ui-base-color; + border-bottom: 1px solid lighten($ui-base-color, 8%); + display: flex; + align-items: center; + gap: 30px; + + &__message { + flex: 1 1 auto; + padding: 20px 15px; + cursor: default; + font-size: 14px; + line-height: 18px; + color: $primary-text-color; + } + + &__action { + padding: 15px; + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + } +} diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/compose_form.scss index 1468bd4f5..72d3aad1d 100644 --- a/app/javascript/flavours/glitch/styles/components/composer.scss +++ b/app/javascript/flavours/glitch/styles/components/compose_form.scss @@ -1,4 +1,4 @@ -.composer { +.compose-form { padding: 10px; .emoji-picker-dropdown { @@ -25,16 +25,16 @@ } } -.no-reduce-motion .composer--spoiler { +.no-reduce-motion .spoiler-input { transition: height 0.4s ease, opacity 0.4s ease; } -.composer--spoiler { +.spoiler-input { height: 0; transform-origin: bottom; opacity: 0.0; - &.composer--spoiler--visible { + &.spoiler-input--visible { height: 36px; margin-bottom: 11px; opacity: 1.0; @@ -64,7 +64,7 @@ } } -.composer--warning { +.compose-form__warning { color: $inverted-text-color; margin-bottom: 15px; background: $ui-primary-color; @@ -123,7 +123,7 @@ } } -.composer--reply { +.reply-indicator { margin: 0 0 10px; border-radius: 4px; padding: 10px; @@ -131,117 +131,117 @@ min-height: 23px; overflow-y: auto; flex: 0 2 auto; +} - & > header { - margin-bottom: 5px; - overflow: hidden; +.reply-indicator__header { + margin-bottom: 5px; + overflow: hidden; - & > .account.small { color: $inverted-text-color; } + & > .account.small { color: $inverted-text-color; } +} - & > .cancel { - float: right; - line-height: 24px; - } - } +.reply-indicator__cancel { + float: right; + line-height: 24px; +} - & > .content { - position: relative; - margin: 10px 0; - padding: 0 12px; - font-size: 14px; - line-height: 20px; - color: $inverted-text-color; - word-wrap: break-word; - font-weight: 400; - overflow: visible; - white-space: pre-wrap; - padding-top: 5px; - overflow: hidden; +.reply-indicator__content { + position: relative; + margin: 10px 0; + padding: 0 12px; + font-size: 14px; + line-height: 20px; + color: $inverted-text-color; + word-wrap: break-word; + font-weight: 400; + overflow: visible; + white-space: pre-wrap; + padding-top: 5px; + overflow: hidden; - p, pre, blockquote { - margin-bottom: 20px; - white-space: pre-wrap; + p, pre, blockquote { + margin-bottom: 20px; + white-space: pre-wrap; - &:last-child { - margin-bottom: 0; - } + &:last-child { + margin-bottom: 0; } + } - h1, h2, h3, h4, h5 { - margin-top: 20px; - margin-bottom: 20px; - } + h1, h2, h3, h4, h5 { + margin-top: 20px; + margin-bottom: 20px; + } - h1, h2 { - font-weight: 700; - font-size: 18px; - } + h1, h2 { + font-weight: 700; + font-size: 18px; + } - h2 { - font-size: 16px; - } + h2 { + font-size: 16px; + } - h3, h4, h5 { - font-weight: 500; - } + h3, h4, h5 { + font-weight: 500; + } - blockquote { - padding-left: 10px; - border-left: 3px solid $inverted-text-color; - color: $inverted-text-color; - white-space: normal; + blockquote { + padding-left: 10px; + border-left: 3px solid $inverted-text-color; + color: $inverted-text-color; + white-space: normal; - p:last-child { - margin-bottom: 0; - } + p:last-child { + margin-bottom: 0; } + } - b, strong { - font-weight: 700; - } + b, strong { + font-weight: 700; + } - em, i { - font-style: italic; - } + em, i { + font-style: italic; + } - sub { - font-size: smaller; - vertical-align: sub; - } + sub { + font-size: smaller; + vertical-align: sub; + } - sup { - font-size: smaller; - vertical-align: super; - } + sup { + font-size: smaller; + vertical-align: super; + } - ul, ol { - margin-left: 1em; + ul, ol { + margin-left: 1em; - p { - margin: 0; - } + p { + margin: 0; } + } - ul { - list-style-type: disc; - } + ul { + list-style-type: disc; + } - ol { - list-style-type: decimal; - } + ol { + list-style-type: decimal; + } - a { - color: $lighter-text-color; - text-decoration: none; + a { + color: $lighter-text-color; + text-decoration: none; - &:hover { text-decoration: underline } + &:hover { text-decoration: underline } - &.mention { - &:hover { - text-decoration: none; + &.mention { + &:hover { + text-decoration: none; - span { text-decoration: underline } - } + span { text-decoration: underline } } } } @@ -253,8 +253,12 @@ } } -.compose-form__autosuggest-wrapper, -.autosuggest-input { +.compose-form .compose-form__autosuggest-wrapper { + position: relative; +} + +.compose-form .autosuggest-textarea, +.compose-form .autosuggest-input { position: relative; width: 100%; @@ -284,10 +288,6 @@ all: unset; } - &:disabled { - background: $ui-secondary-color; - } - &:focus { outline: 0; } @@ -304,7 +304,7 @@ } } -.composer--textarea--icons { +.compose-form__textarea-icons { display: block; position: absolute; top: 29px; @@ -401,25 +401,25 @@ } } -.composer--upload_form { +.compose-form__upload-wrapper { overflow: hidden; +} - & > .content { - display: flex; - flex-direction: row; - flex-wrap: wrap; - font-family: inherit; - padding: 5px; - overflow: hidden; - } +.compose-form__uploads-wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + font-family: inherit; + padding: 5px; + overflow: hidden; } -.composer--upload_form--item { +.compose-form__upload { flex: 1 1 0; margin: 5px; min-width: 40%; - & > div { + .compose-form__upload-thumbnail { position: relative; border-radius: 4px; height: 140px; @@ -459,52 +459,52 @@ } } -.composer--upload_form--actions { +.compose-form__upload__actions { background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent); display: flex; align-items: flex-start; justify-content: space-between; } -.composer--upload_form--progress { +.upload-progress { display: flex; padding: 10px; color: $darker-text-color; overflow: hidden; - & > .fa { + .fa { font-size: 34px; margin-right: 10px; } - & > .message { - flex: 1 1 auto; + span { + display: block; + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + } +} - & > span { - display: block; - font-size: 12px; - font-weight: 500; - text-transform: uppercase; - } +.upload-progress__message { + flex: 1 1 auto; +} - & > .backdrop { - position: relative; - margin-top: 5px; - border-radius: 6px; - width: 100%; - height: 6px; - background: $ui-base-lighter-color; - - & > .tracker { - position: absolute; - top: 0; - left: 0; - height: 6px; - border-radius: 6px; - background: $ui-highlight-color; - } - } - } +.upload-progress__backdrop { + position: relative; + margin-top: 5px; + border-radius: 6px; + width: 100%; + height: 6px; + background: $ui-base-lighter-color; +} + +.upload-progress__tracker { + position: absolute; + top: 0; + left: 0; + height: 6px; + border-radius: 6px; + background: $ui-highlight-color; } .compose-form__modifiers { @@ -514,7 +514,7 @@ background: $simple-background-color; } -.composer--options-wrapper { +.compose-form__buttons-wrapper { padding: 10px; background: darken($simple-background-color, 8%); border-radius: 0 0 4px 4px; @@ -524,11 +524,12 @@ flex: 0 0 auto; } -.composer--options { +.compose-form__buttons { display: flex; flex: 0 0 auto; - & > * { + & .icon-button, + & .text-icon-button { display: inline-block; box-sizing: content-box; padding: 0 3px; @@ -550,30 +551,41 @@ } } -.compose--counter-wrapper { +.character-counter__wrapper { align-self: center; margin-right: 4px; } -.composer--options--dropdown { - &.open { - & > .value { - border-radius: 4px 4px 0 0; - box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1); - color: $primary-text-color; - background: $ui-highlight-color; +.privacy-dropdown.active { + .privacy-dropdown__value { + background: $simple-background-color; + border-radius: 4px 4px 0 0; + box-shadow: 0 -4px 4px rgba($base-shadow-color, 0.1); + + .icon-button { transition: none; } - &.top { - & > .value { - border-radius: 0 0 4px 4px; - box-shadow: 0 4px 4px rgba($base-shadow-color, 0.1); + + &.active { + background: $ui-highlight-color; + + .icon-button { + color: $primary-text-color; } } } + + &.top .privacy-dropdown__value { + border-radius: 0 0 4px 4px; + } + + .privacy-dropdown__dropdown { + display: block; + box-shadow: 2px 4px 6px rgba($base-shadow-color, 0.1); + } } -.composer--options--dropdown--content { +.privacy-dropdown__dropdown { position: absolute; border-radius: 4px; box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); @@ -582,14 +594,14 @@ transform-origin: 50% 0; } -.composer--options--dropdown--content--item { +.privacy-dropdown__option { display: flex; align-items: center; padding: 10px; color: $inverted-text-color; cursor: pointer; - & > .content { + .privacy-dropdown__option__content { flex: 1 1 auto; color: $lighter-text-color; @@ -607,7 +619,7 @@ background: $ui-highlight-color; color: $primary-text-color; - & > .content { + .privacy-dropdown__option__content { color: $primary-text-color; strong { color: $primary-text-color } @@ -617,31 +629,25 @@ &.active:hover { background: lighten($ui-highlight-color, 4%) } } -.composer--publisher { - padding-top: 10px; - text-align: right; - white-space: nowrap; - overflow: hidden; +.compose-form__publish { + display: flex; justify-content: flex-end; + min-width: 0; flex: 0 0 auto; + column-gap: 5px; - & > .primary { - display: inline-block; - margin: 0; - padding: 7px 10px; - text-align: center; - } + .compose-form__publish-button-wrapper { + overflow: hidden; + padding-top: 10px; - & > .side_arm { - display: inline-block; - margin: 0 5px; - padding: 7px 0; - width: 36px; - text-align: center; - } + button { + padding: 7px 10px; + text-align: center; + } - &.over { - & > .count { color: $warning-red } + & > .side_arm { + width: 36px; + } } } diff --git a/app/javascript/flavours/glitch/styles/components/emoji_picker.scss b/app/javascript/flavours/glitch/styles/components/emoji_picker.scss index 0089445e1..790650cfa 100644 --- a/app/javascript/flavours/glitch/styles/components/emoji_picker.scss +++ b/app/javascript/flavours/glitch/styles/components/emoji_picker.scss @@ -111,7 +111,7 @@ position: relative; input { - font-size: 14px; + font-size: 16px; font-weight: 400; padding: 7px 9px; padding-right: 25px; @@ -132,6 +132,10 @@ &:active { outline: 0 !important; } + + &::-webkit-search-cancel-button { + display: none; + } } } diff --git a/app/javascript/flavours/glitch/styles/components/explore.scss b/app/javascript/flavours/glitch/styles/components/explore.scss new file mode 100644 index 000000000..bad77fc1c --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/explore.scss @@ -0,0 +1,115 @@ +.account-card__header { + position: relative; +} + +.explore__search-header { + background: darken($ui-base-color, 4%); + justify-content: center; + align-items: center; + padding: 15px; + + .search { + width: 100%; + margin-bottom: 0; + } + + .search__input { + border: 1px solid lighten($ui-base-color, 8%); + padding: 10px; + } + + .search .fa { + top: 10px; + right: 10px; + color: $dark-text-color; + } + + .search .fa-times-circle { + top: 12px; + } +} + +.explore__search-results { + flex: 1 1 auto; + display: flex; + flex-direction: column; +} + +.story { + display: flex; + align-items: center; + color: $primary-text-color; + text-decoration: none; + padding: 15px 0; + border-bottom: 1px solid lighten($ui-base-color, 8%); + + &:last-child { + border-bottom: 0; + } + + &:hover, + &:active, + &:focus { + background-color: lighten($ui-base-color, 4%); + } + + &__details { + padding: 0 15px; + flex: 1 1 auto; + + &__publisher { + color: $darker-text-color; + margin-bottom: 4px; + } + + &__title { + font-size: 19px; + line-height: 24px; + font-weight: 500; + margin-bottom: 4px; + } + + &__shared { + color: $darker-text-color; + } + } + + &__thumbnail { + flex: 0 0 auto; + margin: 0 15px; + position: relative; + width: 120px; + height: 120px; + + .skeleton { + width: 100%; + height: 100%; + } + + img { + border-radius: 4px; + display: block; + margin: 0; + width: 100%; + height: 100%; + object-fit: cover; + } + + &__preview { + border-radius: 4px; + display: block; + margin: 0; + width: 100%; + height: 100%; + object-fit: fill; + position: absolute; + top: 0; + left: 0; + z-index: 0; + + &--hidden { + display: none; + } + } + } +} diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index b54c3f696..b00038afd 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -20,6 +20,11 @@ background: transparent; padding: 0; cursor: pointer; + text-decoration: none; + + &--destructive { + color: $error-value-color; + } &:hover, &:active { @@ -110,6 +115,27 @@ &:hover { border-color: lighten($ui-primary-color, 4%); color: lighten($darker-text-color, 4%); + text-decoration: none; + } + + &:disabled { + opacity: 0.5; + } + } + + &.button-tertiary { + background: transparent; + padding: 6px 17px; + color: $highlight-text-color; + border: 1px solid $highlight-text-color; + + &:active, + &:focus, + &:hover { + background: $ui-highlight-color; + color: $primary-text-color; + border: 0; + padding: 7px 18px; } &:disabled { @@ -168,6 +194,15 @@ color: $highlight-text-color; } + &.copyable { + transition: background 300ms linear; + } + + &.copied { + background: $valid-value-color; + transition: none; + } + &::-moz-focus-inner { border: 0; } @@ -222,11 +257,12 @@ display: inline-flex; align-items: center; width: auto !important; + padding: 0 4px 0 2px; } &__counter { display: inline-block; - width: 14px; + width: auto; margin-left: 4px; font-size: 12px; font-weight: 500; @@ -863,6 +899,10 @@ position: relative; min-height: 120px; } + + .scrollable { + flex: 1 1 auto; + } } .scrollable.fullscreen { @@ -1004,43 +1044,6 @@ color: $dark-text-color; } - &__footer { - flex: 0 0 auto; - padding: 10px; - padding-top: 20px; - z-index: 1; - - ul { - margin-bottom: 10px; - } - - ul li { - display: inline; - } - - p { - color: $dark-text-color; - font-size: 13px; - margin-bottom: 20px; - - a { - color: $dark-text-color; - text-decoration: underline; - } - } - - a { - text-decoration: none; - color: $darker-text-color; - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } - } - &__trends { flex: 0 1 auto; opacity: 1; @@ -1735,7 +1738,7 @@ noscript { @import 'domains'; @import 'status'; @import 'modal'; -@import 'composer'; +@import 'compose_form'; @import 'columns'; @import 'regeneration_indicator'; @import 'directory'; @@ -1751,3 +1754,7 @@ noscript { @import 'error_boundary'; @import 'single_column'; @import 'announcements'; +@import 'explore'; +@import 'signed_out'; +@import 'privacy_policy'; +@import 'about'; diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index e95bea0d7..ab8609170 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -23,6 +23,7 @@ left: 0; width: 100%; height: 100%; + box-sizing: border-box; display: flex; flex-direction: column; align-items: center; @@ -415,7 +416,6 @@ } .boost-modal, -.favourite-modal, .confirmation-modal, .report-modal, .actions-modal, @@ -460,7 +460,7 @@ } } -.favourite-modal .status-direct { +.boost-modal .status-direct { background-color: inherit; } @@ -477,8 +477,7 @@ } } -.boost-modal__container, -.favourite-modal__container { +.boost-modal__container { overflow-x: scroll; padding: 10px; @@ -489,7 +488,6 @@ } .boost-modal__action-bar, -.favourite-modal__action-bar, .confirmation-modal__action-bar, .mute-modal__action-bar, .block-modal__action-bar { @@ -511,13 +509,11 @@ } } -.boost-modal__status-header, -.favourite-modal__status-header { +.boost-modal__status-header { font-size: 15px; } -.boost-modal__status-time, -.favourite-modal__status-time { +.boost-modal__status-time { float: right; font-size: 14px; } @@ -1289,11 +1285,11 @@ } } -.modal-root__container .composer--options--dropdown { +.modal-root__container .privacy-dropdown { flex-grow: 0; } -.modal-root__container .composer--options--dropdown--content { +.modal-root__container .privacy-dropdown__dropdown { pointer-events: auto; z-index: 9999; } @@ -1304,3 +1300,123 @@ img.modal-warning { margin-bottom: 15px; width: 60px; } + +.interaction-modal { + max-width: 90vw; + width: 600px; + background: $ui-base-color; + border-radius: 8px; + overflow: hidden; + position: relative; + display: block; + padding: 20px; + + h3 { + font-size: 22px; + line-height: 33px; + font-weight: 700; + text-align: center; + } + + &__icon { + color: $highlight-text-color; + margin: 0 5px; + } + + &__lead { + padding: 20px; + text-align: center; + + h3 { + margin-bottom: 15px; + } + + p { + font-size: 17px; + line-height: 22px; + color: $darker-text-color; + } + } + + &__choices { + display: flex; + + &__choice { + flex: 0 0 auto; + width: 50%; + box-sizing: border-box; + padding: 20px; + + h3 { + margin-bottom: 20px; + } + + p { + color: $darker-text-color; + margin-bottom: 20px; + } + + .button { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } + } + } + + @media screen and (max-width: $no-gap-breakpoint - 1px) { + &__choices { + display: block; + + &__choice { + width: auto; + margin-bottom: 20px; + } + } + } +} + +.copypaste { + display: flex; + align-items: center; + gap: 10px; + + input { + display: block; + font-family: inherit; + background: darken($ui-base-color, 8%); + border: 1px solid $highlight-text-color; + color: $darker-text-color; + border-radius: 4px; + padding: 6px 9px; + line-height: 22px; + font-size: 14px; + transition: border-color 300ms linear; + flex: 1 1 auto; + overflow: hidden; + + &:focus { + outline: 0; + background: darken($ui-base-color, 4%); + } + } + + .button { + flex: 0 0 auto; + transition: background 300ms linear; + } + + &.copied { + input { + border: 1px solid $valid-value-color; + transition: none; + } + + .button { + background: $valid-value-color; + transition: none; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/components/privacy_policy.scss b/app/javascript/flavours/glitch/styles/components/privacy_policy.scss new file mode 100644 index 000000000..96cf06742 --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/privacy_policy.scss @@ -0,0 +1,207 @@ +.privacy-policy { + background: $ui-base-color; + padding: 20px; + + @media screen and (min-width: $no-gap-breakpoint) { + border-radius: 4px; + } + + &__body { + margin-top: 20px; + } +} + +.prose { + color: $secondary-text-color; + font-size: 15px; + line-height: 22px; + + p, + ul, + ol { + margin-top: 1.25em; + margin-bottom: 1.25em; + } + + img { + margin-top: 2em; + margin-bottom: 2em; + } + + video { + margin-top: 2em; + margin-bottom: 2em; + } + + figure { + margin-top: 2em; + margin-bottom: 2em; + + figcaption { + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; + } + } + + figure > * { + margin-top: 0; + margin-bottom: 0; + } + + h1 { + font-size: 1.5em; + margin-top: 0; + margin-bottom: 1em; + line-height: 1.33; + } + + h2 { + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; + } + + h3, + h4, + h5, + h6 { + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; + } + + ol { + counter-reset: list-counter; + } + + li { + margin-top: 0.5em; + margin-bottom: 0.5em; + } + + ol > li { + counter-increment: list-counter; + + &::before { + content: counter(list-counter) "."; + position: absolute; + left: 0; + } + } + + ul > li::before { + content: ""; + position: absolute; + background-color: $darker-text-color; + border-radius: 50%; + width: 0.375em; + height: 0.375em; + top: 0.5em; + left: 0.25em; + } + + ul > li, + ol > li { + position: relative; + padding-left: 1.75em; + } + + & > ul > li p { + margin-top: 0.75em; + margin-bottom: 0.75em; + } + + & > ul > li > *:first-child { + margin-top: 1.25em; + } + + & > ul > li > *:last-child { + margin-bottom: 1.25em; + } + + & > ol > li > *:first-child { + margin-top: 1.25em; + } + + & > ol > li > *:last-child { + margin-bottom: 1.25em; + } + + ul ul, + ul ol, + ol ul, + ol ol { + margin-top: 0.75em; + margin-bottom: 0.75em; + } + + h1, + h2, + h3, + h4, + h5, + h6, + strong, + b { + color: $primary-text-color; + font-weight: 700; + } + + em, + i { + font-style: italic; + } + + a { + color: $highlight-text-color; + text-decoration: underline; + + &:focus, + &:hover, + &:active { + text-decoration: none; + } + } + + code { + font-size: 0.875em; + background: darken($ui-base-color, 8%); + border-radius: 4px; + padding: 0.2em 0.3em; + } + + hr { + border: 0; + border-top: 1px solid lighten($ui-base-color, 4%); + margin-top: 3em; + margin-bottom: 3em; + } + + hr + * { + margin-top: 0; + } + + h2 + * { + margin-top: 0; + } + + h3 + * { + margin-top: 0; + } + + h4 + *, + h5 + *, + h6 + * { + margin-top: 0; + } + + & > :first-child { + margin-top: 0; + } + + & > :last-child { + margin-bottom: 0; + } +} diff --git a/app/javascript/flavours/glitch/styles/components/search.scss b/app/javascript/flavours/glitch/styles/components/search.scss index 17a34db62..70af0f651 100644 --- a/app/javascript/flavours/glitch/styles/components/search.scss +++ b/app/javascript/flavours/glitch/styles/components/search.scss @@ -28,10 +28,6 @@ &:focus { background: lighten($ui-base-color, 4%); } - - @media screen and (max-width: 600px) { - font-size: 16px; - } } .search__icon { @@ -132,6 +128,7 @@ align-items: center; padding: 15px; border-bottom: 1px solid lighten($ui-base-color, 8%); + gap: 15px; &:last-child { border-bottom: 0; @@ -173,16 +170,8 @@ font-size: 24px; font-weight: 500; text-align: right; - padding-right: 15px; - margin-left: 5px; color: $secondary-text-color; text-decoration: none; - - &__asterisk { - color: $darker-text-color; - font-size: 18px; - vertical-align: super; - } } &__sparkline { @@ -191,7 +180,7 @@ path:first-child { fill: rgba($highlight-text-color, 0.25) !important; - fill-opacity: 1 !important; + fill-opacity: 100% !important; } path:last-child { diff --git a/app/javascript/flavours/glitch/styles/components/signed_out.scss b/app/javascript/flavours/glitch/styles/components/signed_out.scss new file mode 100644 index 000000000..efb49305d --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/signed_out.scss @@ -0,0 +1,110 @@ +.sign-in-banner { + padding: 10px; + + p { + color: $darker-text-color; + margin-bottom: 20px; + + a { + color: $secondary-text-color; + text-decoration: none; + unicode-bidi: isolate; + + &:hover { + text-decoration: underline; + + .fa { + color: lighten($dark-text-color, 7%); + } + } + } + } + + .button { + margin-bottom: 10px; + } +} + +.server-banner { + padding: 20px 0; + + &__introduction { + color: $darker-text-color; + margin-bottom: 20px; + + strong { + font-weight: 600; + } + + a { + color: inherit; + text-decoration: underline; + + &:hover, + &:active, + &:focus { + text-decoration: none; + } + } + } + + &__hero { + display: block; + border-radius: 4px; + width: 100%; + height: auto; + margin-bottom: 20px; + aspect-ratio: 1.9; + border: 0; + background: $ui-base-color; + object-fit: cover; + } + + &__description { + margin-bottom: 20px; + } + + &__meta { + display: flex; + gap: 10px; + max-width: 100%; + + &__column { + flex: 0 0 auto; + width: calc(50% - 5px); + overflow: hidden; + } + } + + &__number { + font-weight: 600; + color: $primary-text-color; + font-size: 14px; + } + + &__number-label { + color: $darker-text-color; + font-weight: 500; + font-size: 14px; + } + + h4 { + text-transform: uppercase; + color: $darker-text-color; + margin-bottom: 10px; + font-weight: 600; + } + + .account { + padding: 0; + border: 0; + } + + .account__avatar-wrapper { + margin-left: 0; + } + + .spacer { + margin: 10px 0; + } +} diff --git a/app/javascript/flavours/glitch/styles/components/single_column.scss b/app/javascript/flavours/glitch/styles/components/single_column.scss index 3843bcd68..d91306151 100644 --- a/app/javascript/flavours/glitch/styles/components/single_column.scss +++ b/app/javascript/flavours/glitch/styles/components/single_column.scss @@ -6,6 +6,26 @@ height: calc(100% - 10px); overflow-y: hidden; + .hero-widget { + box-shadow: none; + + &__text, + &__img, + &__img img { + border-radius: 0; + } + + &__text { + padding: 15px; + color: $secondary-text-color; + + strong { + font-weight: 700; + color: $primary-text-color; + } + } + } + .search__input { line-height: 18px; font-size: 16px; @@ -21,11 +41,7 @@ flex: 0 1 48px; } - .flex-spacer { - background: transparent; - } - - .composer { + .compose-form { flex: 1; overflow-y: hidden; display: flex; @@ -43,10 +59,6 @@ .autosuggest-textarea__textarea { overflow-y: hidden; } - - .compose-form__upload-thumbnail { - height: 80px; - } } .navigation-panel { @@ -61,6 +73,14 @@ flex: 0 0 auto; } + .logo { + height: 30px; + width: auto; + } +} + +.navigation-panel, +.compose-panel { hr { flex: 0 0 auto; border: 0; @@ -130,97 +150,71 @@ padding-top: 0; } - @media screen and (min-width: 630px) { - .detailed-status { - padding: 15px; + .detailed-status { + padding: 15px; - .media-gallery, - .video-player, - .audio-player { - margin-top: 15px; - } + .media-gallery, + .video-player, + .audio-player { + margin-top: 15px; } + } - .account__header__bar { - padding: 5px 10px; - } + .account__header__bar { + padding: 5px 10px; + } - .navigation-bar, - .compose-form { - padding: 15px; - } + .navigation-bar, + .compose-form { + padding: 15px; + } - .compose-form .compose-form__publish .compose-form__publish-button-wrapper { - padding-top: 15px; - } + .compose-form .compose-form__publish .compose-form__publish-button-wrapper { + padding-top: 15px; + } - .notification__report { - padding: 15px 15px 15px (48px + 15px * 2); - min-height: 48px + 2px; + .notification__report { + padding: 15px 15px 15px (48px + 15px * 2); + min-height: 48px + 2px; - &__avatar { - left: 15px; - top: 17px; - } + &__avatar { + left: 15px; + top: 17px; } + } - .status { - padding: 15px; - min-height: 48px + 2px; + .status { + padding: 15px; + min-height: 48px + 2px; - .media-gallery, - &__action-bar, - .video-player, - .audio-player { - margin-top: 10px; - } + .media-gallery, + &__action-bar, + .video-player, + .audio-player { + margin-top: 10px; } + } - .account { - padding: 15px 10px; + .account { + padding: 15px 10px; - &__header__bio { - margin: 0 -10px; - } + &__header__bio { + margin: 0 -10px; } + } - .notification { - &__message { - padding-top: 15px; - } - - .status { - padding-top: 8px; - } + .notification { + &__message { + padding-top: 15px; + } - .account { - padding-top: 8px; - } + .status { + padding-top: 8px; } - } -} -.floating-action-button { - position: fixed; - display: flex; - justify-content: center; - align-items: center; - width: 3.9375rem; - height: 3.9375rem; - bottom: 1.3125rem; - right: 1.3125rem; - background: darken($ui-highlight-color, 2%); - color: $white; - border-radius: 50%; - font-size: 21px; - line-height: 21px; - text-decoration: none; - box-shadow: 2px 3px 9px rgba($base-shadow-color, 0.4); - - &:hover, - &:focus, - &:active { - background: $ui-highlight-color; + .account { + padding-top: 8px; + } } } @@ -237,37 +231,104 @@ .search { margin-bottom: 10px; } -} -@media screen and (max-width: 600px + (285px * 1) + (10px * 1)) { - .columns-area__panels__pane--compositional { + .tabs-bar__link.optional { display: none; } - .with-fab .scrollable .item-list:last-child { - padding-bottom: 5.25rem; + .search-page .search { + display: none; } -} -@media screen and (min-width: 600px + (285px * 1) + (10px * 1)) { - .floating-action-button, - .tabs-bar__link.optional { + .navigation-panel__legal { display: none; } +} - .search-page .search { - display: none; +@media screen and (max-width: $no-gap-breakpoint - 1px) { + $sidebar-width: 285px; + + .columns-area__panels__main { + width: calc(100% - $sidebar-width); + } + + .columns-area__panels { + min-height: calc(100vh - $ui-header-height); + } + + .columns-area__panels__pane--navigational { + min-width: $sidebar-width; + + .columns-area__panels__pane__inner { + width: $sidebar-width; + } + + .navigation-panel { + margin: 0; + background: $ui-base-color; + border-left: 1px solid lighten($ui-base-color, 8%); + height: 100vh; + } + + .navigation-panel__sign-in-banner, + .navigation-panel__logo, + .getting-started__trends { + display: none; + } + + .column-link__icon { + font-size: 18px; + } + } + + .ui__header { + display: flex; + background: $ui-base-color; + border-bottom: 1px solid lighten($ui-base-color, 8%); + } + + .column-header, + .column-back-button, + .scrollable, + .error-column { + border-radius: 0 !important; } } -@media screen and (max-width: 600px + (285px * 2) + (10px * 2)) { +@media screen and (max-width: $no-gap-breakpoint - 285px - 1px) { + $sidebar-width: 55px; + + .columns-area__panels__main { + width: calc(100% - $sidebar-width); + } + .columns-area__panels__pane--navigational { - display: none; + min-width: $sidebar-width; + + .columns-area__panels__pane__inner { + width: $sidebar-width; + } + + .column-link span { + display: none; + } + + .list-panel { + display: none; + } } } -@media screen and (min-width: 600px + (285px * 2) + (10px * 2)) { - .tabs-bar { +.explore__search-header { + display: none; +} + +@media screen and (max-width: $no-gap-breakpoint - 1px) { + .columns-area__panels__pane--compositional { display: none; } + + .explore__search-header { + display: flex; + } } diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss index 9511e0c61..5e4bddc67 100644 --- a/app/javascript/flavours/glitch/styles/components/status.scss +++ b/app/javascript/flavours/glitch/styles/components/status.scss @@ -559,12 +559,10 @@ } .status__prepend { - margin-top: -10px; - margin-bottom: 10px; + margin-top: -2px; + margin-bottom: 8px; margin-left: 58px; color: $dark-text-color; - padding: 8px 0; - padding-bottom: 2px; font-size: 14px; position: relative; @@ -669,6 +667,7 @@ display: inline-block; font-weight: 500; font-size: 12px; + line-height: 17px; margin-left: 6px; } diff --git a/app/javascript/flavours/glitch/styles/containers.scss b/app/javascript/flavours/glitch/styles/containers.scss index b8d0fdad2..a3aee7eef 100644 --- a/app/javascript/flavours/glitch/styles/containers.scss +++ b/app/javascript/flavours/glitch/styles/containers.scss @@ -9,11 +9,7 @@ } .logo-container { - margin: 100px auto 50px; - - @media screen and (max-width: 500px) { - margin: 40px auto 0; - } + margin: 50px auto; h1 { display: flex; @@ -34,7 +30,6 @@ outline: 0; padding: 12px 16px; line-height: 32px; - font-family: $font-display, sans-serif; font-weight: 500; font-size: 14px; } @@ -42,7 +37,7 @@ } .compose-standalone { - .composer { + .compose-form { width: 400px; margin: 0 auto; padding: 20px 0; @@ -111,789 +106,3 @@ margin-left: 10px; } } - -.grid-3 { - display: grid; - grid-gap: 10px; - grid-template-columns: 3fr 1fr; - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-column: 1/3; - grid-row: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 2; - } - - .column-2 { - grid-column: 2; - grid-row: 2; - } - - .column-3 { - grid-column: 1/3; - grid-row: 3; - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - grid-template-columns: minmax(0, 100%); - - .column-0 { - grid-column: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - } - - .column-2 { - grid-column: 1; - grid-row: 2; - } - - .column-3 { - grid-column: 1; - grid-row: 4; - } - } -} - -.grid-4 { - display: grid; - grid-gap: 10px; - grid-template-columns: repeat(4, minmax(0, 1fr)); - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-column: 1 / 5; - grid-row: 1; - } - - .column-1 { - grid-column: 1 / 4; - grid-row: 2; - } - - .column-2 { - grid-column: 4; - grid-row: 2; - } - - .column-3 { - grid-column: 2 / 5; - grid-row: 3; - } - - .column-4 { - grid-column: 1; - grid-row: 3; - } - - .landing-page__call-to-action { - min-height: 100%; - } - - .flash-message { - margin-bottom: 10px; - } - - @media screen and (max-width: 738px) { - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - - .landing-page__call-to-action { - padding: 20px; - display: flex; - align-items: center; - justify-content: center; - } - - .row__information-board { - width: 100%; - justify-content: center; - align-items: center; - } - - .row__mascot { - display: none; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - grid-template-columns: minmax(0, 100%); - - .column-0 { - grid-column: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - } - - .column-2 { - grid-column: 1; - grid-row: 2; - } - - .column-3 { - grid-column: 1; - grid-row: 5; - } - - .column-4 { - grid-column: 1; - grid-row: 4; - } - } -} - -.public-layout { - @media screen and (max-width: $no-gap-breakpoint) { - padding-top: 48px; - } - - .container { - max-width: 960px; - - @media screen and (max-width: $no-gap-breakpoint) { - padding: 0; - } - } - - .header { - background: lighten($ui-base-color, 8%); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - height: 48px; - margin: 10px 0; - display: flex; - align-items: stretch; - justify-content: center; - flex-wrap: nowrap; - overflow: hidden; - - @media screen and (max-width: $no-gap-breakpoint) { - position: fixed; - width: 100%; - top: 0; - left: 0; - margin: 0; - border-radius: 0; - box-shadow: none; - z-index: 110; - } - - & > div { - flex: 1 1 33.3%; - min-height: 1px; - } - - .nav-left { - display: flex; - align-items: stretch; - justify-content: flex-start; - flex-wrap: nowrap; - } - - .nav-center { - display: flex; - align-items: stretch; - justify-content: center; - flex-wrap: nowrap; - } - - .nav-right { - display: flex; - align-items: stretch; - justify-content: flex-end; - flex-wrap: nowrap; - } - - .brand { - display: block; - padding: 15px; - - .logo { - display: block; - height: 18px; - width: auto; - position: relative; - bottom: -2px; - fill: $primary-text-color; - - @media screen and (max-width: $no-gap-breakpoint) { - height: 20px; - } - } - - &:hover, - &:focus, - &:active { - background: lighten($ui-base-color, 12%); - } - } - - .nav-link { - display: flex; - align-items: center; - padding: 0 1rem; - font-size: 12px; - font-weight: 500; - text-decoration: none; - color: $darker-text-color; - white-space: nowrap; - text-align: center; - - &:hover, - &:focus, - &:active { - text-decoration: underline; - color: $primary-text-color; - } - - @media screen and (max-width: 550px) { - &.optional { - display: none; - } - } - } - - .nav-button { - background: lighten($ui-base-color, 16%); - margin: 8px; - margin-left: 0; - border-radius: 4px; - - &:hover, - &:focus, - &:active { - text-decoration: none; - background: lighten($ui-base-color, 20%); - } - } - } - - $no-columns-breakpoint: 600px; - - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-row: 1; - grid-column: 1; - } - - .column-1 { - grid-row: 1; - grid-column: 2; - } - - @media screen and (max-width: $no-columns-breakpoint) { - grid-template-columns: 100%; - grid-gap: 0; - - .column-1 { - display: none; - } - } - } - - .page-header { - @media screen and (max-width: $no-gap-breakpoint) { - border-bottom: 0; - } - } - - .public-account-header { - overflow: hidden; - margin-bottom: 10px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - &.inactive { - opacity: 0.5; - - .public-account-header__image, - .avatar { - filter: grayscale(100%); - } - - .logo-button { - background-color: $secondary-text-color; - } - } - - .logo-button { - padding: 3px 15px; - } - - &__image { - border-radius: 4px 4px 0 0; - overflow: hidden; - height: 300px; - position: relative; - background: darken($ui-base-color, 12%); - - &::after { - content: ""; - display: block; - position: absolute; - width: 100%; - height: 100%; - box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); - top: 0; - left: 0; - } - - img { - object-fit: cover; - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 4px 4px 0 0; - } - - @media screen and (max-width: 600px) { - height: 200px; - } - } - - &--no-bar { - margin-bottom: 0; - - .public-account-header__image, - .public-account-header__image img { - border-radius: 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - box-shadow: none; - - &__image::after { - display: none; - } - - &__image, - &__image img { - border-radius: 0; - } - } - - &__bar { - position: relative; - margin-top: -80px; - display: flex; - justify-content: flex-start; - - &::before { - content: ""; - display: block; - background: lighten($ui-base-color, 4%); - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 60px; - border-radius: 0 0 4px 4px; - z-index: -1; - } - - .avatar { - display: block; - width: 120px; - height: 120px; - @include avatar-size(120px); - padding-left: 20px - 4px; - flex: 0 0 auto; - - img { - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 50%; - border: 4px solid lighten($ui-base-color, 4%); - background: darken($ui-base-color, 8%); - @include avatar-radius(); - } - } - - @media screen and (max-width: 600px) { - margin-top: 0; - background: lighten($ui-base-color, 4%); - border-radius: 0 0 4px 4px; - padding: 5px; - - &::before { - display: none; - } - - .avatar { - width: 48px; - height: 48px; - @include avatar-size(48px); - padding: 7px 0; - padding-left: 10px; - - img { - border: 0; - border-radius: 4px; - @include avatar-radius(); - } - - @media screen and (max-width: 360px) { - display: none; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - - @media screen and (max-width: $no-columns-breakpoint) { - flex-wrap: wrap; - } - } - - &__tabs { - flex: 1 1 auto; - margin-left: 20px; - - &__name { - padding-top: 20px; - padding-bottom: 8px; - - h1 { - font-size: 20px; - line-height: 18px * 1.5; - color: $primary-text-color; - font-weight: 500; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - text-shadow: 1px 1px 1px $base-shadow-color; - - small { - display: block; - font-size: 14px; - color: $primary-text-color; - font-weight: 400; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - @media screen and (max-width: 600px) { - margin-left: 15px; - display: flex; - justify-content: space-between; - align-items: center; - - &__name { - padding-top: 0; - padding-bottom: 0; - - h1 { - font-size: 16px; - line-height: 24px; - text-shadow: none; - - small { - color: $darker-text-color; - } - } - } - } - - &__tabs { - display: flex; - justify-content: flex-start; - align-items: stretch; - height: 58px; - - .details-counters { - display: flex; - flex-direction: row; - min-width: 300px; - } - - @media screen and (max-width: $no-columns-breakpoint) { - .details-counters { - display: none; - } - } - - .counter { - min-width: 33.3%; - box-sizing: border-box; - flex: 0 0 auto; - color: $darker-text-color; - padding: 10px; - border-right: 1px solid lighten($ui-base-color, 4%); - cursor: default; - text-align: center; - position: relative; - - a { - display: block; - } - - &:last-child { - border-right: 0; - } - - &::after { - display: block; - content: ""; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - border-bottom: 4px solid $ui-primary-color; - opacity: 0.5; - transition: all 400ms ease; - } - - &.active { - &::after { - border-bottom: 4px solid $highlight-text-color; - opacity: 1; - } - - &.inactive::after { - border-bottom-color: $secondary-text-color; - } - } - - &:hover { - &::after { - opacity: 1; - transition-duration: 100ms; - } - } - - a { - text-decoration: none; - color: inherit; - } - - .counter-label { - font-size: 12px; - display: block; - } - - .counter-number { - font-weight: 500; - font-size: 18px; - margin-bottom: 5px; - color: $primary-text-color; - font-family: $font-display, sans-serif; - } - } - - .spacer { - flex: 1 1 auto; - height: 1px; - } - - &__buttons { - padding: 7px 8px; - } - } - } - - &__extra { - display: none; - margin-top: 4px; - - .public-account-bio { - border-radius: 0; - box-shadow: none; - background: transparent; - margin: 0 -5px; - - .account__header__fields { - border-top: 1px solid lighten($ui-base-color, 12%); - } - - .roles { - display: none; - } - } - - &__links { - margin-top: -15px; - font-size: 14px; - color: $darker-text-color; - - a { - display: inline-block; - color: $darker-text-color; - text-decoration: none; - padding: 15px; - font-weight: 500; - - strong { - font-weight: 700; - color: $primary-text-color; - } - } - } - - @media screen and (max-width: $no-columns-breakpoint) { - display: block; - flex: 100%; - } - } - } - - .account__section-headline { - border-radius: 4px 4px 0 0; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - } - - .detailed-status__meta { - margin-top: 25px; - } - - .public-account-bio { - background: lighten($ui-base-color, 8%); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - overflow: hidden; - margin-bottom: 10px; - - @media screen and (max-width: $no-gap-breakpoint) { - box-shadow: none; - margin-bottom: 0; - border-radius: 0; - } - - .account__header__fields { - margin: 0; - border-top: 0; - - a { - color: $highlight-text-color; - } - - dl:first-child .verified { - border-radius: 0 4px 0 0; - } - - .verified a { - color: $valid-value-color; - } - } - - .account__header__content { - padding: 20px; - padding-bottom: 0; - color: $primary-text-color; - } - - &__extra, - .roles { - padding: 20px; - font-size: 14px; - color: $darker-text-color; - } - - .roles { - padding-bottom: 0; - } - } - - .directory__list { - display: grid; - grid-gap: 10px; - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - - .account-card { - display: flex; - flex-direction: column; - } - - @media screen and (max-width: $no-gap-breakpoint) { - display: block; - - .account-card { - margin-bottom: 10px; - display: block; - } - } - } - - .card-grid { - display: flex; - flex-wrap: wrap; - min-width: 100%; - margin: 0 -5px; - - & > div { - box-sizing: border-box; - flex: 1 0 auto; - width: 300px; - padding: 0 5px; - margin-bottom: 10px; - max-width: 33.333%; - - @media screen and (max-width: 900px) { - max-width: 50%; - } - - @media screen and (max-width: 600px) { - max-width: 100%; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin: 0; - border-top: 1px solid lighten($ui-base-color, 8%); - - & > div { - width: 100%; - padding: 0; - margin-bottom: 0; - border-bottom: 1px solid lighten($ui-base-color, 8%); - - &:last-child { - border-bottom: 0; - } - - .card__bar { - background: $ui-base-color; - - &:hover, - &:active, - &:focus { - background: lighten($ui-base-color, 4%); - } - } - } - } - } -} diff --git a/app/javascript/flavours/glitch/styles/contrast/diff.scss b/app/javascript/flavours/glitch/styles/contrast/diff.scss index 9bd31cd7e..4fa1a0361 100644 --- a/app/javascript/flavours/glitch/styles/contrast/diff.scss +++ b/app/javascript/flavours/glitch/styles/contrast/diff.scss @@ -1,73 +1,78 @@ -// components.scss +.compose-form { + .compose-form__modifiers { + .compose-form__upload { + &-description { + input { + &::placeholder { + opacity: 1; + } + } + } + } + } +} -.rich-formatting a, -.rich-formatting p a, -.rich-formatting li a, -.landing-page__short-description p a, .status__content a, -.reply-indicator__content a { - color: lighten($ui-highlight-color, 12%); +.link-footer a, +.reply-indicator__content a, +.status__content__read-more-button { text-decoration: underline; - &.mention { - text-decoration: none; - } - - &.mention span { - text-decoration: underline; - - &:hover, - &:focus, - &:active { - text-decoration: none; - } - } - &:hover, &:focus, &:active { text-decoration: none; } - &.status__content__spoiler-link { - color: $secondary-text-color; + &.mention { text-decoration: none; - } -} -.status__content__read-more-button { - text-decoration: underline; + span { + text-decoration: underline; + } - &:hover, - &:focus, - &:active { - text-decoration: none; + &:hover, + &:focus, + &:active { + span { + text-decoration: none; + } + } } } -.getting-started__footer a { - text-decoration: underline; - - &:hover, - &:focus, - &:active { - text-decoration: none; - } +.status__content a { + color: $highlight-text-color; } .nothing-here { color: $darker-text-color; } -.public-layout .public-account-header__tabs__tabs .counter.active::after { - border-bottom: 4px solid $ui-highlight-color; +.compose-form__poll-wrapper .button.button-secondary, +.compose-form .autosuggest-textarea__textarea::placeholder, +.compose-form .spoiler-input__input::placeholder, +.report-dialog-modal__textarea::placeholder, +.language-dropdown__dropdown__results__item__common-name, +.compose-form .icon-button { + color: $inverted-text-color; } -.composer { - .composer--spoiler input, - .compose-form__autosuggest-wrapper textarea { - &::placeholder { - color: $inverted-text-color; - } +.text-icon-button.active { + color: $ui-highlight-color; +} + +.language-dropdown__dropdown__results__item.active { + background: $ui-highlight-color; + font-weight: 500; +} + +.link-button:disabled { + cursor: not-allowed; + + &:hover, + &:focus, + &:active { + text-decoration: none !important; } } diff --git a/app/javascript/flavours/glitch/styles/contrast/variables.scss b/app/javascript/flavours/glitch/styles/contrast/variables.scss index ab14a7b73..e272b6ca3 100644 --- a/app/javascript/flavours/glitch/styles/contrast/variables.scss +++ b/app/javascript/flavours/glitch/styles/contrast/variables.scss @@ -14,8 +14,8 @@ $ui-highlight-color: $classic-highlight-color !default; $darker-text-color: lighten($ui-primary-color, 20%) !default; $dark-text-color: lighten($ui-primary-color, 12%) !default; $secondary-text-color: lighten($ui-secondary-color, 6%) !default; -$highlight-text-color: lighten($ui-highlight-color, 8%) !default; -$action-button-color: #8d9ac2; +$highlight-text-color: lighten($ui-highlight-color, 10%) !default; +$action-button-color: lighten($ui-base-color, 50%); $inverted-text-color: $black !default; $lighter-text-color: darken($ui-base-color,6%) !default; diff --git a/app/javascript/flavours/glitch/styles/dashboard.scss b/app/javascript/flavours/glitch/styles/dashboard.scss index 9b06b44d6..bb103e9ce 100644 --- a/app/javascript/flavours/glitch/styles/dashboard.scss +++ b/app/javascript/flavours/glitch/styles/dashboard.scss @@ -39,7 +39,6 @@ font-size: 24px; line-height: 21px; color: $primary-text-color; - font-family: $font-display, sans-serif; margin-bottom: 20px; line-height: 30px; } diff --git a/app/javascript/flavours/glitch/styles/footer.scss b/app/javascript/flavours/glitch/styles/footer.scss deleted file mode 100644 index 0c3e42033..000000000 --- a/app/javascript/flavours/glitch/styles/footer.scss +++ /dev/null @@ -1,152 +0,0 @@ -.public-layout { - .footer { - text-align: left; - padding-top: 20px; - padding-bottom: 60px; - font-size: 12px; - color: lighten($ui-base-color, 34%); - - @media screen and (max-width: $no-gap-breakpoint) { - padding-left: 20px; - padding-right: 20px; - } - - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr 1fr 2fr 1fr 1fr; - - .column-0 { - grid-column: 1; - grid-row: 1; - min-width: 0; - } - - .column-1 { - grid-column: 2; - grid-row: 1; - min-width: 0; - } - - .column-2 { - grid-column: 3; - grid-row: 1; - min-width: 0; - text-align: center; - - h4 a { - color: lighten($ui-base-color, 34%); - } - } - - .column-3 { - grid-column: 4; - grid-row: 1; - min-width: 0; - } - - .column-4 { - grid-column: 5; - grid-row: 1; - min-width: 0; - } - - @media screen and (max-width: 690px) { - grid-template-columns: 1fr 2fr 1fr; - - .column-0, - .column-1 { - grid-column: 1; - } - - .column-1 { - grid-row: 2; - } - - .column-2 { - grid-column: 2; - } - - .column-3, - .column-4 { - grid-column: 3; - } - - .column-4 { - grid-row: 2; - } - } - - @media screen and (max-width: 600px) { - .column-1 { - display: block; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - .column-0, - .column-1, - .column-3, - .column-4 { - display: none; - } - - .column-2 h4 { - display: none; - } - } - } - - .legal-xs { - display: none; - text-align: center; - padding-top: 20px; - - @media screen and (max-width: $no-gap-breakpoint) { - display: block; - } - } - - h4 { - text-transform: uppercase; - font-weight: 700; - margin-bottom: 8px; - color: $darker-text-color; - - a { - color: inherit; - text-decoration: none; - } - } - - ul a, - .legal-xs a { - text-decoration: none; - color: lighten($ui-base-color, 34%); - - &:hover, - &:active, - &:focus { - text-decoration: underline; - } - } - - .brand { - .logo { - display: block; - height: 36px; - width: auto; - margin: 0 auto; - color: lighten($ui-base-color, 34%); - } - - &:hover, - &:focus, - &:active { - .logo { - color: lighten($ui-base-color, 38%); - } - } - } - } -} diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 9ed656e13..899c59a2a 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -6,9 +6,10 @@ code { } .form-container { - max-width: 400px; + max-width: 450px; padding: 20px; - margin: 0 auto; + padding-bottom: 50px; + margin: 50px auto; } .indicator-icon { @@ -117,10 +118,22 @@ code { } .title { - color: #d9e1e8; - font-size: 20px; - line-height: 28px; - font-weight: 400; + font-size: 28px; + line-height: 33px; + font-weight: 700; + margin-bottom: 15px; + } + + .lead { + font-size: 17px; + line-height: 22px; + color: $secondary-text-color; + margin-bottom: 30px; + } + + .rules-list { + font-size: 17px; + line-height: 22px; margin-bottom: 30px; } @@ -225,7 +238,7 @@ code { & > label { font-family: inherit; - font-size: 16px; + font-size: 14px; color: $primary-text-color; display: block; font-weight: 500; @@ -262,6 +275,20 @@ code { .input:last-child { margin-bottom: 0; } + + &__thumbnail { + display: block; + margin: 0; + margin-bottom: 10px; + max-width: 100%; + height: auto; + border-radius: 4px; + background: url("images/void.png"); + + &:last-child { + margin-bottom: 0; + } + } } .fields-row { @@ -362,6 +389,7 @@ code { input[type=email], input[type=password], input[type=url], + input[type=datetime-local], textarea { box-sizing: border-box; font-size: 16px; @@ -402,7 +430,8 @@ code { input[type=text], input[type=number], input[type=email], - input[type=password] { + input[type=password], + input[type=datetime-local] { &:focus:invalid:not(:placeholder-shown), &:required:invalid:not(:placeholder-shown) { border-color: lighten($error-red, 12%); @@ -418,6 +447,7 @@ code { input[type=number], input[type=email], input[type=password], + input[type=datetime-local], textarea, select { border-color: lighten($error-red, 12%); @@ -445,6 +475,11 @@ code { } } + .stacked-actions { + margin-top: 30px; + margin-bottom: 15px; + } + button, .button, .block-button { @@ -496,6 +531,16 @@ code { } } + .button.button-tertiary { + padding: 9px; + + &:hover, + &:focus, + &:active { + padding: 10px; + } + } + select { appearance: none; box-sizing: border-box; @@ -550,41 +595,6 @@ code { } } } - - &__overlay-area { - position: relative; - - &__blurred form { - filter: blur(2px); - } - - &__overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - background: rgba($ui-base-color, 0.65); - border-radius: 4px; - margin-left: -4px; - margin-top: -4px; - padding: 4px; - - &__content { - text-align: center; - - &.rich-formatting { - &, - p { - color: $primary-text-color; - } - } - } - } - } } .block-icon { @@ -855,24 +865,7 @@ code { } } -.table-form { - p { - margin-bottom: 15px; - - strong { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - } -} - -.simple_form, -.table-form { +.simple_form { .warning { box-sizing: border-box; background: rgba($error-value-color, 0.5); diff --git a/app/javascript/flavours/glitch/styles/index.scss b/app/javascript/flavours/glitch/styles/index.scss index f808773f3..fbb02c788 100644 --- a/app/javascript/flavours/glitch/styles/index.scss +++ b/app/javascript/flavours/glitch/styles/index.scss @@ -2,7 +2,6 @@ @import 'variables'; @import 'styles/fonts/roboto'; @import 'styles/fonts/roboto-mono'; -@import 'styles/fonts/montserrat'; @import 'reset'; @import 'basics'; @@ -10,8 +9,6 @@ @import 'containers'; @import 'lists'; @import 'modal'; -@import 'footer'; -@import 'compact_header'; @import 'widgets'; @import 'forms'; @import 'accounts'; diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index d16f23aed..22828c7d1 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -1,237 +1,373 @@ // Notes! // Sass color functions, "darken" and "lighten" are automatically replaced. -.glitch.local-settings { - background: $ui-base-color; +html { + scrollbar-color: $ui-base-color rgba($ui-base-color, 0.25); +} - &__navigation { - background: darken($ui-base-color, 8%); +// Change the colors of button texts +.button { + color: $white; + + &.button-alternative-2 { + color: $white; } +} - &__navigation__item { - background: darken($ui-base-color, 8%); +.status-card__actions button, +.status-card__actions a { + color: rgba($white, 0.8); - &:hover { - background: $ui-base-color; - } + &:hover, + &:active, + &:focus { + color: $white; } } -.notification__dismiss-overlay { - .wrappy { - box-shadow: unset; - } +// Change default background colors of columns +.column > .scrollable, +.getting-started, +.column-inline-form, +.error-column, +.regeneration-indicator { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); + border-top: 0; +} - .ckbox { - text-shadow: unset; - } +.column > .scrollable.about { + border-top: 1px solid lighten($ui-base-color, 8%); } -.status.status-direct { - background: darken($ui-base-color, 8%); - border-bottom-color: darken($ui-base-color, 12%); +.about__meta, +.about__section__title, +.interaction-modal { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); +} - &.collapsed> .status__content:after { - background: linear-gradient(rgba(darken($ui-base-color, 8%), 0), rgba(darken($ui-base-color, 8%), 1)); - } +.rules-list li::before { + background: $ui-highlight-color; } -.focusable:focus.status.status-direct { - background: darken($ui-base-color, 4%); +.directory__card__img { + background: lighten($ui-base-color, 12%); +} + +.filter-form { + background: $white; + border-bottom: 1px solid lighten($ui-base-color, 8%); +} + +.column-back-button, +.column-header { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); + + @media screen and (max-width: $no-gap-breakpoint) { + border-top: 0; + } - &.collapsed> .status__content:after { - background: linear-gradient(rgba(darken($ui-base-color, 4%), 0), rgba(darken($ui-base-color, 4%), 1)); + &--slim-button { + top: -50px; + right: 0; } } -// Change columns' default background colors -.column { - > .scrollable { - background: darken($ui-base-color, 13%); +.column-header__back-button, +.column-header__button, +.column-header__button.active, +.account__header { + background: $white; +} + +.column-header__button.active { + color: $ui-highlight-color; + + &:hover, + &:active, + &:focus { + color: $ui-highlight-color; + background: $white; } } -.status.collapsed .status__content:after { - background: linear-gradient(rgba(darken($ui-base-color, 13%), 0), rgba(darken($ui-base-color, 13%), 1)); +.account__header__bar .avatar .account__avatar { + border-color: $white; } -.drawer__inner { - background: $ui-base-color; +.getting-started__footer a { + color: $ui-secondary-color; + text-decoration: underline; } -.drawer__inner__mastodon { - background: $ui-base-color url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color(darken($ui-base-color, 13%))}"/></svg>') no-repeat bottom / 100% auto !important; +.confirmation-modal__secondary-button, +.confirmation-modal__cancel-button, +.mute-modal__cancel-button, +.block-modal__cancel-button { + color: lighten($ui-base-color, 26%); - .mastodon { - filter: contrast(75%) brightness(75%) !important; + &:hover, + &:focus, + &:active { + color: $primary-text-color; } } -// Change the default appearance of the content warning button -.status__content { - - .status__content__spoiler-link { +.column-subheading { + background: darken($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 8%); +} - background: lighten($ui-base-color, 30%); +.getting-started, +.scrollable { + .column-link { + background: $white; + border-bottom: 1px solid lighten($ui-base-color, 8%); - &:hover { - background: lighten($ui-base-color, 35%); - text-decoration: none; + &:hover, + &:active, + &:focus { + background: $ui-base-color; } + } +} + +.getting-started .navigation-bar { + border-top: 1px solid lighten($ui-base-color, 8%); + border-bottom: 1px solid lighten($ui-base-color, 8%); + @media screen and (max-width: $no-gap-breakpoint) { + border-top: 0; } +} +.compose-form__autosuggest-wrapper, +.poll__option input[type="text"], +.compose-form .spoiler-input__input, +.compose-form__poll-wrapper select, +.search__input, +.setting-text, +.report-dialog-modal__textarea, +.audio-player { + border: 1px solid lighten($ui-base-color, 8%); } -// Change the background colors of media and video spoilers -.media-spoiler, -.video-player__spoiler, -.account-gallery__item a { - background: $ui-base-color; +.report-dialog-modal .dialog-option .poll__input { + color: $white; } -// Change the colors used in the dropdown menu -.dropdown-menu { - background: $ui-base-color; +.search__input { + @media screen and (max-width: $no-gap-breakpoint) { + border-top: 0; + border-bottom: 0; + } } -.dropdown-menu__arrow { +.list-editor .search .search__input { + border-top: 0; + border-bottom: 0; +} - &.left { - border-left-color: $ui-base-color; - } +.compose-form__poll-wrapper select { + background: $simple-background-color url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 8%))}'/></svg>") no-repeat right 8px center / auto 16px; +} - &.top { - border-top-color: $ui-base-color; - } +.compose-form__poll-wrapper, +.compose-form__poll-wrapper .poll__footer { + border-top-color: lighten($ui-base-color, 8%); +} - &.bottom { - border-bottom-color: $ui-base-color; - } +.notification__filter-bar { + border: 1px solid lighten($ui-base-color, 8%); + border-top: 0; +} - &.right { - border-right-color: $ui-base-color; - } +.compose-form .compose-form__buttons-wrapper { + background: $ui-base-color; + border: 1px solid lighten($ui-base-color, 8%); + border-top: 0; +} +.drawer__header, +.drawer__inner { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); } -.dropdown-menu__item { - a, - button { - background: $ui-base-color; - color: $ui-secondary-color; - } +.drawer__inner__mastodon { + background: $white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto; } -// Change the default color of several parts of the compose form -.composer { +// Change the colors used in compose-form +.compose-form { + .compose-form__modifiers { + .compose-form__upload__actions .icon-button { + color: lighten($white, 7%); - .composer--spoiler input, .compose-form__autosuggest-wrapper textarea { - color: lighten($ui-base-color, 80%); + &:active, + &:focus, + &:hover { + color: $white; + } + } - &:disabled { background: lighten($simple-background-color, 10%) } + .compose-form__upload-description input { + color: lighten($white, 7%); - &::placeholder { - color: lighten($ui-base-color, 70%); + &::placeholder { + color: lighten($white, 7%); + } } } - .composer--options-wrapper { - background: lighten($ui-base-color, 10%); + .compose-form__buttons-wrapper { + background: darken($ui-base-color, 6%); } - .composer--options > hr { - display: none; + .autosuggest-textarea__suggestions { + background: darken($ui-base-color, 6%); } - .composer--options--dropdown--content--item { - color: $ui-primary-color; - - strong { - color: $ui-primary-color; + .autosuggest-textarea__suggestions__item { + &:hover, + &:focus, + &:active, + &.selected { + background: lighten($ui-base-color, 4%); } - } - } -.composer--upload_form--actions .icon-button { - color: lighten($white, 7%); +.emoji-mart-bar { + border-color: lighten($ui-base-color, 4%); - &:active, - &:focus, - &:hover { - color: $white; + &:first-child { + background: darken($ui-base-color, 6%); } } -.language-dropdown__dropdown__results__item:hover, -.language-dropdown__dropdown__results__item:focus, -.language-dropdown__dropdown__results__item:active { - background-color: $ui-base-color; +.emoji-mart-search input { + background: rgba($ui-base-color, 0.3); + border-color: $ui-base-color; } -.dropdown-menu__separator, -.dropdown-menu__item.edited-timestamp__history__item, -.dropdown-menu__container__header, -.compare-history-modal .report-modal__target, -.report-dialog-modal .poll__option.dialog-option { - border-bottom-color: lighten($ui-base-color, 12%); +// Change the background colors of statuses +.focusable:focus { + background: $ui-base-color; } -.report-dialog-modal__container { - border-bottom-color: lighten($ui-base-color, 12%); +.detailed-status, +.detailed-status__action-bar { + background: $white; } -.status__content, -.reply-indicator__content { - a { - color: $highlight-text-color; +// Change the background colors of status__content__spoiler-link +.reply-indicator__content .status__content__spoiler-link, +.status__content .status__content__spoiler-link { + background: $ui-base-color; + + &:hover, + &:focus { + background: lighten($ui-base-color, 4%); } } -.emoji-mart-bar { - border-color: darken($ui-base-color, 4%); - - &:first-child { - background: lighten($ui-base-color, 10%); - } +// Change the background colors of media and video spoilers +.media-spoiler, +.video-player__spoiler { + background: $ui-base-color; } -.emoji-mart-search input { - background: rgba($ui-base-color, 0.3); - border-color: $ui-base-color; +.privacy-dropdown.active .privacy-dropdown__value.active .icon-button { + color: $white; } -.autosuggest-textarea__suggestions { - background: lighten($ui-base-color, 10%) +.account-gallery__item a { + background-color: $ui-base-color; } -.autosuggest-textarea__suggestions__item { - &:hover, - &:focus, - &:active, - &.selected { - background: darken($ui-base-color, 4%); +// Change the colors used in the dropdown menu +.dropdown-menu { + background: $white; + + &__arrow { + &.left { + border-left-color: $white; + } + + &.top { + border-top-color: $white; + } + + &.bottom { + border-bottom-color: $white; + } + + &.right { + border-right-color: $white; + } + } + + &__item { + a, + button { + background: $white; + color: $darker-text-color; + } } } -.react-toggle-track { - background: $ui-secondary-color; +// Change the text colors on inverted background +.privacy-dropdown__option.active, +.privacy-dropdown__option:hover, +.privacy-dropdown__option.active .privacy-dropdown__option__content, +.privacy-dropdown__option.active .privacy-dropdown__option__content strong, +.privacy-dropdown__option:hover .privacy-dropdown__option__content, +.privacy-dropdown__option:hover .privacy-dropdown__option__content strong, +.dropdown-menu__item a:active, +.dropdown-menu__item a:focus, +.dropdown-menu__item a:hover, +.actions-modal ul li:not(:empty) a.active, +.actions-modal ul li:not(:empty) a.active button, +.actions-modal ul li:not(:empty) a:active, +.actions-modal ul li:not(:empty) a:active button, +.actions-modal ul li:not(:empty) a:focus, +.actions-modal ul li:not(:empty) a:focus button, +.actions-modal ul li:not(:empty) a:hover, +.actions-modal ul li:not(:empty) a:hover button, +.language-dropdown__dropdown__results__item.active, +.admin-wrapper .sidebar ul .simple-navigation-active-leaf a, +.simple_form .block-button, +.simple_form .button, +.simple_form button { + color: $white; } -.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { - background: lighten($ui-secondary-color, 10%); +.language-dropdown__dropdown__results__item .language-dropdown__dropdown__results__item__common-name { + color: lighten($ui-base-color, 8%); } -.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track { - background: darken($ui-highlight-color, 10%); +.language-dropdown__dropdown__results__item.active .language-dropdown__dropdown__results__item__common-name { + color: darken($ui-base-color, 12%); +} + +.dropdown-menu__separator, +.dropdown-menu__item.edited-timestamp__history__item, +.dropdown-menu__container__header, +.compare-history-modal .report-modal__target, +.report-dialog-modal .poll__option.dialog-option { + border-bottom-color: lighten($ui-base-color, 4%); +} + +.report-dialog-modal__container { + border-top-color: lighten($ui-base-color, 4%); } // Change the background colors of modals .actions-modal, .boost-modal, -.favourite-modal, .confirmation-modal, .mute-modal, .block-modal, @@ -243,12 +379,43 @@ .compare-history-modal, .report-modal__comment .setting-text__wrapper, .report-modal__comment .setting-text, -.report-dialog-modal__textarea { +.announcements, +.picture-in-picture__header, +.picture-in-picture__footer, +.reactions-bar__item { background: $white; border: 1px solid lighten($ui-base-color, 8%); } -.report-dialog-modal .dialog-option .poll__input { +.reactions-bar__item:hover, +.reactions-bar__item:focus, +.reactions-bar__item:active, +.language-dropdown__dropdown__results__item:hover, +.language-dropdown__dropdown__results__item:focus, +.language-dropdown__dropdown__results__item:active { + background-color: $ui-base-color; +} + +.reactions-bar__item.active { + background-color: mix($white, $ui-highlight-color, 80%); + border-color: mix(lighten($ui-base-color, 8%), $ui-highlight-color, 80%); +} + +.media-modal__overlay .picture-in-picture__footer { + border: 0; +} + +.picture-in-picture__header { + border-bottom: 0; +} + +.announcements, +.picture-in-picture__footer { + border-top: 0; +} + +.icon-with-badge__badge { + border-color: $white; color: $white; } @@ -260,8 +427,43 @@ border-top-color: lighten($ui-base-color, 8%); } +.column-header__collapsible-inner { + background: darken($ui-base-color, 4%); + border: 1px solid lighten($ui-base-color, 8%); + border-top: 0; +} + +.dashboard__quick-access, +.focal-point__preview strong, +.admin-wrapper .content__heading__tabs a.selected { + color: $white; +} + +.button.button-tertiary { + &:hover, + &:focus, + &:active { + color: $white; + } +} + +.button.button-secondary { + border-color: $darker-text-color; + color: $darker-text-color; + + &:hover, + &:focus, + &:active { + border-color: darken($darker-text-color, 8%); + color: darken($darker-text-color, 8%); + } +} + +.flash-message.warning { + color: lighten($gold-star, 16%); +} + .boost-modal__action-bar, -.favourite-modal__action-bar, .confirmation-modal__action-bar, .mute-modal__action-bar, .block-modal__action-bar, @@ -279,33 +481,134 @@ } } +.display-case__case { + background: $white; +} + +.embed-modal .embed-modal__container .embed-modal__html { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); + + &:focus { + border-color: lighten($ui-base-color, 12%); + background: $white; + } +} + +.react-toggle-track { + background: $ui-secondary-color; +} + +.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track { + background: darken($ui-secondary-color, 10%); +} + +.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track { + background: lighten($ui-highlight-color, 10%); +} + // Change the default color used for the text in an empty column or on the error column .empty-column-indicator, .error-column { - color: lighten($ui-base-color, 60%); + color: $primary-text-color; + background: $white; } // Change the default colors used on some parts of the profile pages .activity-stream-tabs { - background: $account-background-color; + border-bottom-color: lighten($ui-base-color, 8%); +} - a { - &.active { - color: $ui-primary-color; - } +.nothing-here, +.page-header, +.directory__tag > a, +.directory__tag > div { + background: $white; + border: 1px solid lighten($ui-base-color, 8%); + + @media screen and (max-width: $no-gap-breakpoint) { + border-left: 0; + border-right: 0; + border-top: 0; + } +} + +.simple_form { + input[type="text"], + input[type="number"], + input[type="email"], + input[type="password"], + textarea { + &:hover { + border-color: lighten($ui-base-color, 12%); + } + } +} + +.picture-in-picture-placeholder { + background: $white; + border-color: lighten($ui-base-color, 8%); + color: lighten($ui-base-color, 8%); +} + +.directory__tag > a { + &:hover, + &:active, + &:focus { + background: $ui-base-color; + } + + @media screen and (max-width: $no-gap-breakpoint) { + border: 0; + } +} + +.directory__tag.active > a, +.directory__tag.active > div { + border-color: $ui-highlight-color; + + &, + h4, + h4 small, + .fa, + .trends__item__current { + color: $white; } + &:hover, + &:active, + &:focus { + background: $ui-highlight-color; + } +} + +.batch-table { + &__toolbar, + &__row, + .nothing-here { + border-color: lighten($ui-base-color, 8%); + } } .activity-stream { + border: 1px solid lighten($ui-base-color, 8%); + + &--under-tabs { + border-top: 0; + } .entry { background: $account-background-color; + + .detailed-status.light, + .more.light, + .status.light { + border-bottom-color: lighten($ui-base-color, 8%); + } } .status.light { - .status__content { color: $primary-text-color; } @@ -315,17 +618,14 @@ color: $primary-text-color; } } - } - } .accounts-grid { .account-grid-card { - .controls { .icon-button { - color: $ui-secondary-color; + color: $darker-text-color; } } @@ -336,69 +636,79 @@ } .username { - color: $ui-secondary-color; + color: $darker-text-color; } .account__header__content { color: $primary-text-color; } - } } -.button.logo-button { - color: $white; +.simple_form { + .warning { + box-shadow: none; + background: rgba($error-red, 0.5); + text-shadow: none; + } - svg { - fill: $white; + .recommended { + border-color: $ui-highlight-color; + color: $ui-highlight-color; + background-color: rgba($ui-highlight-color, 0.1); } } -.public-layout { - .header, - .public-account-header, - .public-account-bio { - box-shadow: none; - } +.compose-form .compose-form__warning { + border-color: $ui-highlight-color; + background-color: rgba($ui-highlight-color, 0.1); - .header { - background: lighten($ui-base-color, 12%); + &, + a { + color: $ui-highlight-color; } +} - .public-account-header { - &__image { - background: lighten($ui-base-color, 12%); +.reply-indicator { + background: transparent; + border: 1px solid lighten($ui-base-color, 8%); +} - &::after { - box-shadow: none; - } - } +.dismissable-banner { + border-left: 1px solid lighten($ui-base-color, 8%); + border-right: 1px solid lighten($ui-base-color, 8%); +} - &__tabs { - &__name { - h1, - h1 small { - color: $white; - } - } - } +.status__content, +.reply-indicator__content { + a { + color: $highlight-text-color; + } +} + +.button.logo-button { + color: $white; + + svg { + fill: $white; } } +.notification__filter-bar button.active::after, .account__section-headline a.active::after { border-color: transparent transparent $white; } .hero-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget, .moved-account-widget, .memoriam-widget, .activity-stream, .nothing-here, .directory__tag > a, -.directory__tag > div { +.directory__tag > div, +.card > a, +.page-header, +.compose-form .compose-form__warning { box-shadow: none; } @@ -406,3 +716,55 @@ border: 1px solid lighten($ui-base-color, 8%); background: $simple-background-color url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 8%))}'/></svg>") no-repeat right 8px center / auto 16px; } + +// Glitch-soc-specific changes + +.glitch.local-settings { + background: $ui-base-color; + border: 1px solid lighten($ui-base-color, 8%); +} + +.glitch.local-settings__navigation { + background: darken($ui-base-color, 8%); +} + +.glitch.local-settings__navigation__item { + background: darken($ui-base-color, 8%); + border-bottom: 1px lighten($ui-base-color, 8%) solid; + + &:hover { + background: $ui-base-color; + } + + &.active { + background: $ui-highlight-color; + color: $white; + } + + &.close, &.close:hover { + background: $error-value-color; + color: $primary-text-color; + } +} + +.notification__dismiss-overlay { + .wrappy { + box-shadow: unset; + + .ckbox { + text-shadow: unset; + } + } +} + +.status.collapsed .status__content:after { + background: linear-gradient(rgba(darken($ui-base-color, 13%), 0), rgba(darken($ui-base-color, 13%), 1)); +} + +.drawer__inner__mastodon { + background: $white url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto !important; + + .mastodon { + filter: contrast(75%) brightness(75%) !important; + } +} diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss b/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss index f1c8a3503..cae065878 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss @@ -7,11 +7,17 @@ $classic-primary-color: #9baec8; $classic-secondary-color: #d9e1e8; $classic-highlight-color: #6364ff; +// Differences +$success-green: lighten(#3c754d, 8%); + +$base-overlay-background: $white !default; +$valid-value-color: $success-green !default; + $ui-base-color: $classic-secondary-color !default; -$ui-base-lighter-color: darken($ui-base-color, 57%); -$ui-highlight-color: $classic-highlight-color !default; -$ui-primary-color: $classic-primary-color !default; +$ui-base-lighter-color: #b0c0cf; +$ui-primary-color: #9bcbed; $ui-secondary-color: $classic-base-color !default; +$ui-highlight-color: $classic-highlight-color !default; $primary-text-color: $black !default; $darker-text-color: $classic-base-color !default; @@ -19,17 +25,14 @@ $highlight-text-color: darken($ui-highlight-color, 8%) !default; $dark-text-color: #444b5d; $action-button-color: #606984; -$success-green: lighten(#3c754d, 8%); - -$base-overlay-background: $white !default; - $inverted-text-color: $black !default; $lighter-text-color: $classic-base-color !default; $light-text-color: #444b5d; +// Newly added colors $account-background-color: $white !default; -//Invert darkened and lightened colors +// Invert darkened and lightened colors @function darken($color, $amount) { @return hsl(hue($color), saturation($color), lightness($color) + $amount); } diff --git a/app/javascript/flavours/glitch/styles/rtl.scss b/app/javascript/flavours/glitch/styles/rtl.scss index d0153c9f9..c14c07cb9 100644 --- a/app/javascript/flavours/glitch/styles/rtl.scss +++ b/app/javascript/flavours/glitch/styles/rtl.scss @@ -30,33 +30,18 @@ body.rtl { right: -26px; } - .landing-page__logo { - margin-right: 0; - margin-left: 20px; - } - - .landing-page .features-list .features-list__row .visual { - margin-left: 0; - margin-right: 15px; - } - .column-link__icon, .column-header__icon { margin-right: 0; margin-left: 5px; } - .composer .compose--counter-wrapper { + .compose-form .character-counter__wrapper { margin-right: 0; margin-left: 4px; } - .composer--publisher { - text-align: left; - } - - .boost-modal__status-time, - .favourite-modal__status-time { + .boost-modal__status-time { float: left; } @@ -327,44 +312,6 @@ body.rtl { margin-left: 45px; } - .landing-page .header-wrapper .mascot { - right: 60px; - left: auto; - } - - .landing-page__call-to-action .row__information-board { - direction: rtl; - } - - .landing-page .header .hero .floats .float-1 { - left: -120px; - right: auto; - } - - .landing-page .header .hero .floats .float-2 { - left: 210px; - right: auto; - } - - .landing-page .header .hero .floats .float-3 { - left: 110px; - right: auto; - } - - .landing-page .header .links .brand img { - left: 0; - } - - .landing-page .fa-external-link { - padding-right: 5px; - padding-left: 0 !important; - } - - .landing-page .features #mastodon-timeline { - margin-right: 0; - margin-left: 30px; - } - @media screen and (min-width: 631px) { .column, .drawer { @@ -392,32 +339,6 @@ body.rtl { padding-right: 0; } - .public-layout { - .header { - .nav-button { - margin-left: 8px; - margin-right: 0; - } - } - - .public-account-header__tabs { - margin-left: 0; - margin-right: 20px; - } - } - - .landing-page__information { - .account__display-name { - margin-right: 0; - margin-left: 5px; - } - - .account__avatar-wrapper { - margin-left: 12px; - margin-right: 0; - } - } - .card__bar .display-name { margin-left: 0; margin-right: 15px; diff --git a/app/javascript/flavours/glitch/styles/statuses.scss b/app/javascript/flavours/glitch/styles/statuses.scss index c302fc0d0..947a5d3ae 100644 --- a/app/javascript/flavours/glitch/styles/statuses.scss +++ b/app/javascript/flavours/glitch/styles/statuses.scss @@ -133,8 +133,7 @@ a.button.logo-button { justify-content: center; } -.embed, -.public-layout { +.embed { .status__content[data-spoiler=folded] { .e-content { display: none; @@ -204,8 +203,7 @@ a.button.logo-button { } // Styling from upstream's WebUI, as public pages use the same layout -.embed, -.public-layout { +.embed { .status { .status__info { font-size: 15px; @@ -244,8 +242,7 @@ a.button.logo-button { } .rtl { - .embed, - .public-layout { + .embed { .status { padding-left: 10px; padding-right: 68px; diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss index 598c67034..919255790 100644 --- a/app/javascript/flavours/glitch/styles/tables.scss +++ b/app/javascript/flavours/glitch/styles/tables.scss @@ -190,6 +190,55 @@ a.table-action-link { } } + &__select-all { + background: $ui-base-color; + height: 47px; + align-items: center; + justify-content: center; + border: 1px solid darken($ui-base-color, 8%); + border-top: 0; + color: $secondary-text-color; + display: none; + + &.active { + display: flex; + } + + .selected, + .not-selected { + display: none; + + &.active { + display: block; + } + } + + strong { + font-weight: 700; + } + + span { + padding: 8px; + display: inline-block; + } + + button { + background: transparent; + border: 0; + font: inherit; + color: $highlight-text-color; + border-radius: 4px; + font-weight: 700; + padding: 8px; + + &:hover, + &:focus, + &:active { + background: lighten($ui-base-color, 8%); + } + } + } + &__form { padding: 16px; border: 1px solid darken($ui-base-color, 8%); diff --git a/app/javascript/flavours/glitch/styles/variables.scss b/app/javascript/flavours/glitch/styles/variables.scss index 65758e6e0..b865b5a2d 100644 --- a/app/javascript/flavours/glitch/styles/variables.scss +++ b/app/javascript/flavours/glitch/styles/variables.scss @@ -51,7 +51,7 @@ $media-modal-media-max-width: 100%; // put margins on top and bottom of image to avoid the screen covered by image. $media-modal-media-max-height: 80%; -$no-gap-breakpoint: 415px; +$no-gap-breakpoint: 1175px; $font-sans-serif: 'mastodon-font-sans-serif' !default; $font-display: 'mastodon-font-display' !default; diff --git a/app/javascript/flavours/glitch/styles/widgets.scss b/app/javascript/flavours/glitch/styles/widgets.scss index a88f3b2c7..fd091ee89 100644 --- a/app/javascript/flavours/glitch/styles/widgets.scss +++ b/app/javascript/flavours/glitch/styles/widgets.scss @@ -108,13 +108,6 @@ } } -.box-widget { - padding: 20px; - border-radius: 4px; - background: $ui-base-color; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); -} - .placeholder-widget { padding: 16px; border-radius: 4px; @@ -124,47 +117,6 @@ margin-bottom: 10px; } -.contact-widget { - min-height: 100%; - font-size: 15px; - color: $darker-text-color; - line-height: 20px; - word-wrap: break-word; - font-weight: 400; - padding: 0; - - h4 { - padding: 10px; - text-transform: uppercase; - font-weight: 700; - font-size: 13px; - color: $darker-text-color; - } - - .account { - border-bottom: 0; - padding: 10px 0; - padding-top: 5px; - } - - & > a { - display: inline-block; - padding: 10px; - padding-top: 0; - color: $darker-text-color; - text-decoration: none; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } -} - .moved-account-widget { padding: 15px; padding-bottom: 20px; @@ -245,37 +197,6 @@ margin-bottom: 10px; } -.page-header { - background: lighten($ui-base-color, 8%); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - padding: 60px 15px; - text-align: center; - margin: 10px 0; - - h1 { - color: $primary-text-color; - font-size: 36px; - line-height: 1.1; - font-weight: 700; - margin-bottom: 10px; - } - - p { - font-size: 15px; - color: $darker-text-color; - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin-top: 0; - background: lighten($ui-base-color, 4%); - - h1 { - font-size: 24px; - } - } -} - .directory { background: $ui-base-color; border-radius: 4px; @@ -357,34 +278,6 @@ } } -.avatar-stack { - display: flex; - justify-content: flex-end; - - .account__avatar { - flex: 0 0 auto; - width: 36px; - height: 36px; - border-radius: 50%; - position: relative; - margin-left: -10px; - background: darken($ui-base-color, 8%); - border: 2px solid $ui-base-color; - - &:nth-child(1) { - z-index: 1; - } - - &:nth-child(2) { - z-index: 2; - } - - &:nth-child(3) { - z-index: 3; - } - } -} - .accounts-table { width: 100%; @@ -486,11 +379,7 @@ .moved-account-widget, .memoriam-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget, -.directory, -.page-header { +.directory { @media screen and (max-width: $no-gap-breakpoint) { margin-bottom: 0; box-shadow: none; @@ -498,88 +387,6 @@ } } -$maximum-width: 1235px; -$fluid-breakpoint: $maximum-width + 20px; - -.statuses-grid { - min-height: 600px; - - @media screen and (max-width: 640px) { - width: 100% !important; // Masonry layout is unnecessary at this width - } - - &__item { - width: math.div(960px - 20px, 3); - - @media screen and (max-width: $fluid-breakpoint) { - width: math.div(940px - 20px, 3); - } - - @media screen and (max-width: 640px) { - width: 100%; - } - - @media screen and (max-width: $no-gap-breakpoint) { - width: 100vw; - } - } - - .detailed-status { - border-radius: 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 1px solid lighten($ui-base-color, 16%); - } - - &.compact { - .detailed-status__meta { - margin-top: 15px; - } - - .status__content { - font-size: 15px; - line-height: 20px; - - .emojione { - width: 20px; - height: 20px; - margin: -3px 0 0; - } - - .status__content__spoiler-link { - line-height: 20px; - margin: 0; - } - } - - .media-gallery, - .status-card, - .video-player { - margin-top: 15px; - } - } - } -} - -.notice-widget { - margin-bottom: 10px; - color: $darker-text-color; - - p { - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - } - - a { - font-size: 14px; - line-height: 20px; - } -} - -.notice-widget, .placeholder-widget { a { text-decoration: none; @@ -593,37 +400,3 @@ $fluid-breakpoint: $maximum-width + 20px; } } } - -.table-of-contents { - background: darken($ui-base-color, 4%); - min-height: 100%; - font-size: 14px; - border-radius: 4px; - - li a { - display: block; - font-weight: 500; - padding: 15px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-decoration: none; - color: $primary-text-color; - border-bottom: 1px solid lighten($ui-base-color, 4%); - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } - - li:last-child a { - border-bottom: 0; - } - - li ul { - padding-left: 20px; - border-bottom: 1px solid lighten($ui-base-color, 4%); - } -} diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index f3c7fac7e..e85dd74e1 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -1,6 +1,5 @@ # (REQUIRED) The location of the pack files. pack: - about: packs/about.js admin: - packs/admin.js - packs/public.js diff --git a/app/javascript/flavours/glitch/util/api.js b/app/javascript/flavours/glitch/util/api.js deleted file mode 100644 index 90d8465ef..000000000 --- a/app/javascript/flavours/glitch/util/api.js +++ /dev/null @@ -1,52 +0,0 @@ -import axios from 'axios'; -import ready from './ready'; -import LinkHeader from 'http-link-header'; - -export const getLinks = response => { - const value = response.headers.link; - - if (!value) { - return { refs: [] }; - } - - return LinkHeader.parse(value); -}; - -const csrfHeader = {}; - -const setCSRFHeader = () => { - const csrfToken = document.querySelector('meta[name=csrf-token]'); - - if (csrfToken) { - csrfHeader['X-CSRF-Token'] = csrfToken.content; - } -}; - -ready(setCSRFHeader); - -const authorizationHeaderFromState = getState => { - const accessToken = getState && getState().getIn(['meta', 'access_token'], ''); - - if (!accessToken) { - return {}; - } - - return { - 'Authorization': `Bearer ${accessToken}`, - }; -}; - -export default getState => axios.create({ - headers: { - ...csrfHeader, - ...authorizationHeaderFromState(getState), - }, - - transformResponse: [function (data) { - try { - return JSON.parse(data); - } catch(Exception) { - return data; - } - }], -}); diff --git a/app/javascript/flavours/glitch/util/emoji/emoji_map.json b/app/javascript/flavours/glitch/util/emoji/emoji_map.json deleted file mode 100644 index 121fea2a5..000000000 --- a/app/javascript/flavours/glitch/util/emoji/emoji_map.json +++ /dev/null @@ -1 +0,0 @@ -{"๐":"1f600","๐":"1f603","๐":"1f604","๐":"1f601","๐":"1f606","๐ ":"1f605","๐คฃ":"1f923","๐":"1f602","๐":"1f642","๐":"1f643","๐":"1f609","๐":"1f60a","๐":"1f607","๐ฅฐ":"1f970","๐":"1f60d","๐คฉ":"1f929","๐":"1f618","๐":"1f617","โบ":"263a","๐":"1f61a","๐":"1f619","๐ฅฒ":"1f972","๐":"1f60b","๐":"1f61b","๐":"1f61c","๐คช":"1f92a","๐":"1f61d","๐ค":"1f911","๐ค":"1f917","๐คญ":"1f92d","๐คซ":"1f92b","๐ค":"1f914","๐ค":"1f910","๐คจ":"1f928","๐":"1f610","๐":"1f611","๐ถ":"1f636","๐":"1f60f","๐":"1f612","๐":"1f644","๐ฌ":"1f62c","๐คฅ":"1f925","๐":"1f60c","๐":"1f614","๐ช":"1f62a","๐คค":"1f924","๐ด":"1f634","๐ท":"1f637","๐ค":"1f912","๐ค":"1f915","๐คข":"1f922","๐คฎ":"1f92e","๐คง":"1f927","๐ฅต":"1f975","๐ฅถ":"1f976","๐ฅด":"1f974","๐ต":"1f635","๐คฏ":"1f92f","๐ค ":"1f920","๐ฅณ":"1f973","๐ฅธ":"1f978","๐":"1f60e","๐ค":"1f913","๐ง":"1f9d0","๐":"1f615","๐":"1f61f","๐":"1f641","โน":"2639","๐ฎ":"1f62e","๐ฏ":"1f62f","๐ฒ":"1f632","๐ณ":"1f633","๐ฅบ":"1f97a","๐ฆ":"1f626","๐ง":"1f627","๐จ":"1f628","๐ฐ":"1f630","๐ฅ":"1f625","๐ข":"1f622","๐ญ":"1f62d","๐ฑ":"1f631","๐":"1f616","๐ฃ":"1f623","๐":"1f61e","๐":"1f613","๐ฉ":"1f629","๐ซ":"1f62b","๐ฅฑ":"1f971","๐ค":"1f624","๐ก":"1f621","๐ ":"1f620","๐คฌ":"1f92c","๐":"1f608","๐ฟ":"1f47f","๐":"1f480","โ ":"2620","๐ฉ":"1f4a9","๐คก":"1f921","๐น":"1f479","๐บ":"1f47a","๐ป":"1f47b","๐ฝ":"1f47d","๐พ":"1f47e","๐ค":"1f916","๐บ":"1f63a","๐ธ":"1f638","๐น":"1f639","๐ป":"1f63b","๐ผ":"1f63c","๐ฝ":"1f63d","๐":"1f640","๐ฟ":"1f63f","๐พ":"1f63e","๐":"1f648","๐":"1f649","๐":"1f64a","๐":"1f48b","๐":"1f48c","๐":"1f498","๐":"1f49d","๐":"1f496","๐":"1f497","๐":"1f493","๐":"1f49e","๐":"1f495","๐":"1f49f","โฃ":"2763","๐":"1f494","โค":"2764","๐งก":"1f9e1","๐":"1f49b","๐":"1f49a","๐":"1f499","๐":"1f49c","๐ค":"1f90e","๐ค":"1f5a4","๐ค":"1f90d","๐ฏ":"1f4af","๐ข":"1f4a2","๐ฅ":"1f4a5","๐ซ":"1f4ab","๐ฆ":"1f4a6","๐จ":"1f4a8","๐ณ":"1f573","๐ฃ":"1f4a3","๐ฌ":"1f4ac","๐จ":"1f5e8","๐ฏ":"1f5ef","๐ญ":"1f4ad","๐ค":"1f4a4","๐":"1f44b","๐ค":"1f91a","๐":"1f590","โ":"270b","๐":"1f596","๐":"1f44c","๐ค":"1f90c","๐ค":"1f90f","โ":"270c","๐ค":"1f91e","๐ค":"1f91f","๐ค":"1f918","๐ค":"1f919","๐":"1f448","๐":"1f449","๐":"1f446","๐":"1f595","๐":"1f447","โ":"261d","๐":"1f44d","๐":"1f44e","โ":"270a","๐":"1f44a","๐ค":"1f91b","๐ค":"1f91c","๐":"1f44f","๐":"1f64c","๐":"1f450","๐คฒ":"1f932","๐ค":"1f91d","๐":"1f64f","โ":"270d","๐ ":"1f485","๐คณ":"1f933","๐ช":"1f4aa","๐ฆพ":"1f9be","๐ฆฟ":"1f9bf","๐ฆต":"1f9b5","๐ฆถ":"1f9b6","๐":"1f442","๐ฆป":"1f9bb","๐":"1f443","๐ง ":"1f9e0","๐ซ":"1fac0","๐ซ":"1fac1","๐ฆท":"1f9b7","๐ฆด":"1f9b4","๐":"1f440","๐":"1f441","๐ ":"1f445","๐":"1f444","๐ถ":"1f476","๐ง":"1f9d2","๐ฆ":"1f466","๐ง":"1f467","๐ง":"1f9d1","๐ฑ":"1f471","๐จ":"1f468","๐ง":"1f9d4","๐ฉ":"1f469","๐ง":"1f9d3","๐ด":"1f474","๐ต":"1f475","๐":"1f64d","๐":"1f64e","๐ ":"1f645","๐":"1f646","๐":"1f481","๐":"1f64b","๐ง":"1f9cf","๐":"1f647","๐คฆ":"1f926","๐คท":"1f937","๐ฎ":"1f46e","๐ต":"1f575","๐":"1f482","๐ฅท":"1f977","๐ท":"1f477","๐คด":"1f934","๐ธ":"1f478","๐ณ":"1f473","๐ฒ":"1f472","๐ง":"1f9d5","๐คต":"1f935","๐ฐ":"1f470","๐คฐ":"1f930","๐คฑ":"1f931","๐ผ":"1f47c","๐ ":"1f385","๐คถ":"1f936","๐ฆธ":"1f9b8","๐ฆน":"1f9b9","๐ง":"1f9d9","๐ง":"1f9da","๐ง":"1f9db","๐ง":"1f9dc","๐ง":"1f9dd","๐ง":"1f9de","๐ง":"1f9df","๐":"1f486","๐":"1f487","๐ถ":"1f6b6","๐ง":"1f9cd","๐ง":"1f9ce","๐":"1f3c3","๐":"1f483","๐บ":"1f57a","๐ด":"1f574","๐ฏ":"1f46f","๐ง":"1f9d6","๐ง":"1f9d7","๐คบ":"1f93a","๐":"1f3c7","โท":"26f7","๐":"1f3c2","๐":"1f3cc","๐":"1f3c4","๐ฃ":"1f6a3","๐":"1f3ca","โน":"26f9","๐":"1f3cb","๐ด":"1f6b4","๐ต":"1f6b5","๐คธ":"1f938","๐คผ":"1f93c","๐คฝ":"1f93d","๐คพ":"1f93e","๐คน":"1f939","๐ง":"1f9d8","๐":"1f6c0","๐":"1f6cc","๐ญ":"1f46d","๐ซ":"1f46b","๐ฌ":"1f46c","๐":"1f48f","๐":"1f491","๐ช":"1f46a","๐ฃ":"1f5e3","๐ค":"1f464","๐ฅ":"1f465","๐ซ":"1fac2","๐ฃ":"1f463","๐ป":"1f463","๐ผ":"1f463","๐ฝ":"1f463","๐พ":"1f463","๐ฟ":"1f463","๐ฆฐ":"1f463","๐ฆฑ":"1f463","๐ฆณ":"1f463","๐ฆฒ":"1f463","๐ต":"1f435","๐":"1f412","๐ฆ":"1f98d","๐ฆง":"1f9a7","๐ถ":"1f436","๐":"1f415","๐ฆฎ":"1f9ae","๐ฉ":"1f429","๐บ":"1f43a","๐ฆ":"1f98a","๐ฆ":"1f99d","๐ฑ":"1f431","๐":"1f408","๐ฆ":"1f981","๐ฏ":"1f42f","๐ ":"1f405","๐":"1f406","๐ด":"1f434","๐":"1f40e","๐ฆ":"1f984","๐ฆ":"1f993","๐ฆ":"1f98c","๐ฆฌ":"1f9ac","๐ฎ":"1f42e","๐":"1f402","๐":"1f403","๐":"1f404","๐ท":"1f437","๐":"1f416","๐":"1f417","๐ฝ":"1f43d","๐":"1f40f","๐":"1f411","๐":"1f410","๐ช":"1f42a","๐ซ":"1f42b","๐ฆ":"1f999","๐ฆ":"1f992","๐":"1f418","๐ฆฃ":"1f9a3","๐ฆ":"1f98f","๐ฆ":"1f99b","๐ญ":"1f42d","๐":"1f401","๐":"1f400","๐น":"1f439","๐ฐ":"1f430","๐":"1f407","๐ฟ":"1f43f","๐ฆซ":"1f9ab","๐ฆ":"1f994","๐ฆ":"1f987","๐ป":"1f43b","๐จ":"1f428","๐ผ":"1f43c","๐ฆฅ":"1f9a5","๐ฆฆ":"1f9a6","๐ฆจ":"1f9a8","๐ฆ":"1f998","๐ฆก":"1f9a1","๐พ":"1f43e","๐ฆ":"1f983","๐":"1f414","๐":"1f413","๐ฃ":"1f423","๐ค":"1f424","๐ฅ":"1f425","๐ฆ":"1f426","๐ง":"1f427","๐":"1f54a","๐ฆ ":"1f985","๐ฆ":"1f986","๐ฆข":"1f9a2","๐ฆ":"1f989","๐ฆค":"1f9a4","๐ชถ":"1fab6","๐ฆฉ":"1f9a9","๐ฆ":"1f99a","๐ฆ":"1f99c","๐ธ":"1f438","๐":"1f40a","๐ข":"1f422","๐ฆ":"1f98e","๐":"1f40d","๐ฒ":"1f432","๐":"1f409","๐ฆ":"1f995","๐ฆ":"1f996","๐ณ":"1f433","๐":"1f40b","๐ฌ":"1f42c","๐ฆญ":"1f9ad","๐":"1f41f","๐ ":"1f420","๐ก":"1f421","๐ฆ":"1f988","๐":"1f419","๐":"1f41a","๐":"1f40c","๐ฆ":"1f98b","๐":"1f41b","๐":"1f41c","๐":"1f41d","๐ชฒ":"1fab2","๐":"1f41e","๐ฆ":"1f997","๐ชณ":"1fab3","๐ท":"1f577","๐ธ":"1f578","๐ฆ":"1f982","๐ฆ":"1f99f","๐ชฐ":"1fab0","๐ชฑ":"1fab1","๐ฆ ":"1f9a0","๐":"1f490","๐ธ":"1f338","๐ฎ":"1f4ae","๐ต":"1f3f5","๐น":"1f339","๐ฅ":"1f940","๐บ":"1f33a","๐ป":"1f33b","๐ผ":"1f33c","๐ท":"1f337","๐ฑ":"1f331","๐ชด":"1fab4","๐ฒ":"1f332","๐ณ":"1f333","๐ด":"1f334","๐ต":"1f335","๐พ":"1f33e","๐ฟ":"1f33f","โ":"2618","๐":"1f340","๐":"1f341","๐":"1f342","๐":"1f343","๐":"1f347","๐":"1f348","๐":"1f349","๐":"1f34a","๐":"1f34b","๐":"1f34c","๐":"1f34d","๐ฅญ":"1f96d","๐":"1f34e","๐":"1f34f","๐":"1f350","๐":"1f351","๐":"1f352","๐":"1f353","๐ซ":"1fad0","๐ฅ":"1f95d","๐ ":"1f345","๐ซ":"1fad2","๐ฅฅ":"1f965","๐ฅ":"1f951","๐":"1f346","๐ฅ":"1f954","๐ฅ":"1f955","๐ฝ":"1f33d","๐ถ":"1f336","๐ซ":"1fad1","๐ฅ":"1f952","๐ฅฌ":"1f96c","๐ฅฆ":"1f966","๐ง":"1f9c4","๐ง ":"1f9c5","๐":"1f344","๐ฅ":"1f95c","๐ฐ":"1f330","๐":"1f35e","๐ฅ":"1f950","๐ฅ":"1f956","๐ซ":"1fad3","๐ฅจ":"1f968","๐ฅฏ":"1f96f","๐ฅ":"1f95e","๐ง":"1f9c7","๐ง":"1f9c0","๐":"1f356","๐":"1f357","๐ฅฉ":"1f969","๐ฅ":"1f953","๐":"1f354","๐":"1f35f","๐":"1f355","๐ญ":"1f32d","๐ฅช":"1f96a","๐ฎ":"1f32e","๐ฏ":"1f32f","๐ซ":"1fad4","๐ฅ":"1f959","๐ง":"1f9c6","๐ฅ":"1f95a","๐ณ":"1f373","๐ฅ":"1f958","๐ฒ":"1f372","๐ซ":"1fad5","๐ฅฃ":"1f963","๐ฅ":"1f957","๐ฟ":"1f37f","๐ง":"1f9c8","๐ง":"1f9c2","๐ฅซ":"1f96b","๐ฑ":"1f371","๐":"1f358","๐":"1f359","๐":"1f35a","๐":"1f35b","๐":"1f35c","๐":"1f35d","๐ ":"1f360","๐ข":"1f362","๐ฃ":"1f363","๐ค":"1f364","๐ฅ":"1f365","๐ฅฎ":"1f96e","๐ก":"1f361","๐ฅ":"1f95f","๐ฅ ":"1f960","๐ฅก":"1f961","๐ฆ":"1f980","๐ฆ":"1f99e","๐ฆ":"1f990","๐ฆ":"1f991","๐ฆช":"1f9aa","๐ฆ":"1f366","๐ง":"1f367","๐จ":"1f368","๐ฉ":"1f369","๐ช":"1f36a","๐":"1f382","๐ฐ":"1f370","๐ง":"1f9c1","๐ฅง":"1f967","๐ซ":"1f36b","๐ฌ":"1f36c","๐ญ":"1f36d","๐ฎ":"1f36e","๐ฏ":"1f36f","๐ผ":"1f37c","๐ฅ":"1f95b","โ":"2615","๐ซ":"1fad6","๐ต":"1f375","๐ถ":"1f376","๐พ":"1f37e","๐ท":"1f377","๐ธ":"1f378","๐น":"1f379","๐บ":"1f37a","๐ป":"1f37b","๐ฅ":"1f942","๐ฅ":"1f943","๐ฅค":"1f964","๐ง":"1f9cb","๐ง":"1f9c3","๐ง":"1f9c9","๐ง":"1f9ca","๐ฅข":"1f962","๐ฝ":"1f37d","๐ด":"1f374","๐ฅ":"1f944","๐ช":"1f52a","๐บ":"1f3fa","๐":"1f30d","๐":"1f30e","๐":"1f30f","๐":"1f310","๐บ":"1f5fa","๐พ":"1f5fe","๐งญ":"1f9ed","๐":"1f3d4","โฐ":"26f0","๐":"1f30b","๐ป":"1f5fb","๐":"1f3d5","๐":"1f3d6","๐":"1f3dc","๐":"1f3dd","๐":"1f3de","๐":"1f3df","๐":"1f3db","๐":"1f3d7","๐งฑ":"1f9f1","๐ชจ":"1faa8","๐ชต":"1fab5","๐":"1f6d6","๐":"1f3d8","๐":"1f3da","๐ ":"1f3e0","๐ก":"1f3e1","๐ข":"1f3e2","๐ฃ":"1f3e3","๐ค":"1f3e4","๐ฅ":"1f3e5","๐ฆ":"1f3e6","๐จ":"1f3e8","๐ฉ":"1f3e9","๐ช":"1f3ea","๐ซ":"1f3eb","๐ฌ":"1f3ec","๐ญ":"1f3ed","๐ฏ":"1f3ef","๐ฐ":"1f3f0","๐":"1f492","๐ผ":"1f5fc","๐ฝ":"1f5fd","โช":"26ea","๐":"1f54c","๐":"1f6d5","๐":"1f54d","โฉ":"26e9","๐":"1f54b","โฒ":"26f2","โบ":"26fa","๐":"1f301","๐":"1f303","๐":"1f3d9","๐":"1f304","๐ ":"1f305","๐":"1f306","๐":"1f307","๐":"1f309","โจ":"2668","๐ ":"1f3a0","๐ก":"1f3a1","๐ข":"1f3a2","๐":"1f488","๐ช":"1f3aa","๐":"1f682","๐":"1f683","๐":"1f684","๐ ":"1f685","๐":"1f686","๐":"1f687","๐":"1f688","๐":"1f689","๐":"1f68a","๐":"1f69d","๐":"1f69e","๐":"1f68b","๐":"1f68c","๐":"1f68d","๐":"1f68e","๐":"1f690","๐":"1f691","๐":"1f692","๐":"1f693","๐":"1f694","๐":"1f695","๐":"1f696","๐":"1f697","๐":"1f698","๐":"1f699","๐ป":"1f6fb","๐":"1f69a","๐":"1f69b","๐":"1f69c","๐":"1f3ce","๐":"1f3cd","๐ต":"1f6f5","๐ฆฝ":"1f9bd","๐ฆผ":"1f9bc","๐บ":"1f6fa","๐ฒ":"1f6b2","๐ด":"1f6f4","๐น":"1f6f9","๐ผ":"1f6fc","๐":"1f68f","๐ฃ":"1f6e3","๐ค":"1f6e4","๐ข":"1f6e2","โฝ":"26fd","๐จ":"1f6a8","๐ฅ":"1f6a5","๐ฆ":"1f6a6","๐":"1f6d1","๐ง":"1f6a7","โ":"2693","โต":"26f5","๐ถ":"1f6f6","๐ค":"1f6a4","๐ณ":"1f6f3","โด":"26f4","๐ฅ":"1f6e5","๐ข":"1f6a2","โ":"2708","๐ฉ":"1f6e9","๐ซ":"1f6eb","๐ฌ":"1f6ec","๐ช":"1fa82","๐บ":"1f4ba","๐":"1f681","๐":"1f69f","๐ ":"1f6a0","๐ก":"1f6a1","๐ฐ":"1f6f0","๐":"1f680","๐ธ":"1f6f8","๐":"1f6ce","๐งณ":"1f9f3","โ":"231b","โณ":"23f3","โ":"231a","โฐ":"23f0","โฑ":"23f1","โฒ":"23f2","๐ฐ":"1f570","๐":"1f55b","๐ง":"1f567","๐":"1f550","๐":"1f55c","๐":"1f551","๐":"1f55d","๐":"1f552","๐":"1f55e","๐":"1f553","๐":"1f55f","๐":"1f554","๐ ":"1f560","๐":"1f555","๐ก":"1f561","๐":"1f556","๐ข":"1f562","๐":"1f557","๐ฃ":"1f563","๐":"1f558","๐ค":"1f564","๐":"1f559","๐ฅ":"1f565","๐":"1f55a","๐ฆ":"1f566","๐":"1f311","๐":"1f312","๐":"1f313","๐":"1f314","๐":"1f315","๐":"1f316","๐":"1f317","๐":"1f318","๐":"1f319","๐":"1f31a","๐":"1f31b","๐":"1f31c","๐ก":"1f321","โ":"2600","๐":"1f31d","๐":"1f31e","๐ช":"1fa90","โญ":"2b50","๐":"1f31f","๐ ":"1f320","๐":"1f30c","โ":"2601","โ ":"26c5","โ":"26c8","๐ค":"1f324","๐ฅ":"1f325","๐ฆ":"1f326","๐ง":"1f327","๐จ":"1f328","๐ฉ":"1f329","๐ช":"1f32a","๐ซ":"1f32b","๐ฌ":"1f32c","๐":"1f300","๐":"1f308","๐":"1f302","โ":"2602","โ":"2614","โฑ":"26f1","โก":"26a1","โ":"2744","โ":"2603","โ":"26c4","โ":"2604","๐ฅ":"1f525","๐ง":"1f4a7","๐":"1f30a","๐":"1f383","๐":"1f384","๐":"1f386","๐":"1f387","๐งจ":"1f9e8","โจ":"2728","๐":"1f388","๐":"1f389","๐":"1f38a","๐":"1f38b","๐":"1f38d","๐":"1f38e","๐":"1f38f","๐":"1f390","๐":"1f391","๐งง":"1f9e7","๐":"1f380","๐":"1f381","๐":"1f397","๐":"1f39f","๐ซ":"1f3ab","๐":"1f396","๐":"1f3c6","๐ ":"1f3c5","๐ฅ":"1f947","๐ฅ":"1f948","๐ฅ":"1f949","โฝ":"26bd","โพ":"26be","๐ฅ":"1f94e","๐":"1f3c0","๐":"1f3d0","๐":"1f3c8","๐":"1f3c9","๐พ":"1f3be","๐ฅ":"1f94f","๐ณ":"1f3b3","๐":"1f3cf","๐":"1f3d1","๐":"1f3d2","๐ฅ":"1f94d","๐":"1f3d3","๐ธ":"1f3f8","๐ฅ":"1f94a","๐ฅ":"1f94b","๐ฅ ":"1f945","โณ":"26f3","โธ":"26f8","๐ฃ":"1f3a3","๐คฟ":"1f93f","๐ฝ":"1f3bd","๐ฟ":"1f3bf","๐ท":"1f6f7","๐ฅ":"1f94c","๐ฏ":"1f3af","๐ช":"1fa80","๐ช":"1fa81","๐ฑ":"1f3b1","๐ฎ":"1f52e","๐ช":"1fa84","๐งฟ":"1f9ff","๐ฎ":"1f3ae","๐น":"1f579","๐ฐ":"1f3b0","๐ฒ":"1f3b2","๐งฉ":"1f9e9","๐งธ":"1f9f8","๐ช ":"1fa85","๐ช":"1fa86","โ ":"2660","โฅ":"2665","โฆ":"2666","โฃ":"2663","โ":"265f","๐":"1f0cf","๐":"1f004","๐ด":"1f3b4","๐ญ":"1f3ad","๐ผ":"1f5bc","๐จ":"1f3a8","๐งต":"1f9f5","๐ชก":"1faa1","๐งถ":"1f9f6","๐ชข":"1faa2","๐":"1f453","๐ถ":"1f576","๐ฅฝ":"1f97d","๐ฅผ":"1f97c","๐ฆบ":"1f9ba","๐":"1f454","๐":"1f455","๐":"1f456","๐งฃ":"1f9e3","๐งค":"1f9e4","๐งฅ":"1f9e5","๐งฆ":"1f9e6","๐":"1f457","๐":"1f458","๐ฅป":"1f97b","๐ฉฑ":"1fa71","๐ฉฒ":"1fa72","๐ฉณ":"1fa73","๐":"1f459","๐":"1f45a","๐":"1f45b","๐":"1f45c","๐":"1f45d","๐":"1f6cd","๐":"1f392","๐ฉด":"1fa74","๐":"1f45e","๐":"1f45f","๐ฅพ":"1f97e","๐ฅฟ":"1f97f","๐ ":"1f460","๐ก":"1f461","๐ฉฐ":"1fa70","๐ข":"1f462","๐":"1f451","๐":"1f452","๐ฉ":"1f3a9","๐":"1f393","๐งข":"1f9e2","๐ช":"1fa96","โ":"26d1","๐ฟ":"1f4ff","๐":"1f484","๐":"1f48d","๐":"1f48e","๐":"1f507","๐":"1f508","๐":"1f509","๐":"1f50a","๐ข":"1f4e2","๐ฃ":"1f4e3","๐ฏ":"1f4ef","๐":"1f514","๐":"1f515","๐ผ":"1f3bc","๐ต":"1f3b5","๐ถ":"1f3b6","๐":"1f399","๐":"1f39a","๐":"1f39b","๐ค":"1f3a4","๐ง":"1f3a7","๐ป":"1f4fb","๐ท":"1f3b7","๐ช":"1fa97","๐ธ":"1f3b8","๐น":"1f3b9","๐บ":"1f3ba","๐ป":"1f3bb","๐ช":"1fa95","๐ฅ":"1f941","๐ช":"1fa98","๐ฑ":"1f4f1","๐ฒ":"1f4f2","โ":"260e","๐":"1f4de","๐":"1f4df","๐ ":"1f4e0","๐":"1f50b","๐":"1f50c","๐ป":"1f4bb","๐ฅ":"1f5a5","๐จ":"1f5a8","โจ":"2328","๐ฑ":"1f5b1","๐ฒ":"1f5b2","๐ฝ":"1f4bd","๐พ":"1f4be","๐ฟ":"1f4bf","๐":"1f4c0","๐งฎ":"1f9ee","๐ฅ":"1f3a5","๐":"1f39e","๐ฝ":"1f4fd","๐ฌ":"1f3ac","๐บ":"1f4fa","๐ท":"1f4f7","๐ธ":"1f4f8","๐น":"1f4f9","๐ผ":"1f4fc","๐":"1f50d","๐":"1f50e","๐ฏ":"1f56f","๐ก":"1f4a1","๐ฆ":"1f526","๐ฎ":"1f3ee","๐ช":"1fa94","๐":"1f4d4","๐":"1f4d5","๐":"1f4d6","๐":"1f4d7","๐":"1f4d8","๐":"1f4d9","๐":"1f4da","๐":"1f4d3","๐":"1f4d2","๐":"1f4c3","๐":"1f4dc","๐":"1f4c4","๐ฐ":"1f4f0","๐":"1f5de","๐":"1f4d1","๐":"1f516","๐ท":"1f3f7","๐ฐ":"1f4b0","๐ช":"1fa99","๐ด":"1f4b4","๐ต":"1f4b5","๐ถ":"1f4b6","๐ท":"1f4b7","๐ธ":"1f4b8","๐ณ":"1f4b3","๐งพ":"1f9fe","๐น":"1f4b9","โ":"2709","๐ง":"1f4e7","๐จ":"1f4e8","๐ฉ":"1f4e9","๐ค":"1f4e4","๐ฅ":"1f4e5","๐ฆ":"1f4e6","๐ซ":"1f4eb","๐ช":"1f4ea","๐ฌ":"1f4ec","๐ญ":"1f4ed","๐ฎ":"1f4ee","๐ณ":"1f5f3","โ":"270f","โ":"2712","๐":"1f58b","๐":"1f58a","๐":"1f58c","๐":"1f58d","๐":"1f4dd","๐ผ":"1f4bc","๐":"1f4c1","๐":"1f4c2","๐":"1f5c2","๐ ":"1f4c5","๐":"1f4c6","๐":"1f5d2","๐":"1f5d3","๐":"1f4c7","๐":"1f4c8","๐":"1f4c9","๐":"1f4ca","๐":"1f4cb","๐":"1f4cc","๐":"1f4cd","๐":"1f4ce","๐":"1f587","๐":"1f4cf","๐":"1f4d0","โ":"2702","๐":"1f5c3","๐":"1f5c4","๐":"1f5d1","๐":"1f512","๐":"1f513","๐":"1f50f","๐":"1f510","๐":"1f511","๐":"1f5dd","๐จ":"1f528","๐ช":"1fa93","โ":"26cf","โ":"2692","๐ ":"1f6e0","๐ก":"1f5e1","โ":"2694","๐ซ":"1f52b","๐ช":"1fa83","๐น":"1f3f9","๐ก":"1f6e1","๐ช":"1fa9a","๐ง":"1f527","๐ช":"1fa9b","๐ฉ":"1f529","โ":"2699","๐":"1f5dc","โ":"2696","๐ฆฏ":"1f9af","๐":"1f517","โ":"26d3","๐ช":"1fa9d","๐งฐ":"1f9f0","๐งฒ":"1f9f2","๐ช":"1fa9c","โ":"2697","๐งช":"1f9ea","๐งซ":"1f9eb","๐งฌ":"1f9ec","๐ฌ":"1f52c","๐ญ":"1f52d","๐ก":"1f4e1","๐":"1f489","๐ฉธ":"1fa78","๐":"1f48a","๐ฉน":"1fa79","๐ฉบ":"1fa7a","๐ช":"1f6aa","๐":"1f6d7","๐ช":"1fa9e","๐ช":"1fa9f","๐":"1f6cf","๐":"1f6cb","๐ช":"1fa91","๐ฝ":"1f6bd","๐ช ":"1faa0","๐ฟ":"1f6bf","๐":"1f6c1","๐ชค":"1faa4","๐ช":"1fa92","๐งด":"1f9f4","๐งท":"1f9f7","๐งน":"1f9f9","๐งบ":"1f9fa","๐งป":"1f9fb","๐ชฃ":"1faa3","๐งผ":"1f9fc","๐ชฅ":"1faa5","๐งฝ":"1f9fd","๐งฏ":"1f9ef","๐":"1f6d2","๐ฌ":"1f6ac","โฐ":"26b0","๐ชฆ":"1faa6","โฑ":"26b1","๐ฟ":"1f5ff","๐ชง":"1faa7","๐ง":"1f3e7","๐ฎ":"1f6ae","๐ฐ":"1f6b0","โฟ":"267f","๐น":"1f6b9","๐บ":"1f6ba","๐ป":"1f6bb","๐ผ":"1f6bc","๐พ":"1f6be","๐":"1f6c2","๐":"1f6c3","๐":"1f6c4","๐ ":"1f6c5","โ ":"26a0","๐ธ":"1f6b8","โ":"26d4","๐ซ":"1f6ab","๐ณ":"1f6b3","๐ญ":"1f6ad","๐ฏ":"1f6af","๐ฑ":"1f6b1","๐ท":"1f6b7","๐ต":"1f4f5","๐":"1f51e","โข":"2622","โฃ":"2623","โฌ":"2b06","โ":"2197","โก":"27a1","โ":"2198","โฌ":"2b07","โ":"2199","โฌ ":"2b05","โ":"2196","โ":"2195","โ":"2194","โฉ":"21a9","โช":"21aa","โคด":"2934","โคต":"2935","๐":"1f503","๐":"1f504","๐":"1f519","๐":"1f51a","๐":"1f51b","๐":"1f51c","๐":"1f51d","๐":"1f6d0","โ":"269b","๐":"1f549","โก":"2721","โธ":"2638","โฏ":"262f","โ":"271d","โฆ":"2626","โช":"262a","โฎ":"262e","๐":"1f54e","๐ฏ":"1f52f","โ":"2648","โ":"2649","โ":"264a","โ":"264b","โ":"264c","โ":"264d","โ":"264e","โ":"264f","โ":"2650","โ":"2651","โ":"2652","โ":"2653","โ":"26ce","๐":"1f500","๐":"1f501","๐":"1f502","โถ":"25b6","โฉ":"23e9","โญ":"23ed","โฏ":"23ef","โ":"25c0","โช":"23ea","โฎ":"23ee","๐ผ":"1f53c","โซ":"23eb","๐ฝ":"1f53d","โฌ":"23ec","โธ":"23f8","โน":"23f9","โบ":"23fa","โ":"23cf","๐ฆ":"1f3a6","๐ ":"1f505","๐":"1f506","๐ถ":"1f4f6","๐ณ":"1f4f3","๐ด":"1f4f4","โ":"2640","โ":"2642","โง":"26a7","โ":"2716","โ":"2795","โ":"2796","โ":"2797","โพ":"267e","โผ":"203c","โ":"2049","โ":"2753","โ":"2754","โ":"2755","โ":"2757","ใฐ":"3030","๐ฑ":"1f4b1","๐ฒ":"1f4b2","โ":"2695","โป":"267b","โ":"269c","๐ฑ":"1f531","๐":"1f4db","๐ฐ":"1f530","โญ":"2b55","โ ":"2705","โ":"2611","โ":"2714","โ":"274c","โ":"274e","โฐ":"27b0","โฟ":"27bf","ใฝ":"303d","โณ":"2733","โด":"2734","โ":"2747","ยฉ":"a9","ยฎ":"ae","โข":"2122","๐":"1f51f","๐ ":"1f520","๐ก":"1f521","๐ข":"1f522","๐ฃ":"1f523","๐ค":"1f524","๐ ฐ":"1f170","๐":"1f18e","๐ ฑ":"1f171","๐":"1f191","๐":"1f192","๐":"1f193","โน":"2139","๐":"1f194","โ":"24c2","๐":"1f195","๐":"1f196","๐ พ":"1f17e","๐":"1f197","๐ ฟ":"1f17f","๐":"1f198","๐":"1f199","๐":"1f19a","๐":"1f201","๐":"1f202","๐ท":"1f237","๐ถ":"1f236","๐ฏ":"1f22f","๐":"1f250","๐น":"1f239","๐":"1f21a","๐ฒ":"1f232","๐":"1f251","๐ธ":"1f238","๐ด":"1f234","๐ณ":"1f233","ใ":"3297","ใ":"3299","๐บ":"1f23a","๐ต":"1f235","๐ด":"1f534","๐ ":"1f7e0","๐ก":"1f7e1","๐ข":"1f7e2","๐ต":"1f535","๐ฃ":"1f7e3","๐ค":"1f7e4","โซ":"26ab","โช":"26aa","๐ฅ":"1f7e5","๐ง":"1f7e7","๐จ":"1f7e8","๐ฉ":"1f7e9","๐ฆ":"1f7e6","๐ช":"1f7ea","๐ซ":"1f7eb","โฌ":"2b1b","โฌ":"2b1c","โผ":"25fc","โป":"25fb","โพ":"25fe","โฝ":"25fd","โช":"25aa","โซ":"25ab","๐ถ":"1f536","๐ท":"1f537","๐ธ":"1f538","๐น":"1f539","๐บ":"1f53a","๐ป":"1f53b","๐ ":"1f4a0","๐":"1f518","๐ณ":"1f533","๐ฒ":"1f532","๐":"1f3c1","๐ฉ":"1f6a9","๐":"1f38c","๐ด":"1f3f4","๐ณ":"1f3f3","โบ๏ธ":"263a","โน๏ธ":"2639","โ ๏ธ":"2620","โฃ๏ธ":"2763","โค๏ธ":"2764","๐ณ๏ธ":"1f573","๐จ๏ธ":"1f5e8","๐ฏ๏ธ":"1f5ef","๐๐ป":"1f44b-1f3fb","๐๐ผ":"1f44b-1f3fc","๐๐ฝ":"1f44b-1f3fd","๐๐พ":"1f44b-1f3fe","๐๐ฟ":"1f44b-1f3ff","๐ค๐ป":"1f91a-1f3fb","๐ค๐ผ":"1f91a-1f3fc","๐ค๐ฝ":"1f91a-1f3fd","๐ค๐พ":"1f91a-1f3fe","๐ค๐ฟ":"1f91a-1f3ff","๐๏ธ":"1f590","๐๐ป":"1f590-1f3fb","๐๐ผ":"1f590-1f3fc","๐๐ฝ":"1f590-1f3fd","๐๐พ":"1f590-1f3fe","๐๐ฟ":"1f590-1f3ff","โ๐ป":"270b-1f3fb","โ๐ผ":"270b-1f3fc","โ๐ฝ":"270b-1f3fd","โ๐พ":"270b-1f3fe","โ๐ฟ":"270b-1f3ff","๐๐ป":"1f596-1f3fb","๐๐ผ":"1f596-1f3fc","๐๐ฝ":"1f596-1f3fd","๐๐พ":"1f596-1f3fe","๐๐ฟ":"1f596-1f3ff","๐๐ป":"1f44c-1f3fb","๐๐ผ":"1f44c-1f3fc","๐๐ฝ":"1f44c-1f3fd","๐๐พ":"1f44c-1f3fe","๐๐ฟ":"1f44c-1f3ff","๐ค๐ป":"1f90c-1f3fb","๐ค๐ผ":"1f90c-1f3fc","๐ค๐ฝ":"1f90c-1f3fd","๐ค๐พ":"1f90c-1f3fe","๐ค๐ฟ":"1f90c-1f3ff","๐ค๐ป":"1f90f-1f3fb","๐ค๐ผ":"1f90f-1f3fc","๐ค๐ฝ":"1f90f-1f3fd","๐ค๐พ":"1f90f-1f3fe","๐ค๐ฟ":"1f90f-1f3ff","โ๏ธ":"270c","โ๐ป":"270c-1f3fb","โ๐ผ":"270c-1f3fc","โ๐ฝ":"270c-1f3fd","โ๐พ":"270c-1f3fe","โ๐ฟ":"270c-1f3ff","๐ค๐ป":"1f91e-1f3fb","๐ค๐ผ":"1f91e-1f3fc","๐ค๐ฝ":"1f91e-1f3fd","๐ค๐พ":"1f91e-1f3fe","๐ค๐ฟ":"1f91e-1f3ff","๐ค๐ป":"1f91f-1f3fb","๐ค๐ผ":"1f91f-1f3fc","๐ค๐ฝ":"1f91f-1f3fd","๐ค๐พ":"1f91f-1f3fe","๐ค๐ฟ":"1f91f-1f3ff","๐ค๐ป":"1f918-1f3fb","๐ค๐ผ":"1f918-1f3fc","๐ค๐ฝ":"1f918-1f3fd","๐ค๐พ":"1f918-1f3fe","๐ค๐ฟ":"1f918-1f3ff","๐ค๐ป":"1f919-1f3fb","๐ค๐ผ":"1f919-1f3fc","๐ค๐ฝ":"1f919-1f3fd","๐ค๐พ":"1f919-1f3fe","๐ค๐ฟ":"1f919-1f3ff","๐๐ป":"1f448-1f3fb","๐๐ผ":"1f448-1f3fc","๐๐ฝ":"1f448-1f3fd","๐๐พ":"1f448-1f3fe","๐๐ฟ":"1f448-1f3ff","๐๐ป":"1f449-1f3fb","๐๐ผ":"1f449-1f3fc","๐๐ฝ":"1f449-1f3fd","๐๐พ":"1f449-1f3fe","๐๐ฟ":"1f449-1f3ff","๐๐ป":"1f446-1f3fb","๐๐ผ":"1f446-1f3fc","๐๐ฝ":"1f446-1f3fd","๐๐พ":"1f446-1f3fe","๐๐ฟ":"1f446-1f3ff","๐๐ป":"1f595-1f3fb","๐๐ผ":"1f595-1f3fc","๐๐ฝ":"1f595-1f3fd","๐๐พ":"1f595-1f3fe","๐๐ฟ":"1f595-1f3ff","๐๐ป":"1f447-1f3fb","๐๐ผ":"1f447-1f3fc","๐๐ฝ":"1f447-1f3fd","๐๐พ":"1f447-1f3fe","๐๐ฟ":"1f447-1f3ff","โ๏ธ":"261d","โ๐ป":"261d-1f3fb","โ๐ผ":"261d-1f3fc","โ๐ฝ":"261d-1f3fd","โ๐พ":"261d-1f3fe","โ๐ฟ":"261d-1f3ff","๐๐ป":"1f44d-1f3fb","๐๐ผ":"1f44d-1f3fc","๐๐ฝ":"1f44d-1f3fd","๐๐พ":"1f44d-1f3fe","๐๐ฟ":"1f44d-1f3ff","๐๐ป":"1f44e-1f3fb","๐๐ผ":"1f44e-1f3fc","๐๐ฝ":"1f44e-1f3fd","๐๐พ":"1f44e-1f3fe","๐๐ฟ":"1f44e-1f3ff","โ๐ป":"270a-1f3fb","โ๐ผ":"270a-1f3fc","โ๐ฝ":"270a-1f3fd","โ๐พ":"270a-1f3fe","โ๐ฟ":"270a-1f3ff","๐๐ป":"1f44a-1f3fb","๐๐ผ":"1f44a-1f3fc","๐๐ฝ":"1f44a-1f3fd","๐๐พ":"1f44a-1f3fe","๐๐ฟ":"1f44a-1f3ff","๐ค๐ป":"1f91b-1f3fb","๐ค๐ผ":"1f91b-1f3fc","๐ค๐ฝ":"1f91b-1f3fd","๐ค๐พ":"1f91b-1f3fe","๐ค๐ฟ":"1f91b-1f3ff","๐ค๐ป":"1f91c-1f3fb","๐ค๐ผ":"1f91c-1f3fc","๐ค๐ฝ":"1f91c-1f3fd","๐ค๐พ":"1f91c-1f3fe","๐ค๐ฟ":"1f91c-1f3ff","๐๐ป":"1f44f-1f3fb","๐๐ผ":"1f44f-1f3fc","๐๐ฝ":"1f44f-1f3fd","๐๐พ":"1f44f-1f3fe","๐๐ฟ":"1f44f-1f3ff","๐๐ป":"1f64c-1f3fb","๐๐ผ":"1f64c-1f3fc","๐๐ฝ":"1f64c-1f3fd","๐๐พ":"1f64c-1f3fe","๐๐ฟ":"1f64c-1f3ff","๐๐ป":"1f450-1f3fb","๐๐ผ":"1f450-1f3fc","๐๐ฝ":"1f450-1f3fd","๐๐พ":"1f450-1f3fe","๐๐ฟ":"1f450-1f3ff","๐คฒ๐ป":"1f932-1f3fb","๐คฒ๐ผ":"1f932-1f3fc","๐คฒ๐ฝ":"1f932-1f3fd","๐คฒ๐พ":"1f932-1f3fe","๐คฒ๐ฟ":"1f932-1f3ff","๐๐ป":"1f64f-1f3fb","๐๐ผ":"1f64f-1f3fc","๐๐ฝ":"1f64f-1f3fd","๐๐พ":"1f64f-1f3fe","๐๐ฟ":"1f64f-1f3ff","โ๏ธ":"270d","โ๐ป":"270d-1f3fb","โ๐ผ":"270d-1f3fc","โ๐ฝ":"270d-1f3fd","โ๐พ":"270d-1f3fe","โ๐ฟ":"270d-1f3ff","๐ ๐ป":"1f485-1f3fb","๐ ๐ผ":"1f485-1f3fc","๐ ๐ฝ":"1f485-1f3fd","๐ ๐พ":"1f485-1f3fe","๐ ๐ฟ":"1f485-1f3ff","๐คณ๐ป":"1f933-1f3fb","๐คณ๐ผ":"1f933-1f3fc","๐คณ๐ฝ":"1f933-1f3fd","๐คณ๐พ":"1f933-1f3fe","๐คณ๐ฟ":"1f933-1f3ff","๐ช๐ป":"1f4aa-1f3fb","๐ช๐ผ":"1f4aa-1f3fc","๐ช๐ฝ":"1f4aa-1f3fd","๐ช๐พ":"1f4aa-1f3fe","๐ช๐ฟ":"1f4aa-1f3ff","๐ฆต๐ป":"1f9b5-1f3fb","๐ฆต๐ผ":"1f9b5-1f3fc","๐ฆต๐ฝ":"1f9b5-1f3fd","๐ฆต๐พ":"1f9b5-1f3fe","๐ฆต๐ฟ":"1f9b5-1f3ff","๐ฆถ๐ป":"1f9b6-1f3fb","๐ฆถ๐ผ":"1f9b6-1f3fc","๐ฆถ๐ฝ":"1f9b6-1f3fd","๐ฆถ๐พ":"1f9b6-1f3fe","๐ฆถ๐ฟ":"1f9b6-1f3ff","๐๐ป":"1f442-1f3fb","๐๐ผ":"1f442-1f3fc","๐๐ฝ":"1f442-1f3fd","๐๐พ":"1f442-1f3fe","๐๐ฟ":"1f442-1f3ff","๐ฆป๐ป":"1f9bb-1f3fb","๐ฆป๐ผ":"1f9bb-1f3fc","๐ฆป๐ฝ":"1f9bb-1f3fd","๐ฆป๐พ":"1f9bb-1f3fe","๐ฆป๐ฟ":"1f9bb-1f3ff","๐๐ป":"1f443-1f3fb","๐๐ผ":"1f443-1f3fc","๐๐ฝ":"1f443-1f3fd","๐๐พ":"1f443-1f3fe","๐๐ฟ":"1f443-1f3ff","๐๏ธ":"1f441","๐ถ๐ป":"1f476-1f3fb","๐ถ๐ผ":"1f476-1f3fc","๐ถ๐ฝ":"1f476-1f3fd","๐ถ๐พ":"1f476-1f3fe","๐ถ๐ฟ":"1f476-1f3ff","๐ง๐ป":"1f9d2-1f3fb","๐ง๐ผ":"1f9d2-1f3fc","๐ง๐ฝ":"1f9d2-1f3fd","๐ง๐พ":"1f9d2-1f3fe","๐ง๐ฟ":"1f9d2-1f3ff","๐ฆ๐ป":"1f466-1f3fb","๐ฆ๐ผ":"1f466-1f3fc","๐ฆ๐ฝ":"1f466-1f3fd","๐ฆ๐พ":"1f466-1f3fe","๐ฆ๐ฟ":"1f466-1f3ff","๐ง๐ป":"1f467-1f3fb","๐ง๐ผ":"1f467-1f3fc","๐ง๐ฝ":"1f467-1f3fd","๐ง๐พ":"1f467-1f3fe","๐ง๐ฟ":"1f467-1f3ff","๐ง๐ป":"1f9d1-1f3fb","๐ง๐ผ":"1f9d1-1f3fc","๐ง๐ฝ":"1f9d1-1f3fd","๐ง๐พ":"1f9d1-1f3fe","๐ง๐ฟ":"1f9d1-1f3ff","๐ฑ๐ป":"1f471-1f3fb","๐ฑ๐ผ":"1f471-1f3fc","๐ฑ๐ฝ":"1f471-1f3fd","๐ฑ๐พ":"1f471-1f3fe","๐ฑ๐ฟ":"1f471-1f3ff","๐จ๐ป":"1f468-1f3fb","๐จ๐ผ":"1f468-1f3fc","๐จ๐ฝ":"1f468-1f3fd","๐จ๐พ":"1f468-1f3fe","๐จ๐ฟ":"1f468-1f3ff","๐ง๐ป":"1f9d4-1f3fb","๐ง๐ผ":"1f9d4-1f3fc","๐ง๐ฝ":"1f9d4-1f3fd","๐ง๐พ":"1f9d4-1f3fe","๐ง๐ฟ":"1f9d4-1f3ff","๐ฉ๐ป":"1f469-1f3fb","๐ฉ๐ผ":"1f469-1f3fc","๐ฉ๐ฝ":"1f469-1f3fd","๐ฉ๐พ":"1f469-1f3fe","๐ฉ๐ฟ":"1f469-1f3ff","๐ง๐ป":"1f9d3-1f3fb","๐ง๐ผ":"1f9d3-1f3fc","๐ง๐ฝ":"1f9d3-1f3fd","๐ง๐พ":"1f9d3-1f3fe","๐ง๐ฟ":"1f9d3-1f3ff","๐ด๐ป":"1f474-1f3fb","๐ด๐ผ":"1f474-1f3fc","๐ด๐ฝ":"1f474-1f3fd","๐ด๐พ":"1f474-1f3fe","๐ด๐ฟ":"1f474-1f3ff","๐ต๐ป":"1f475-1f3fb","๐ต๐ผ":"1f475-1f3fc","๐ต๐ฝ":"1f475-1f3fd","๐ต๐พ":"1f475-1f3fe","๐ต๐ฟ":"1f475-1f3ff","๐๐ป":"1f64d-1f3fb","๐๐ผ":"1f64d-1f3fc","๐๐ฝ":"1f64d-1f3fd","๐๐พ":"1f64d-1f3fe","๐๐ฟ":"1f64d-1f3ff","๐๐ป":"1f64e-1f3fb","๐๐ผ":"1f64e-1f3fc","๐๐ฝ":"1f64e-1f3fd","๐๐พ":"1f64e-1f3fe","๐๐ฟ":"1f64e-1f3ff","๐ ๐ป":"1f645-1f3fb","๐ ๐ผ":"1f645-1f3fc","๐ ๐ฝ":"1f645-1f3fd","๐ ๐พ":"1f645-1f3fe","๐ ๐ฟ":"1f645-1f3ff","๐๐ป":"1f646-1f3fb","๐๐ผ":"1f646-1f3fc","๐๐ฝ":"1f646-1f3fd","๐๐พ":"1f646-1f3fe","๐๐ฟ":"1f646-1f3ff","๐๐ป":"1f481-1f3fb","๐๐ผ":"1f481-1f3fc","๐๐ฝ":"1f481-1f3fd","๐๐พ":"1f481-1f3fe","๐๐ฟ":"1f481-1f3ff","๐๐ป":"1f64b-1f3fb","๐๐ผ":"1f64b-1f3fc","๐๐ฝ":"1f64b-1f3fd","๐๐พ":"1f64b-1f3fe","๐๐ฟ":"1f64b-1f3ff","๐ง๐ป":"1f9cf-1f3fb","๐ง๐ผ":"1f9cf-1f3fc","๐ง๐ฝ":"1f9cf-1f3fd","๐ง๐พ":"1f9cf-1f3fe","๐ง๐ฟ":"1f9cf-1f3ff","๐๐ป":"1f647-1f3fb","๐๐ผ":"1f647-1f3fc","๐๐ฝ":"1f647-1f3fd","๐๐พ":"1f647-1f3fe","๐๐ฟ":"1f647-1f3ff","๐คฆ๐ป":"1f926-1f3fb","๐คฆ๐ผ":"1f926-1f3fc","๐คฆ๐ฝ":"1f926-1f3fd","๐คฆ๐พ":"1f926-1f3fe","๐คฆ๐ฟ":"1f926-1f3ff","๐คท๐ป":"1f937-1f3fb","๐คท๐ผ":"1f937-1f3fc","๐คท๐ฝ":"1f937-1f3fd","๐คท๐พ":"1f937-1f3fe","๐คท๐ฟ":"1f937-1f3ff","๐ฎ๐ป":"1f46e-1f3fb","๐ฎ๐ผ":"1f46e-1f3fc","๐ฎ๐ฝ":"1f46e-1f3fd","๐ฎ๐พ":"1f46e-1f3fe","๐ฎ๐ฟ":"1f46e-1f3ff","๐ต๏ธ":"1f575","๐ต๐ป":"1f575-1f3fb","๐ต๐ผ":"1f575-1f3fc","๐ต๐ฝ":"1f575-1f3fd","๐ต๐พ":"1f575-1f3fe","๐ต๐ฟ":"1f575-1f3ff","๐๐ป":"1f482-1f3fb","๐๐ผ":"1f482-1f3fc","๐๐ฝ":"1f482-1f3fd","๐๐พ":"1f482-1f3fe","๐๐ฟ":"1f482-1f3ff","๐ฅท๐ป":"1f977-1f3fb","๐ฅท๐ผ":"1f977-1f3fc","๐ฅท๐ฝ":"1f977-1f3fd","๐ฅท๐พ":"1f977-1f3fe","๐ฅท๐ฟ":"1f977-1f3ff","๐ท๐ป":"1f477-1f3fb","๐ท๐ผ":"1f477-1f3fc","๐ท๐ฝ":"1f477-1f3fd","๐ท๐พ":"1f477-1f3fe","๐ท๐ฟ":"1f477-1f3ff","๐คด๐ป":"1f934-1f3fb","๐คด๐ผ":"1f934-1f3fc","๐คด๐ฝ":"1f934-1f3fd","๐คด๐พ":"1f934-1f3fe","๐คด๐ฟ":"1f934-1f3ff","๐ธ๐ป":"1f478-1f3fb","๐ธ๐ผ":"1f478-1f3fc","๐ธ๐ฝ":"1f478-1f3fd","๐ธ๐พ":"1f478-1f3fe","๐ธ๐ฟ":"1f478-1f3ff","๐ณ๐ป":"1f473-1f3fb","๐ณ๐ผ":"1f473-1f3fc","๐ณ๐ฝ":"1f473-1f3fd","๐ณ๐พ":"1f473-1f3fe","๐ณ๐ฟ":"1f473-1f3ff","๐ฒ๐ป":"1f472-1f3fb","๐ฒ๐ผ":"1f472-1f3fc","๐ฒ๐ฝ":"1f472-1f3fd","๐ฒ๐พ":"1f472-1f3fe","๐ฒ๐ฟ":"1f472-1f3ff","๐ง๐ป":"1f9d5-1f3fb","๐ง๐ผ":"1f9d5-1f3fc","๐ง๐ฝ":"1f9d5-1f3fd","๐ง๐พ":"1f9d5-1f3fe","๐ง๐ฟ":"1f9d5-1f3ff","๐คต๐ป":"1f935-1f3fb","๐คต๐ผ":"1f935-1f3fc","๐คต๐ฝ":"1f935-1f3fd","๐คต๐พ":"1f935-1f3fe","๐คต๐ฟ":"1f935-1f3ff","๐ฐ๐ป":"1f470-1f3fb","๐ฐ๐ผ":"1f470-1f3fc","๐ฐ๐ฝ":"1f470-1f3fd","๐ฐ๐พ":"1f470-1f3fe","๐ฐ๐ฟ":"1f470-1f3ff","๐คฐ๐ป":"1f930-1f3fb","๐คฐ๐ผ":"1f930-1f3fc","๐คฐ๐ฝ":"1f930-1f3fd","๐คฐ๐พ":"1f930-1f3fe","๐คฐ๐ฟ":"1f930-1f3ff","๐คฑ๐ป":"1f931-1f3fb","๐คฑ๐ผ":"1f931-1f3fc","๐คฑ๐ฝ":"1f931-1f3fd","๐คฑ๐พ":"1f931-1f3fe","๐คฑ๐ฟ":"1f931-1f3ff","๐ผ๐ป":"1f47c-1f3fb","๐ผ๐ผ":"1f47c-1f3fc","๐ผ๐ฝ":"1f47c-1f3fd","๐ผ๐พ":"1f47c-1f3fe","๐ผ๐ฟ":"1f47c-1f3ff","๐ ๐ป":"1f385-1f3fb","๐ ๐ผ":"1f385-1f3fc","๐ ๐ฝ":"1f385-1f3fd","๐ ๐พ":"1f385-1f3fe","๐ ๐ฟ":"1f385-1f3ff","๐คถ๐ป":"1f936-1f3fb","๐คถ๐ผ":"1f936-1f3fc","๐คถ๐ฝ":"1f936-1f3fd","๐คถ๐พ":"1f936-1f3fe","๐คถ๐ฟ":"1f936-1f3ff","๐ฆธ๐ป":"1f9b8-1f3fb","๐ฆธ๐ผ":"1f9b8-1f3fc","๐ฆธ๐ฝ":"1f9b8-1f3fd","๐ฆธ๐พ":"1f9b8-1f3fe","๐ฆธ๐ฟ":"1f9b8-1f3ff","๐ฆน๐ป":"1f9b9-1f3fb","๐ฆน๐ผ":"1f9b9-1f3fc","๐ฆน๐ฝ":"1f9b9-1f3fd","๐ฆน๐พ":"1f9b9-1f3fe","๐ฆน๐ฟ":"1f9b9-1f3ff","๐ง๐ป":"1f9d9-1f3fb","๐ง๐ผ":"1f9d9-1f3fc","๐ง๐ฝ":"1f9d9-1f3fd","๐ง๐พ":"1f9d9-1f3fe","๐ง๐ฟ":"1f9d9-1f3ff","๐ง๐ป":"1f9da-1f3fb","๐ง๐ผ":"1f9da-1f3fc","๐ง๐ฝ":"1f9da-1f3fd","๐ง๐พ":"1f9da-1f3fe","๐ง๐ฟ":"1f9da-1f3ff","๐ง๐ป":"1f9db-1f3fb","๐ง๐ผ":"1f9db-1f3fc","๐ง๐ฝ":"1f9db-1f3fd","๐ง๐พ":"1f9db-1f3fe","๐ง๐ฟ":"1f9db-1f3ff","๐ง๐ป":"1f9dc-1f3fb","๐ง๐ผ":"1f9dc-1f3fc","๐ง๐ฝ":"1f9dc-1f3fd","๐ง๐พ":"1f9dc-1f3fe","๐ง๐ฟ":"1f9dc-1f3ff","๐ง๐ป":"1f9dd-1f3fb","๐ง๐ผ":"1f9dd-1f3fc","๐ง๐ฝ":"1f9dd-1f3fd","๐ง๐พ":"1f9dd-1f3fe","๐ง๐ฟ":"1f9dd-1f3ff","๐๐ป":"1f486-1f3fb","๐๐ผ":"1f486-1f3fc","๐๐ฝ":"1f486-1f3fd","๐๐พ":"1f486-1f3fe","๐๐ฟ":"1f486-1f3ff","๐๐ป":"1f487-1f3fb","๐๐ผ":"1f487-1f3fc","๐๐ฝ":"1f487-1f3fd","๐๐พ":"1f487-1f3fe","๐๐ฟ":"1f487-1f3ff","๐ถ๐ป":"1f6b6-1f3fb","๐ถ๐ผ":"1f6b6-1f3fc","๐ถ๐ฝ":"1f6b6-1f3fd","๐ถ๐พ":"1f6b6-1f3fe","๐ถ๐ฟ":"1f6b6-1f3ff","๐ง๐ป":"1f9cd-1f3fb","๐ง๐ผ":"1f9cd-1f3fc","๐ง๐ฝ":"1f9cd-1f3fd","๐ง๐พ":"1f9cd-1f3fe","๐ง๐ฟ":"1f9cd-1f3ff","๐ง๐ป":"1f9ce-1f3fb","๐ง๐ผ":"1f9ce-1f3fc","๐ง๐ฝ":"1f9ce-1f3fd","๐ง๐พ":"1f9ce-1f3fe","๐ง๐ฟ":"1f9ce-1f3ff","๐๐ป":"1f3c3-1f3fb","๐๐ผ":"1f3c3-1f3fc","๐๐ฝ":"1f3c3-1f3fd","๐๐พ":"1f3c3-1f3fe","๐๐ฟ":"1f3c3-1f3ff","๐๐ป":"1f483-1f3fb","๐๐ผ":"1f483-1f3fc","๐๐ฝ":"1f483-1f3fd","๐๐พ":"1f483-1f3fe","๐๐ฟ":"1f483-1f3ff","๐บ๐ป":"1f57a-1f3fb","๐บ๐ผ":"1f57a-1f3fc","๐บ๐ฝ":"1f57a-1f3fd","๐บ๐พ":"1f57a-1f3fe","๐บ๐ฟ":"1f57a-1f3ff","๐ด๏ธ":"1f574","๐ด๐ป":"1f574-1f3fb","๐ด๐ผ":"1f574-1f3fc","๐ด๐ฝ":"1f574-1f3fd","๐ด๐พ":"1f574-1f3fe","๐ด๐ฟ":"1f574-1f3ff","๐ง๐ป":"1f9d6-1f3fb","๐ง๐ผ":"1f9d6-1f3fc","๐ง๐ฝ":"1f9d6-1f3fd","๐ง๐พ":"1f9d6-1f3fe","๐ง๐ฟ":"1f9d6-1f3ff","๐ง๐ป":"1f9d7-1f3fb","๐ง๐ผ":"1f9d7-1f3fc","๐ง๐ฝ":"1f9d7-1f3fd","๐ง๐พ":"1f9d7-1f3fe","๐ง๐ฟ":"1f9d7-1f3ff","๐๐ป":"1f3c7-1f3fb","๐๐ผ":"1f3c7-1f3fc","๐๐ฝ":"1f3c7-1f3fd","๐๐พ":"1f3c7-1f3fe","๐๐ฟ":"1f3c7-1f3ff","โท๏ธ":"26f7","๐๐ป":"1f3c2-1f3fb","๐๐ผ":"1f3c2-1f3fc","๐๐ฝ":"1f3c2-1f3fd","๐๐พ":"1f3c2-1f3fe","๐๐ฟ":"1f3c2-1f3ff","๐๏ธ":"1f3cc","๐๐ป":"1f3cc-1f3fb","๐๐ผ":"1f3cc-1f3fc","๐๐ฝ":"1f3cc-1f3fd","๐๐พ":"1f3cc-1f3fe","๐๐ฟ":"1f3cc-1f3ff","๐๐ป":"1f3c4-1f3fb","๐๐ผ":"1f3c4-1f3fc","๐๐ฝ":"1f3c4-1f3fd","๐๐พ":"1f3c4-1f3fe","๐๐ฟ":"1f3c4-1f3ff","๐ฃ๐ป":"1f6a3-1f3fb","๐ฃ๐ผ":"1f6a3-1f3fc","๐ฃ๐ฝ":"1f6a3-1f3fd","๐ฃ๐พ":"1f6a3-1f3fe","๐ฃ๐ฟ":"1f6a3-1f3ff","๐๐ป":"1f3ca-1f3fb","๐๐ผ":"1f3ca-1f3fc","๐๐ฝ":"1f3ca-1f3fd","๐๐พ":"1f3ca-1f3fe","๐๐ฟ":"1f3ca-1f3ff","โน๏ธ":"26f9","โน๐ป":"26f9-1f3fb","โน๐ผ":"26f9-1f3fc","โน๐ฝ":"26f9-1f3fd","โน๐พ":"26f9-1f3fe","โน๐ฟ":"26f9-1f3ff","๐๏ธ":"1f3cb","๐๐ป":"1f3cb-1f3fb","๐๐ผ":"1f3cb-1f3fc","๐๐ฝ":"1f3cb-1f3fd","๐๐พ":"1f3cb-1f3fe","๐๐ฟ":"1f3cb-1f3ff","๐ด๐ป":"1f6b4-1f3fb","๐ด๐ผ":"1f6b4-1f3fc","๐ด๐ฝ":"1f6b4-1f3fd","๐ด๐พ":"1f6b4-1f3fe","๐ด๐ฟ":"1f6b4-1f3ff","๐ต๐ป":"1f6b5-1f3fb","๐ต๐ผ":"1f6b5-1f3fc","๐ต๐ฝ":"1f6b5-1f3fd","๐ต๐พ":"1f6b5-1f3fe","๐ต๐ฟ":"1f6b5-1f3ff","๐คธ๐ป":"1f938-1f3fb","๐คธ๐ผ":"1f938-1f3fc","๐คธ๐ฝ":"1f938-1f3fd","๐คธ๐พ":"1f938-1f3fe","๐คธ๐ฟ":"1f938-1f3ff","๐คฝ๐ป":"1f93d-1f3fb","๐คฝ๐ผ":"1f93d-1f3fc","๐คฝ๐ฝ":"1f93d-1f3fd","๐คฝ๐พ":"1f93d-1f3fe","๐คฝ๐ฟ":"1f93d-1f3ff","๐คพ๐ป":"1f93e-1f3fb","๐คพ๐ผ":"1f93e-1f3fc","๐คพ๐ฝ":"1f93e-1f3fd","๐คพ๐พ":"1f93e-1f3fe","๐คพ๐ฟ":"1f93e-1f3ff","๐คน๐ป":"1f939-1f3fb","๐คน๐ผ":"1f939-1f3fc","๐คน๐ฝ":"1f939-1f3fd","๐คน๐พ":"1f939-1f3fe","๐คน๐ฟ":"1f939-1f3ff","๐ง๐ป":"1f9d8-1f3fb","๐ง๐ผ":"1f9d8-1f3fc","๐ง๐ฝ":"1f9d8-1f3fd","๐ง๐พ":"1f9d8-1f3fe","๐ง๐ฟ":"1f9d8-1f3ff","๐๐ป":"1f6c0-1f3fb","๐๐ผ":"1f6c0-1f3fc","๐๐ฝ":"1f6c0-1f3fd","๐๐พ":"1f6c0-1f3fe","๐๐ฟ":"1f6c0-1f3ff","๐๐ป":"1f6cc-1f3fb","๐๐ผ":"1f6cc-1f3fc","๐๐ฝ":"1f6cc-1f3fd","๐๐พ":"1f6cc-1f3fe","๐๐ฟ":"1f6cc-1f3ff","๐ญ๐ป":"1f46d-1f3fb","๐ญ๐ผ":"1f46d-1f3fc","๐ญ๐ฝ":"1f46d-1f3fd","๐ญ๐พ":"1f46d-1f3fe","๐ญ๐ฟ":"1f46d-1f3ff","๐ซ๐ป":"1f46b-1f3fb","๐ซ๐ผ":"1f46b-1f3fc","๐ซ๐ฝ":"1f46b-1f3fd","๐ซ๐พ":"1f46b-1f3fe","๐ซ๐ฟ":"1f46b-1f3ff","๐ฌ๐ป":"1f46c-1f3fb","๐ฌ๐ผ":"1f46c-1f3fc","๐ฌ๐ฝ":"1f46c-1f3fd","๐ฌ๐พ":"1f46c-1f3fe","๐ฌ๐ฟ":"1f46c-1f3ff","๐๐ป":"1f48f-1f3fb","๐๐ผ":"1f48f-1f3fc","๐๐ฝ":"1f48f-1f3fd","๐๐พ":"1f48f-1f3fe","๐๐ฟ":"1f48f-1f3ff","๐๐ป":"1f491-1f3fb","๐๐ผ":"1f491-1f3fc","๐๐ฝ":"1f491-1f3fd","๐๐พ":"1f491-1f3fe","๐๐ฟ":"1f491-1f3ff","๐ฃ๏ธ":"1f5e3","๐ฟ๏ธ":"1f43f","๐๏ธ":"1f54a","๐ท๏ธ":"1f577","๐ธ๏ธ":"1f578","๐ต๏ธ":"1f3f5","โ๏ธ":"2618","๐ถ๏ธ":"1f336","๐ฝ๏ธ":"1f37d","๐บ๏ธ":"1f5fa","๐๏ธ":"1f3d4","โฐ๏ธ":"26f0","๐๏ธ":"1f3d5","๐๏ธ":"1f3d6","๐๏ธ":"1f3dc","๐๏ธ":"1f3dd","๐๏ธ":"1f3de","๐๏ธ":"1f3df","๐๏ธ":"1f3db","๐๏ธ":"1f3d7","๐๏ธ":"1f3d8","๐๏ธ":"1f3da","โฉ๏ธ":"26e9","๐๏ธ":"1f3d9","โจ๏ธ":"2668","๐๏ธ":"1f3ce","๐๏ธ":"1f3cd","๐ฃ๏ธ":"1f6e3","๐ค๏ธ":"1f6e4","๐ข๏ธ":"1f6e2","๐ณ๏ธ":"1f6f3","โด๏ธ":"26f4","๐ฅ๏ธ":"1f6e5","โ๏ธ":"2708","๐ฉ๏ธ":"1f6e9","๐ฐ๏ธ":"1f6f0","๐๏ธ":"1f6ce","โฑ๏ธ":"23f1","โฒ๏ธ":"23f2","๐ฐ๏ธ":"1f570","๐ก๏ธ":"1f321","โ๏ธ":"2600","โ๏ธ":"2601","โ๏ธ":"26c8","๐ค๏ธ":"1f324","๐ฅ๏ธ":"1f325","๐ฆ๏ธ":"1f326","๐ง๏ธ":"1f327","๐จ๏ธ":"1f328","๐ฉ๏ธ":"1f329","๐ช๏ธ":"1f32a","๐ซ๏ธ":"1f32b","๐ฌ๏ธ":"1f32c","โ๏ธ":"2602","โฑ๏ธ":"26f1","โ๏ธ":"2744","โ๏ธ":"2603","โ๏ธ":"2604","๐๏ธ":"1f397","๐๏ธ":"1f39f","๐๏ธ":"1f396","โธ๏ธ":"26f8","๐น๏ธ":"1f579","โ ๏ธ":"2660","โฅ๏ธ":"2665","โฆ๏ธ":"2666","โฃ๏ธ":"2663","โ๏ธ":"265f","๐ผ๏ธ":"1f5bc","๐ถ๏ธ":"1f576","๐๏ธ":"1f6cd","โ๏ธ":"26d1","๐๏ธ":"1f399","๐๏ธ":"1f39a","๐๏ธ":"1f39b","โ๏ธ":"260e","๐ฅ๏ธ":"1f5a5","๐จ๏ธ":"1f5a8","โจ๏ธ":"2328","๐ฑ๏ธ":"1f5b1","๐ฒ๏ธ":"1f5b2","๐๏ธ":"1f39e","๐ฝ๏ธ":"1f4fd","๐ฏ๏ธ":"1f56f","๐๏ธ":"1f5de","๐ท๏ธ":"1f3f7","โ๏ธ":"2709","๐ณ๏ธ":"1f5f3","โ๏ธ":"270f","โ๏ธ":"2712","๐๏ธ":"1f58b","๐๏ธ":"1f58a","๐๏ธ":"1f58c","๐๏ธ":"1f58d","๐๏ธ":"1f5c2","๐๏ธ":"1f5d2","๐๏ธ":"1f5d3","๐๏ธ":"1f587","โ๏ธ":"2702","๐๏ธ":"1f5c3","๐๏ธ":"1f5c4","๐๏ธ":"1f5d1","๐๏ธ":"1f5dd","โ๏ธ":"26cf","โ๏ธ":"2692","๐ ๏ธ":"1f6e0","๐ก๏ธ":"1f5e1","โ๏ธ":"2694","๐ก๏ธ":"1f6e1","โ๏ธ":"2699","๐๏ธ":"1f5dc","โ๏ธ":"2696","โ๏ธ":"26d3","โ๏ธ":"2697","๐๏ธ":"1f6cf","๐๏ธ":"1f6cb","โฐ๏ธ":"26b0","โฑ๏ธ":"26b1","โ ๏ธ":"26a0","โข๏ธ":"2622","โฃ๏ธ":"2623","โฌ๏ธ":"2b06","โ๏ธ":"2197","โก๏ธ":"27a1","โ๏ธ":"2198","โฌ๏ธ":"2b07","โ๏ธ":"2199","โฌ ๏ธ":"2b05","โ๏ธ":"2196","โ๏ธ":"2195","โ๏ธ":"2194","โฉ๏ธ":"21a9","โช๏ธ":"21aa","โคด๏ธ":"2934","โคต๏ธ":"2935","โ๏ธ":"269b","๐๏ธ":"1f549","โก๏ธ":"2721","โธ๏ธ":"2638","โฏ๏ธ":"262f","โ๏ธ":"271d","โฆ๏ธ":"2626","โช๏ธ":"262a","โฎ๏ธ":"262e","โถ๏ธ":"25b6","โญ๏ธ":"23ed","โฏ๏ธ":"23ef","โ๏ธ":"25c0","โฎ๏ธ":"23ee","โธ๏ธ":"23f8","โน๏ธ":"23f9","โบ๏ธ":"23fa","โ๏ธ":"23cf","โ๏ธ":"2640","โ๏ธ":"2642","โง๏ธ":"26a7","โ๏ธ":"2716","โพ๏ธ":"267e","โผ๏ธ":"203c","โ๏ธ":"2049","ใฐ๏ธ":"3030","โ๏ธ":"2695","โป๏ธ":"267b","โ๏ธ":"269c","โ๏ธ":"2611","โ๏ธ":"2714","ใฝ๏ธ":"303d","โณ๏ธ":"2733","โด๏ธ":"2734","โ๏ธ":"2747","ยฉ๏ธ":"a9","ยฎ๏ธ":"ae","โข๏ธ":"2122","#โฃ":"23-20e3","*โฃ":"2a-20e3","0โฃ":"30-20e3","1โฃ":"31-20e3","2โฃ":"32-20e3","3โฃ":"33-20e3","4โฃ":"34-20e3","5โฃ":"35-20e3","6โฃ":"36-20e3","7โฃ":"37-20e3","8โฃ":"38-20e3","9โฃ":"39-20e3","๐ ฐ๏ธ":"1f170","๐ ฑ๏ธ":"1f171","โน๏ธ":"2139","โ๏ธ":"24c2","๐ พ๏ธ":"1f17e","๐ ฟ๏ธ":"1f17f","๐๏ธ":"1f202","๐ท๏ธ":"1f237","ใ๏ธ":"3297","ใ๏ธ":"3299","โผ๏ธ":"25fc","โป๏ธ":"25fb","โช๏ธ":"25aa","โซ๏ธ":"25ab","๐ณ๏ธ":"1f3f3","๐ฆ๐จ":"1f1e6-1f1e8","๐ฆ๐ฉ":"1f1e6-1f1e9","๐ฆ๐ช":"1f1e6-1f1ea","๐ฆ๐ซ":"1f1e6-1f1eb","๐ฆ๐ฌ":"1f1e6-1f1ec","๐ฆ๐ฎ":"1f1e6-1f1ee","๐ฆ๐ฑ":"1f1e6-1f1f1","๐ฆ๐ฒ":"1f1e6-1f1f2","๐ฆ๐ด":"1f1e6-1f1f4","๐ฆ๐ถ":"1f1e6-1f1f6","๐ฆ๐ท":"1f1e6-1f1f7","๐ฆ๐ธ":"1f1e6-1f1f8","๐ฆ๐น":"1f1e6-1f1f9","๐ฆ๐บ":"1f1e6-1f1fa","๐ฆ๐ผ":"1f1e6-1f1fc","๐ฆ๐ฝ":"1f1e6-1f1fd","๐ฆ๐ฟ":"1f1e6-1f1ff","๐ง๐ฆ":"1f1e7-1f1e6","๐ง๐ง":"1f1e7-1f1e7","๐ง๐ฉ":"1f1e7-1f1e9","๐ง๐ช":"1f1e7-1f1ea","๐ง๐ซ":"1f1e7-1f1eb","๐ง๐ฌ":"1f1e7-1f1ec","๐ง๐ญ":"1f1e7-1f1ed","๐ง๐ฎ":"1f1e7-1f1ee","๐ง๐ฏ":"1f1e7-1f1ef","๐ง๐ฑ":"1f1e7-1f1f1","๐ง๐ฒ":"1f1e7-1f1f2","๐ง๐ณ":"1f1e7-1f1f3","๐ง๐ด":"1f1e7-1f1f4","๐ง๐ถ":"1f1e7-1f1f6","๐ง๐ท":"1f1e7-1f1f7","๐ง๐ธ":"1f1e7-1f1f8","๐ง๐น":"1f1e7-1f1f9","๐ง๐ป":"1f1e7-1f1fb","๐ง๐ผ":"1f1e7-1f1fc","๐ง๐พ":"1f1e7-1f1fe","๐ง๐ฟ":"1f1e7-1f1ff","๐จ๐ฆ":"1f1e8-1f1e6","๐จ๐จ":"1f1e8-1f1e8","๐จ๐ฉ":"1f1e8-1f1e9","๐จ๐ซ":"1f1e8-1f1eb","๐จ๐ฌ":"1f1e8-1f1ec","๐จ๐ญ":"1f1e8-1f1ed","๐จ๐ฎ":"1f1e8-1f1ee","๐จ๐ฐ":"1f1e8-1f1f0","๐จ๐ฑ":"1f1e8-1f1f1","๐จ๐ฒ":"1f1e8-1f1f2","๐จ๐ณ":"1f1e8-1f1f3","๐จ๐ด":"1f1e8-1f1f4","๐จ๐ต":"1f1e8-1f1f5","๐จ๐ท":"1f1e8-1f1f7","๐จ๐บ":"1f1e8-1f1fa","๐จ๐ป":"1f1e8-1f1fb","๐จ๐ผ":"1f1e8-1f1fc","๐จ๐ฝ":"1f1e8-1f1fd","๐จ๐พ":"1f1e8-1f1fe","๐จ๐ฟ":"1f1e8-1f1ff","๐ฉ๐ช":"1f1e9-1f1ea","๐ฉ๐ฌ":"1f1e9-1f1ec","๐ฉ๐ฏ":"1f1e9-1f1ef","๐ฉ๐ฐ":"1f1e9-1f1f0","๐ฉ๐ฒ":"1f1e9-1f1f2","๐ฉ๐ด":"1f1e9-1f1f4","๐ฉ๐ฟ":"1f1e9-1f1ff","๐ช๐ฆ":"1f1ea-1f1e6","๐ช๐จ":"1f1ea-1f1e8","๐ช๐ช":"1f1ea-1f1ea","๐ช๐ฌ":"1f1ea-1f1ec","๐ช๐ญ":"1f1ea-1f1ed","๐ช๐ท":"1f1ea-1f1f7","๐ช๐ธ":"1f1ea-1f1f8","๐ช๐น":"1f1ea-1f1f9","๐ช๐บ":"1f1ea-1f1fa","๐ซ๐ฎ":"1f1eb-1f1ee","๐ซ๐ฏ":"1f1eb-1f1ef","๐ซ๐ฐ":"1f1eb-1f1f0","๐ซ๐ฒ":"1f1eb-1f1f2","๐ซ๐ด":"1f1eb-1f1f4","๐ซ๐ท":"1f1eb-1f1f7","๐ฌ๐ฆ":"1f1ec-1f1e6","๐ฌ๐ง":"1f1ec-1f1e7","๐ฌ๐ฉ":"1f1ec-1f1e9","๐ฌ๐ช":"1f1ec-1f1ea","๐ฌ๐ซ":"1f1ec-1f1eb","๐ฌ๐ฌ":"1f1ec-1f1ec","๐ฌ๐ญ":"1f1ec-1f1ed","๐ฌ๐ฎ":"1f1ec-1f1ee","๐ฌ๐ฑ":"1f1ec-1f1f1","๐ฌ๐ฒ":"1f1ec-1f1f2","๐ฌ๐ณ":"1f1ec-1f1f3","๐ฌ๐ต":"1f1ec-1f1f5","๐ฌ๐ถ":"1f1ec-1f1f6","๐ฌ๐ท":"1f1ec-1f1f7","๐ฌ๐ธ":"1f1ec-1f1f8","๐ฌ๐น":"1f1ec-1f1f9","๐ฌ๐บ":"1f1ec-1f1fa","๐ฌ๐ผ":"1f1ec-1f1fc","๐ฌ๐พ":"1f1ec-1f1fe","๐ญ๐ฐ":"1f1ed-1f1f0","๐ญ๐ฒ":"1f1ed-1f1f2","๐ญ๐ณ":"1f1ed-1f1f3","๐ญ๐ท":"1f1ed-1f1f7","๐ญ๐น":"1f1ed-1f1f9","๐ญ๐บ":"1f1ed-1f1fa","๐ฎ๐จ":"1f1ee-1f1e8","๐ฎ๐ฉ":"1f1ee-1f1e9","๐ฎ๐ช":"1f1ee-1f1ea","๐ฎ๐ฑ":"1f1ee-1f1f1","๐ฎ๐ฒ":"1f1ee-1f1f2","๐ฎ๐ณ":"1f1ee-1f1f3","๐ฎ๐ด":"1f1ee-1f1f4","๐ฎ๐ถ":"1f1ee-1f1f6","๐ฎ๐ท":"1f1ee-1f1f7","๐ฎ๐ธ":"1f1ee-1f1f8","๐ฎ๐น":"1f1ee-1f1f9","๐ฏ๐ช":"1f1ef-1f1ea","๐ฏ๐ฒ":"1f1ef-1f1f2","๐ฏ๐ด":"1f1ef-1f1f4","๐ฏ๐ต":"1f1ef-1f1f5","๐ฐ๐ช":"1f1f0-1f1ea","๐ฐ๐ฌ":"1f1f0-1f1ec","๐ฐ๐ญ":"1f1f0-1f1ed","๐ฐ๐ฎ":"1f1f0-1f1ee","๐ฐ๐ฒ":"1f1f0-1f1f2","๐ฐ๐ณ":"1f1f0-1f1f3","๐ฐ๐ต":"1f1f0-1f1f5","๐ฐ๐ท":"1f1f0-1f1f7","๐ฐ๐ผ":"1f1f0-1f1fc","๐ฐ๐พ":"1f1f0-1f1fe","๐ฐ๐ฟ":"1f1f0-1f1ff","๐ฑ๐ฆ":"1f1f1-1f1e6","๐ฑ๐ง":"1f1f1-1f1e7","๐ฑ๐จ":"1f1f1-1f1e8","๐ฑ๐ฎ":"1f1f1-1f1ee","๐ฑ๐ฐ":"1f1f1-1f1f0","๐ฑ๐ท":"1f1f1-1f1f7","๐ฑ๐ธ":"1f1f1-1f1f8","๐ฑ๐น":"1f1f1-1f1f9","๐ฑ๐บ":"1f1f1-1f1fa","๐ฑ๐ป":"1f1f1-1f1fb","๐ฑ๐พ":"1f1f1-1f1fe","๐ฒ๐ฆ":"1f1f2-1f1e6","๐ฒ๐จ":"1f1f2-1f1e8","๐ฒ๐ฉ":"1f1f2-1f1e9","๐ฒ๐ช":"1f1f2-1f1ea","๐ฒ๐ซ":"1f1f2-1f1eb","๐ฒ๐ฌ":"1f1f2-1f1ec","๐ฒ๐ญ":"1f1f2-1f1ed","๐ฒ๐ฐ":"1f1f2-1f1f0","๐ฒ๐ฑ":"1f1f2-1f1f1","๐ฒ๐ฒ":"1f1f2-1f1f2","๐ฒ๐ณ":"1f1f2-1f1f3","๐ฒ๐ด":"1f1f2-1f1f4","๐ฒ๐ต":"1f1f2-1f1f5","๐ฒ๐ถ":"1f1f2-1f1f6","๐ฒ๐ท":"1f1f2-1f1f7","๐ฒ๐ธ":"1f1f2-1f1f8","๐ฒ๐น":"1f1f2-1f1f9","๐ฒ๐บ":"1f1f2-1f1fa","๐ฒ๐ป":"1f1f2-1f1fb","๐ฒ๐ผ":"1f1f2-1f1fc","๐ฒ๐ฝ":"1f1f2-1f1fd","๐ฒ๐พ":"1f1f2-1f1fe","๐ฒ๐ฟ":"1f1f2-1f1ff","๐ณ๐ฆ":"1f1f3-1f1e6","๐ณ๐จ":"1f1f3-1f1e8","๐ณ๐ช":"1f1f3-1f1ea","๐ณ๐ซ":"1f1f3-1f1eb","๐ณ๐ฌ":"1f1f3-1f1ec","๐ณ๐ฎ":"1f1f3-1f1ee","๐ณ๐ฑ":"1f1f3-1f1f1","๐ณ๐ด":"1f1f3-1f1f4","๐ณ๐ต":"1f1f3-1f1f5","๐ณ๐ท":"1f1f3-1f1f7","๐ณ๐บ":"1f1f3-1f1fa","๐ณ๐ฟ":"1f1f3-1f1ff","๐ด๐ฒ":"1f1f4-1f1f2","๐ต๐ฆ":"1f1f5-1f1e6","๐ต๐ช":"1f1f5-1f1ea","๐ต๐ซ":"1f1f5-1f1eb","๐ต๐ฌ":"1f1f5-1f1ec","๐ต๐ญ":"1f1f5-1f1ed","๐ต๐ฐ":"1f1f5-1f1f0","๐ต๐ฑ":"1f1f5-1f1f1","๐ต๐ฒ":"1f1f5-1f1f2","๐ต๐ณ":"1f1f5-1f1f3","๐ต๐ท":"1f1f5-1f1f7","๐ต๐ธ":"1f1f5-1f1f8","๐ต๐น":"1f1f5-1f1f9","๐ต๐ผ":"1f1f5-1f1fc","๐ต๐พ":"1f1f5-1f1fe","๐ถ๐ฆ":"1f1f6-1f1e6","๐ท๐ช":"1f1f7-1f1ea","๐ท๐ด":"1f1f7-1f1f4","๐ท๐ธ":"1f1f7-1f1f8","๐ท๐บ":"1f1f7-1f1fa","๐ท๐ผ":"1f1f7-1f1fc","๐ธ๐ฆ":"1f1f8-1f1e6","๐ธ๐ง":"1f1f8-1f1e7","๐ธ๐จ":"1f1f8-1f1e8","๐ธ๐ฉ":"1f1f8-1f1e9","๐ธ๐ช":"1f1f8-1f1ea","๐ธ๐ฌ":"1f1f8-1f1ec","๐ธ๐ญ":"1f1f8-1f1ed","๐ธ๐ฎ":"1f1f8-1f1ee","๐ธ๐ฏ":"1f1f8-1f1ef","๐ธ๐ฐ":"1f1f8-1f1f0","๐ธ๐ฑ":"1f1f8-1f1f1","๐ธ๐ฒ":"1f1f8-1f1f2","๐ธ๐ณ":"1f1f8-1f1f3","๐ธ๐ด":"1f1f8-1f1f4","๐ธ๐ท":"1f1f8-1f1f7","๐ธ๐ธ":"1f1f8-1f1f8","๐ธ๐น":"1f1f8-1f1f9","๐ธ๐ป":"1f1f8-1f1fb","๐ธ๐ฝ":"1f1f8-1f1fd","๐ธ๐พ":"1f1f8-1f1fe","๐ธ๐ฟ":"1f1f8-1f1ff","๐น๐ฆ":"1f1f9-1f1e6","๐น๐จ":"1f1f9-1f1e8","๐น๐ฉ":"1f1f9-1f1e9","๐น๐ซ":"1f1f9-1f1eb","๐น๐ฌ":"1f1f9-1f1ec","๐น๐ญ":"1f1f9-1f1ed","๐น๐ฏ":"1f1f9-1f1ef","๐น๐ฐ":"1f1f9-1f1f0","๐น๐ฑ":"1f1f9-1f1f1","๐น๐ฒ":"1f1f9-1f1f2","๐น๐ณ":"1f1f9-1f1f3","๐น๐ด":"1f1f9-1f1f4","๐น๐ท":"1f1f9-1f1f7","๐น๐น":"1f1f9-1f1f9","๐น๐ป":"1f1f9-1f1fb","๐น๐ผ":"1f1f9-1f1fc","๐น๐ฟ":"1f1f9-1f1ff","๐บ๐ฆ":"1f1fa-1f1e6","๐บ๐ฌ":"1f1fa-1f1ec","๐บ๐ฒ":"1f1fa-1f1f2","๐บ๐ณ":"1f1fa-1f1f3","๐บ๐ธ":"1f1fa-1f1f8","๐บ๐พ":"1f1fa-1f1fe","๐บ๐ฟ":"1f1fa-1f1ff","๐ป๐ฆ":"1f1fb-1f1e6","๐ป๐จ":"1f1fb-1f1e8","๐ป๐ช":"1f1fb-1f1ea","๐ป๐ฌ":"1f1fb-1f1ec","๐ป๐ฎ":"1f1fb-1f1ee","๐ป๐ณ":"1f1fb-1f1f3","๐ป๐บ":"1f1fb-1f1fa","๐ผ๐ซ":"1f1fc-1f1eb","๐ผ๐ธ":"1f1fc-1f1f8","๐ฝ๐ฐ":"1f1fd-1f1f0","๐พ๐ช":"1f1fe-1f1ea","๐พ๐น":"1f1fe-1f1f9","๐ฟ๐ฆ":"1f1ff-1f1e6","๐ฟ๐ฒ":"1f1ff-1f1f2","๐ฟ๐ผ":"1f1ff-1f1fc","๐ถโ๐ซ":"1f636-200d-1f32b-fe0f","๐ฎโ๐จ":"1f62e-200d-1f4a8","๐ตโ๐ซ":"1f635-200d-1f4ab","โคโ๐ฅ":"2764-fe0f-200d-1f525","โคโ๐ฉน":"2764-fe0f-200d-1fa79","๐โ๐จ":"1f441-200d-1f5e8","๐งโโ":"1f9d4-200d-2642-fe0f","๐งโโ":"1f9d4-200d-2640-fe0f","๐จโ๐ฆฐ":"1f468-200d-1f9b0","๐จโ๐ฆฑ":"1f468-200d-1f9b1","๐จโ๐ฆณ":"1f468-200d-1f9b3","๐จโ๐ฆฒ":"1f468-200d-1f9b2","๐ฉโ๐ฆฐ":"1f469-200d-1f9b0","๐งโ๐ฆฐ":"1f9d1-200d-1f9b0","๐ฉโ๐ฆฑ":"1f469-200d-1f9b1","๐งโ๐ฆฑ":"1f9d1-200d-1f9b1","๐ฉโ๐ฆณ":"1f469-200d-1f9b3","๐งโ๐ฆณ":"1f9d1-200d-1f9b3","๐ฉโ๐ฆฒ":"1f469-200d-1f9b2","๐งโ๐ฆฒ":"1f9d1-200d-1f9b2","๐ฑโโ":"1f471-200d-2640-fe0f","๐ฑโโ":"1f471-200d-2642-fe0f","๐โโ":"1f64d-200d-2642-fe0f","๐โโ":"1f64d-200d-2640-fe0f","๐โโ":"1f64e-200d-2642-fe0f","๐โโ":"1f64e-200d-2640-fe0f","๐ โโ":"1f645-200d-2642-fe0f","๐ โโ":"1f645-200d-2640-fe0f","๐โโ":"1f646-200d-2642-fe0f","๐โโ":"1f646-200d-2640-fe0f","๐โโ":"1f481-200d-2642-fe0f","๐โโ":"1f481-200d-2640-fe0f","๐โโ":"1f64b-200d-2642-fe0f","๐โโ":"1f64b-200d-2640-fe0f","๐งโโ":"1f9cf-200d-2642-fe0f","๐งโโ":"1f9cf-200d-2640-fe0f","๐โโ":"1f647-200d-2642-fe0f","๐โโ":"1f647-200d-2640-fe0f","๐คฆโโ":"1f926-200d-2642-fe0f","๐คฆโโ":"1f926-200d-2640-fe0f","๐คทโโ":"1f937-200d-2642-fe0f","๐คทโโ":"1f937-200d-2640-fe0f","๐งโโ":"1f9d1-200d-2695-fe0f","๐จโโ":"1f468-200d-2695-fe0f","๐ฉโโ":"1f469-200d-2695-fe0f","๐งโ๐":"1f9d1-200d-1f393","๐จโ๐":"1f468-200d-1f393","๐ฉโ๐":"1f469-200d-1f393","๐งโ๐ซ":"1f9d1-200d-1f3eb","๐จโ๐ซ":"1f468-200d-1f3eb","๐ฉโ๐ซ":"1f469-200d-1f3eb","๐งโโ":"1f9d1-200d-2696-fe0f","๐จโโ":"1f468-200d-2696-fe0f","๐ฉโโ":"1f469-200d-2696-fe0f","๐งโ๐พ":"1f9d1-200d-1f33e","๐จโ๐พ":"1f468-200d-1f33e","๐ฉโ๐พ":"1f469-200d-1f33e","๐งโ๐ณ":"1f9d1-200d-1f373","๐จโ๐ณ":"1f468-200d-1f373","๐ฉโ๐ณ":"1f469-200d-1f373","๐งโ๐ง":"1f9d1-200d-1f527","๐จโ๐ง":"1f468-200d-1f527","๐ฉโ๐ง":"1f469-200d-1f527","๐งโ๐ญ":"1f9d1-200d-1f3ed","๐จโ๐ญ":"1f468-200d-1f3ed","๐ฉโ๐ญ":"1f469-200d-1f3ed","๐งโ๐ผ":"1f9d1-200d-1f4bc","๐จโ๐ผ":"1f468-200d-1f4bc","๐ฉโ๐ผ":"1f469-200d-1f4bc","๐งโ๐ฌ":"1f9d1-200d-1f52c","๐จโ๐ฌ":"1f468-200d-1f52c","๐ฉโ๐ฌ":"1f469-200d-1f52c","๐งโ๐ป":"1f9d1-200d-1f4bb","๐จโ๐ป":"1f468-200d-1f4bb","๐ฉโ๐ป":"1f469-200d-1f4bb","๐งโ๐ค":"1f9d1-200d-1f3a4","๐จโ๐ค":"1f468-200d-1f3a4","๐ฉโ๐ค":"1f469-200d-1f3a4","๐งโ๐จ":"1f9d1-200d-1f3a8","๐จโ๐จ":"1f468-200d-1f3a8","๐ฉโ๐จ":"1f469-200d-1f3a8","๐งโโ":"1f9d1-200d-2708-fe0f","๐จโโ":"1f468-200d-2708-fe0f","๐ฉโโ":"1f469-200d-2708-fe0f","๐งโ๐":"1f9d1-200d-1f680","๐จโ๐":"1f468-200d-1f680","๐ฉโ๐":"1f469-200d-1f680","๐งโ๐":"1f9d1-200d-1f692","๐จโ๐":"1f468-200d-1f692","๐ฉโ๐":"1f469-200d-1f692","๐ฎโโ":"1f46e-200d-2642-fe0f","๐ฎโโ":"1f46e-200d-2640-fe0f","๐ตโโ":"1f575-fe0f-200d-2642-fe0f","๐ตโโ":"1f575-fe0f-200d-2640-fe0f","๐โโ":"1f482-200d-2642-fe0f","๐โโ":"1f482-200d-2640-fe0f","๐ทโโ":"1f477-200d-2642-fe0f","๐ทโโ":"1f477-200d-2640-fe0f","๐ณโโ":"1f473-200d-2642-fe0f","๐ณโโ":"1f473-200d-2640-fe0f","๐คตโโ":"1f935-200d-2642-fe0f","๐คตโโ":"1f935-200d-2640-fe0f","๐ฐโโ":"1f470-200d-2642-fe0f","๐ฐโโ":"1f470-200d-2640-fe0f","๐ฉโ๐ผ":"1f469-200d-1f37c","๐จโ๐ผ":"1f468-200d-1f37c","๐งโ๐ผ":"1f9d1-200d-1f37c","๐งโ๐":"1f9d1-200d-1f384","๐ฆธโโ":"1f9b8-200d-2642-fe0f","๐ฆธโโ":"1f9b8-200d-2640-fe0f","๐ฆนโโ":"1f9b9-200d-2642-fe0f","๐ฆนโโ":"1f9b9-200d-2640-fe0f","๐งโโ":"1f9d9-200d-2642-fe0f","๐งโโ":"1f9d9-200d-2640-fe0f","๐งโโ":"1f9da-200d-2642-fe0f","๐งโโ":"1f9da-200d-2640-fe0f","๐งโโ":"1f9db-200d-2642-fe0f","๐งโโ":"1f9db-200d-2640-fe0f","๐งโโ":"1f9dc-200d-2642-fe0f","๐งโโ":"1f9dc-200d-2640-fe0f","๐งโโ":"1f9dd-200d-2642-fe0f","๐งโโ":"1f9dd-200d-2640-fe0f","๐งโโ":"1f9de-200d-2642-fe0f","๐งโโ":"1f9de-200d-2640-fe0f","๐งโโ":"1f9df-200d-2642-fe0f","๐งโโ":"1f9df-200d-2640-fe0f","๐โโ":"1f486-200d-2642-fe0f","๐โโ":"1f486-200d-2640-fe0f","๐โโ":"1f487-200d-2642-fe0f","๐โโ":"1f487-200d-2640-fe0f","๐ถโโ":"1f6b6-200d-2642-fe0f","๐ถโโ":"1f6b6-200d-2640-fe0f","๐งโโ":"1f9cd-200d-2642-fe0f","๐งโโ":"1f9cd-200d-2640-fe0f","๐งโโ":"1f9ce-200d-2642-fe0f","๐งโโ":"1f9ce-200d-2640-fe0f","๐งโ๐ฆฏ":"1f9d1-200d-1f9af","๐จโ๐ฆฏ":"1f468-200d-1f9af","๐ฉโ๐ฆฏ":"1f469-200d-1f9af","๐งโ๐ฆผ":"1f9d1-200d-1f9bc","๐จโ๐ฆผ":"1f468-200d-1f9bc","๐ฉโ๐ฆผ":"1f469-200d-1f9bc","๐งโ๐ฆฝ":"1f9d1-200d-1f9bd","๐จโ๐ฆฝ":"1f468-200d-1f9bd","๐ฉโ๐ฆฝ":"1f469-200d-1f9bd","๐โโ":"1f3c3-200d-2642-fe0f","๐โโ":"1f3c3-200d-2640-fe0f","๐ฏโโ":"1f46f-200d-2642-fe0f","๐ฏโโ":"1f46f-200d-2640-fe0f","๐งโโ":"1f9d6-200d-2642-fe0f","๐งโโ":"1f9d6-200d-2640-fe0f","๐งโโ":"1f9d7-200d-2642-fe0f","๐งโโ":"1f9d7-200d-2640-fe0f","๐โโ":"1f3cc-fe0f-200d-2642-fe0f","๐โโ":"1f3cc-fe0f-200d-2640-fe0f","๐โโ":"1f3c4-200d-2642-fe0f","๐โโ":"1f3c4-200d-2640-fe0f","๐ฃโโ":"1f6a3-200d-2642-fe0f","๐ฃโโ":"1f6a3-200d-2640-fe0f","๐โโ":"1f3ca-200d-2642-fe0f","๐โโ":"1f3ca-200d-2640-fe0f","โนโโ":"26f9-fe0f-200d-2642-fe0f","โนโโ":"26f9-fe0f-200d-2640-fe0f","๐โโ":"1f3cb-fe0f-200d-2642-fe0f","๐โโ":"1f3cb-fe0f-200d-2640-fe0f","๐ดโโ":"1f6b4-200d-2642-fe0f","๐ดโโ":"1f6b4-200d-2640-fe0f","๐ตโโ":"1f6b5-200d-2642-fe0f","๐ตโโ":"1f6b5-200d-2640-fe0f","๐คธโโ":"1f938-200d-2642-fe0f","๐คธโโ":"1f938-200d-2640-fe0f","๐คผโโ":"1f93c-200d-2642-fe0f","๐คผโโ":"1f93c-200d-2640-fe0f","๐คฝโโ":"1f93d-200d-2642-fe0f","๐คฝโโ":"1f93d-200d-2640-fe0f","๐คพโโ":"1f93e-200d-2642-fe0f","๐คพโโ":"1f93e-200d-2640-fe0f","๐คนโโ":"1f939-200d-2642-fe0f","๐คนโโ":"1f939-200d-2640-fe0f","๐งโโ":"1f9d8-200d-2642-fe0f","๐งโโ":"1f9d8-200d-2640-fe0f","๐จโ๐ฆ":"1f468-200d-1f466","๐จโ๐ง":"1f468-200d-1f467","๐ฉโ๐ฆ":"1f469-200d-1f466","๐ฉโ๐ง":"1f469-200d-1f467","๐โ๐ฆบ":"1f415-200d-1f9ba","๐โโฌ":"1f408-200d-2b1b","๐ปโโ":"1f43b-200d-2744-fe0f","#๏ธโฃ":"23-20e3","*๏ธโฃ":"2a-20e3","0๏ธโฃ":"30-20e3","1๏ธโฃ":"31-20e3","2๏ธโฃ":"32-20e3","3๏ธโฃ":"33-20e3","4๏ธโฃ":"34-20e3","5๏ธโฃ":"35-20e3","6๏ธโฃ":"36-20e3","7๏ธโฃ":"37-20e3","8๏ธโฃ":"38-20e3","9๏ธโฃ":"39-20e3","๐ณโ๐":"1f3f3-fe0f-200d-1f308","๐ณโโง":"1f3f3-fe0f-200d-26a7-fe0f","๐ดโโ ":"1f3f4-200d-2620-fe0f","๐ถโ๐ซ๏ธ":"1f636-200d-1f32b-fe0f","โค๏ธโ๐ฅ":"2764-fe0f-200d-1f525","โค๏ธโ๐ฉน":"2764-fe0f-200d-1fa79","๐โ๐จ๏ธ":"1f441-200d-1f5e8","๐๏ธโ๐จ":"1f441-200d-1f5e8","๐งโโ๏ธ":"1f9d4-200d-2642-fe0f","๐ง๐ปโโ":"1f9d4-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d4-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d4-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d4-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d4-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d4-200d-2640-fe0f","๐ง๐ปโโ":"1f9d4-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d4-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d4-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d4-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d4-1f3ff-200d-2640-fe0f","๐จ๐ปโ๐ฆฐ":"1f468-1f3fb-200d-1f9b0","๐จ๐ผโ๐ฆฐ":"1f468-1f3fc-200d-1f9b0","๐จ๐ฝโ๐ฆฐ":"1f468-1f3fd-200d-1f9b0","๐จ๐พโ๐ฆฐ":"1f468-1f3fe-200d-1f9b0","๐จ๐ฟโ๐ฆฐ":"1f468-1f3ff-200d-1f9b0","๐จ๐ปโ๐ฆฑ":"1f468-1f3fb-200d-1f9b1","๐จ๐ผโ๐ฆฑ":"1f468-1f3fc-200d-1f9b1","๐จ๐ฝโ๐ฆฑ":"1f468-1f3fd-200d-1f9b1","๐จ๐พโ๐ฆฑ":"1f468-1f3fe-200d-1f9b1","๐จ๐ฟโ๐ฆฑ":"1f468-1f3ff-200d-1f9b1","๐จ๐ปโ๐ฆณ":"1f468-1f3fb-200d-1f9b3","๐จ๐ผโ๐ฆณ":"1f468-1f3fc-200d-1f9b3","๐จ๐ฝโ๐ฆณ":"1f468-1f3fd-200d-1f9b3","๐จ๐พโ๐ฆณ":"1f468-1f3fe-200d-1f9b3","๐จ๐ฟโ๐ฆณ":"1f468-1f3ff-200d-1f9b3","๐จ๐ปโ๐ฆฒ":"1f468-1f3fb-200d-1f9b2","๐จ๐ผโ๐ฆฒ":"1f468-1f3fc-200d-1f9b2","๐จ๐ฝโ๐ฆฒ":"1f468-1f3fd-200d-1f9b2","๐จ๐พโ๐ฆฒ":"1f468-1f3fe-200d-1f9b2","๐จ๐ฟโ๐ฆฒ":"1f468-1f3ff-200d-1f9b2","๐ฉ๐ปโ๐ฆฐ":"1f469-1f3fb-200d-1f9b0","๐ฉ๐ผโ๐ฆฐ":"1f469-1f3fc-200d-1f9b0","๐ฉ๐ฝโ๐ฆฐ":"1f469-1f3fd-200d-1f9b0","๐ฉ๐พโ๐ฆฐ":"1f469-1f3fe-200d-1f9b0","๐ฉ๐ฟโ๐ฆฐ":"1f469-1f3ff-200d-1f9b0","๐ง๐ปโ๐ฆฐ":"1f9d1-1f3fb-200d-1f9b0","๐ง๐ผโ๐ฆฐ":"1f9d1-1f3fc-200d-1f9b0","๐ง๐ฝโ๐ฆฐ":"1f9d1-1f3fd-200d-1f9b0","๐ง๐พโ๐ฆฐ":"1f9d1-1f3fe-200d-1f9b0","๐ง๐ฟโ๐ฆฐ":"1f9d1-1f3ff-200d-1f9b0","๐ฉ๐ปโ๐ฆฑ":"1f469-1f3fb-200d-1f9b1","๐ฉ๐ผโ๐ฆฑ":"1f469-1f3fc-200d-1f9b1","๐ฉ๐ฝโ๐ฆฑ":"1f469-1f3fd-200d-1f9b1","๐ฉ๐พโ๐ฆฑ":"1f469-1f3fe-200d-1f9b1","๐ฉ๐ฟโ๐ฆฑ":"1f469-1f3ff-200d-1f9b1","๐ง๐ปโ๐ฆฑ":"1f9d1-1f3fb-200d-1f9b1","๐ง๐ผโ๐ฆฑ":"1f9d1-1f3fc-200d-1f9b1","๐ง๐ฝโ๐ฆฑ":"1f9d1-1f3fd-200d-1f9b1","๐ง๐พโ๐ฆฑ":"1f9d1-1f3fe-200d-1f9b1","๐ง๐ฟโ๐ฆฑ":"1f9d1-1f3ff-200d-1f9b1","๐ฉ๐ปโ๐ฆณ":"1f469-1f3fb-200d-1f9b3","๐ฉ๐ผโ๐ฆณ":"1f469-1f3fc-200d-1f9b3","๐ฉ๐ฝโ๐ฆณ":"1f469-1f3fd-200d-1f9b3","๐ฉ๐พโ๐ฆณ":"1f469-1f3fe-200d-1f9b3","๐ฉ๐ฟโ๐ฆณ":"1f469-1f3ff-200d-1f9b3","๐ง๐ปโ๐ฆณ":"1f9d1-1f3fb-200d-1f9b3","๐ง๐ผโ๐ฆณ":"1f9d1-1f3fc-200d-1f9b3","๐ง๐ฝโ๐ฆณ":"1f9d1-1f3fd-200d-1f9b3","๐ง๐พโ๐ฆณ":"1f9d1-1f3fe-200d-1f9b3","๐ง๐ฟโ๐ฆณ":"1f9d1-1f3ff-200d-1f9b3","๐ฉ๐ปโ๐ฆฒ":"1f469-1f3fb-200d-1f9b2","๐ฉ๐ผโ๐ฆฒ":"1f469-1f3fc-200d-1f9b2","๐ฉ๐ฝโ๐ฆฒ":"1f469-1f3fd-200d-1f9b2","๐ฉ๐พโ๐ฆฒ":"1f469-1f3fe-200d-1f9b2","๐ฉ๐ฟโ๐ฆฒ":"1f469-1f3ff-200d-1f9b2","๐ง๐ปโ๐ฆฒ":"1f9d1-1f3fb-200d-1f9b2","๐ง๐ผโ๐ฆฒ":"1f9d1-1f3fc-200d-1f9b2","๐ง๐ฝโ๐ฆฒ":"1f9d1-1f3fd-200d-1f9b2","๐ง๐พโ๐ฆฒ":"1f9d1-1f3fe-200d-1f9b2","๐ง๐ฟโ๐ฆฒ":"1f9d1-1f3ff-200d-1f9b2","๐ฑโโ๏ธ":"1f471-200d-2640-fe0f","๐ฑ๐ปโโ":"1f471-1f3fb-200d-2640-fe0f","๐ฑ๐ผโโ":"1f471-1f3fc-200d-2640-fe0f","๐ฑ๐ฝโโ":"1f471-1f3fd-200d-2640-fe0f","๐ฑ๐พโโ":"1f471-1f3fe-200d-2640-fe0f","๐ฑ๐ฟโโ":"1f471-1f3ff-200d-2640-fe0f","๐ฑโโ๏ธ":"1f471-200d-2642-fe0f","๐ฑ๐ปโโ":"1f471-1f3fb-200d-2642-fe0f","๐ฑ๐ผโโ":"1f471-1f3fc-200d-2642-fe0f","๐ฑ๐ฝโโ":"1f471-1f3fd-200d-2642-fe0f","๐ฑ๐พโโ":"1f471-1f3fe-200d-2642-fe0f","๐ฑ๐ฟโโ":"1f471-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64d-200d-2642-fe0f","๐๐ปโโ":"1f64d-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f64d-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f64d-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f64d-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f64d-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64d-200d-2640-fe0f","๐๐ปโโ":"1f64d-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f64d-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f64d-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f64d-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f64d-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f64e-200d-2642-fe0f","๐๐ปโโ":"1f64e-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f64e-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f64e-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f64e-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f64e-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64e-200d-2640-fe0f","๐๐ปโโ":"1f64e-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f64e-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f64e-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f64e-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f64e-1f3ff-200d-2640-fe0f","๐ โโ๏ธ":"1f645-200d-2642-fe0f","๐ ๐ปโโ":"1f645-1f3fb-200d-2642-fe0f","๐ ๐ผโโ":"1f645-1f3fc-200d-2642-fe0f","๐ ๐ฝโโ":"1f645-1f3fd-200d-2642-fe0f","๐ ๐พโโ":"1f645-1f3fe-200d-2642-fe0f","๐ ๐ฟโโ":"1f645-1f3ff-200d-2642-fe0f","๐ โโ๏ธ":"1f645-200d-2640-fe0f","๐ ๐ปโโ":"1f645-1f3fb-200d-2640-fe0f","๐ ๐ผโโ":"1f645-1f3fc-200d-2640-fe0f","๐ ๐ฝโโ":"1f645-1f3fd-200d-2640-fe0f","๐ ๐พโโ":"1f645-1f3fe-200d-2640-fe0f","๐ ๐ฟโโ":"1f645-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f646-200d-2642-fe0f","๐๐ปโโ":"1f646-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f646-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f646-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f646-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f646-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f646-200d-2640-fe0f","๐๐ปโโ":"1f646-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f646-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f646-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f646-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f646-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f481-200d-2642-fe0f","๐๐ปโโ":"1f481-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f481-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f481-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f481-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f481-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f481-200d-2640-fe0f","๐๐ปโโ":"1f481-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f481-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f481-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f481-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f481-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f64b-200d-2642-fe0f","๐๐ปโโ":"1f64b-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f64b-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f64b-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f64b-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f64b-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f64b-200d-2640-fe0f","๐๐ปโโ":"1f64b-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f64b-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f64b-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f64b-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f64b-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9cf-200d-2642-fe0f","๐ง๐ปโโ":"1f9cf-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9cf-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9cf-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9cf-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9cf-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9cf-200d-2640-fe0f","๐ง๐ปโโ":"1f9cf-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9cf-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9cf-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9cf-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9cf-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f647-200d-2642-fe0f","๐๐ปโโ":"1f647-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f647-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f647-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f647-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f647-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f647-200d-2640-fe0f","๐๐ปโโ":"1f647-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f647-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f647-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f647-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f647-1f3ff-200d-2640-fe0f","๐คฆโโ๏ธ":"1f926-200d-2642-fe0f","๐คฆ๐ปโโ":"1f926-1f3fb-200d-2642-fe0f","๐คฆ๐ผโโ":"1f926-1f3fc-200d-2642-fe0f","๐คฆ๐ฝโโ":"1f926-1f3fd-200d-2642-fe0f","๐คฆ๐พโโ":"1f926-1f3fe-200d-2642-fe0f","๐คฆ๐ฟโโ":"1f926-1f3ff-200d-2642-fe0f","๐คฆโโ๏ธ":"1f926-200d-2640-fe0f","๐คฆ๐ปโโ":"1f926-1f3fb-200d-2640-fe0f","๐คฆ๐ผโโ":"1f926-1f3fc-200d-2640-fe0f","๐คฆ๐ฝโโ":"1f926-1f3fd-200d-2640-fe0f","๐คฆ๐พโโ":"1f926-1f3fe-200d-2640-fe0f","๐คฆ๐ฟโโ":"1f926-1f3ff-200d-2640-fe0f","๐คทโโ๏ธ":"1f937-200d-2642-fe0f","๐คท๐ปโโ":"1f937-1f3fb-200d-2642-fe0f","๐คท๐ผโโ":"1f937-1f3fc-200d-2642-fe0f","๐คท๐ฝโโ":"1f937-1f3fd-200d-2642-fe0f","๐คท๐พโโ":"1f937-1f3fe-200d-2642-fe0f","๐คท๐ฟโโ":"1f937-1f3ff-200d-2642-fe0f","๐คทโโ๏ธ":"1f937-200d-2640-fe0f","๐คท๐ปโโ":"1f937-1f3fb-200d-2640-fe0f","๐คท๐ผโโ":"1f937-1f3fc-200d-2640-fe0f","๐คท๐ฝโโ":"1f937-1f3fd-200d-2640-fe0f","๐คท๐พโโ":"1f937-1f3fe-200d-2640-fe0f","๐คท๐ฟโโ":"1f937-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d1-200d-2695-fe0f","๐ง๐ปโโ":"1f9d1-1f3fb-200d-2695-fe0f","๐ง๐ผโโ":"1f9d1-1f3fc-200d-2695-fe0f","๐ง๐ฝโโ":"1f9d1-1f3fd-200d-2695-fe0f","๐ง๐พโโ":"1f9d1-1f3fe-200d-2695-fe0f","๐ง๐ฟโโ":"1f9d1-1f3ff-200d-2695-fe0f","๐จโโ๏ธ":"1f468-200d-2695-fe0f","๐จ๐ปโโ":"1f468-1f3fb-200d-2695-fe0f","๐จ๐ผโโ":"1f468-1f3fc-200d-2695-fe0f","๐จ๐ฝโโ":"1f468-1f3fd-200d-2695-fe0f","๐จ๐พโโ":"1f468-1f3fe-200d-2695-fe0f","๐จ๐ฟโโ":"1f468-1f3ff-200d-2695-fe0f","๐ฉโโ๏ธ":"1f469-200d-2695-fe0f","๐ฉ๐ปโโ":"1f469-1f3fb-200d-2695-fe0f","๐ฉ๐ผโโ":"1f469-1f3fc-200d-2695-fe0f","๐ฉ๐ฝโโ":"1f469-1f3fd-200d-2695-fe0f","๐ฉ๐พโโ":"1f469-1f3fe-200d-2695-fe0f","๐ฉ๐ฟโโ":"1f469-1f3ff-200d-2695-fe0f","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f393","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f393","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f393","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f393","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f393","๐จ๐ปโ๐":"1f468-1f3fb-200d-1f393","๐จ๐ผโ๐":"1f468-1f3fc-200d-1f393","๐จ๐ฝโ๐":"1f468-1f3fd-200d-1f393","๐จ๐พโ๐":"1f468-1f3fe-200d-1f393","๐จ๐ฟโ๐":"1f468-1f3ff-200d-1f393","๐ฉ๐ปโ๐":"1f469-1f3fb-200d-1f393","๐ฉ๐ผโ๐":"1f469-1f3fc-200d-1f393","๐ฉ๐ฝโ๐":"1f469-1f3fd-200d-1f393","๐ฉ๐พโ๐":"1f469-1f3fe-200d-1f393","๐ฉ๐ฟโ๐":"1f469-1f3ff-200d-1f393","๐ง๐ปโ๐ซ":"1f9d1-1f3fb-200d-1f3eb","๐ง๐ผโ๐ซ":"1f9d1-1f3fc-200d-1f3eb","๐ง๐ฝโ๐ซ":"1f9d1-1f3fd-200d-1f3eb","๐ง๐พโ๐ซ":"1f9d1-1f3fe-200d-1f3eb","๐ง๐ฟโ๐ซ":"1f9d1-1f3ff-200d-1f3eb","๐จ๐ปโ๐ซ":"1f468-1f3fb-200d-1f3eb","๐จ๐ผโ๐ซ":"1f468-1f3fc-200d-1f3eb","๐จ๐ฝโ๐ซ":"1f468-1f3fd-200d-1f3eb","๐จ๐พโ๐ซ":"1f468-1f3fe-200d-1f3eb","๐จ๐ฟโ๐ซ":"1f468-1f3ff-200d-1f3eb","๐ฉ๐ปโ๐ซ":"1f469-1f3fb-200d-1f3eb","๐ฉ๐ผโ๐ซ":"1f469-1f3fc-200d-1f3eb","๐ฉ๐ฝโ๐ซ":"1f469-1f3fd-200d-1f3eb","๐ฉ๐พโ๐ซ":"1f469-1f3fe-200d-1f3eb","๐ฉ๐ฟโ๐ซ":"1f469-1f3ff-200d-1f3eb","๐งโโ๏ธ":"1f9d1-200d-2696-fe0f","๐ง๐ปโโ":"1f9d1-1f3fb-200d-2696-fe0f","๐ง๐ผโโ":"1f9d1-1f3fc-200d-2696-fe0f","๐ง๐ฝโโ":"1f9d1-1f3fd-200d-2696-fe0f","๐ง๐พโโ":"1f9d1-1f3fe-200d-2696-fe0f","๐ง๐ฟโโ":"1f9d1-1f3ff-200d-2696-fe0f","๐จโโ๏ธ":"1f468-200d-2696-fe0f","๐จ๐ปโโ":"1f468-1f3fb-200d-2696-fe0f","๐จ๐ผโโ":"1f468-1f3fc-200d-2696-fe0f","๐จ๐ฝโโ":"1f468-1f3fd-200d-2696-fe0f","๐จ๐พโโ":"1f468-1f3fe-200d-2696-fe0f","๐จ๐ฟโโ":"1f468-1f3ff-200d-2696-fe0f","๐ฉโโ๏ธ":"1f469-200d-2696-fe0f","๐ฉ๐ปโโ":"1f469-1f3fb-200d-2696-fe0f","๐ฉ๐ผโโ":"1f469-1f3fc-200d-2696-fe0f","๐ฉ๐ฝโโ":"1f469-1f3fd-200d-2696-fe0f","๐ฉ๐พโโ":"1f469-1f3fe-200d-2696-fe0f","๐ฉ๐ฟโโ":"1f469-1f3ff-200d-2696-fe0f","๐ง๐ปโ๐พ":"1f9d1-1f3fb-200d-1f33e","๐ง๐ผโ๐พ":"1f9d1-1f3fc-200d-1f33e","๐ง๐ฝโ๐พ":"1f9d1-1f3fd-200d-1f33e","๐ง๐พโ๐พ":"1f9d1-1f3fe-200d-1f33e","๐ง๐ฟโ๐พ":"1f9d1-1f3ff-200d-1f33e","๐จ๐ปโ๐พ":"1f468-1f3fb-200d-1f33e","๐จ๐ผโ๐พ":"1f468-1f3fc-200d-1f33e","๐จ๐ฝโ๐พ":"1f468-1f3fd-200d-1f33e","๐จ๐พโ๐พ":"1f468-1f3fe-200d-1f33e","๐จ๐ฟโ๐พ":"1f468-1f3ff-200d-1f33e","๐ฉ๐ปโ๐พ":"1f469-1f3fb-200d-1f33e","๐ฉ๐ผโ๐พ":"1f469-1f3fc-200d-1f33e","๐ฉ๐ฝโ๐พ":"1f469-1f3fd-200d-1f33e","๐ฉ๐พโ๐พ":"1f469-1f3fe-200d-1f33e","๐ฉ๐ฟโ๐พ":"1f469-1f3ff-200d-1f33e","๐ง๐ปโ๐ณ":"1f9d1-1f3fb-200d-1f373","๐ง๐ผโ๐ณ":"1f9d1-1f3fc-200d-1f373","๐ง๐ฝโ๐ณ":"1f9d1-1f3fd-200d-1f373","๐ง๐พโ๐ณ":"1f9d1-1f3fe-200d-1f373","๐ง๐ฟโ๐ณ":"1f9d1-1f3ff-200d-1f373","๐จ๐ปโ๐ณ":"1f468-1f3fb-200d-1f373","๐จ๐ผโ๐ณ":"1f468-1f3fc-200d-1f373","๐จ๐ฝโ๐ณ":"1f468-1f3fd-200d-1f373","๐จ๐พโ๐ณ":"1f468-1f3fe-200d-1f373","๐จ๐ฟโ๐ณ":"1f468-1f3ff-200d-1f373","๐ฉ๐ปโ๐ณ":"1f469-1f3fb-200d-1f373","๐ฉ๐ผโ๐ณ":"1f469-1f3fc-200d-1f373","๐ฉ๐ฝโ๐ณ":"1f469-1f3fd-200d-1f373","๐ฉ๐พโ๐ณ":"1f469-1f3fe-200d-1f373","๐ฉ๐ฟโ๐ณ":"1f469-1f3ff-200d-1f373","๐ง๐ปโ๐ง":"1f9d1-1f3fb-200d-1f527","๐ง๐ผโ๐ง":"1f9d1-1f3fc-200d-1f527","๐ง๐ฝโ๐ง":"1f9d1-1f3fd-200d-1f527","๐ง๐พโ๐ง":"1f9d1-1f3fe-200d-1f527","๐ง๐ฟโ๐ง":"1f9d1-1f3ff-200d-1f527","๐จ๐ปโ๐ง":"1f468-1f3fb-200d-1f527","๐จ๐ผโ๐ง":"1f468-1f3fc-200d-1f527","๐จ๐ฝโ๐ง":"1f468-1f3fd-200d-1f527","๐จ๐พโ๐ง":"1f468-1f3fe-200d-1f527","๐จ๐ฟโ๐ง":"1f468-1f3ff-200d-1f527","๐ฉ๐ปโ๐ง":"1f469-1f3fb-200d-1f527","๐ฉ๐ผโ๐ง":"1f469-1f3fc-200d-1f527","๐ฉ๐ฝโ๐ง":"1f469-1f3fd-200d-1f527","๐ฉ๐พโ๐ง":"1f469-1f3fe-200d-1f527","๐ฉ๐ฟโ๐ง":"1f469-1f3ff-200d-1f527","๐ง๐ปโ๐ญ":"1f9d1-1f3fb-200d-1f3ed","๐ง๐ผโ๐ญ":"1f9d1-1f3fc-200d-1f3ed","๐ง๐ฝโ๐ญ":"1f9d1-1f3fd-200d-1f3ed","๐ง๐พโ๐ญ":"1f9d1-1f3fe-200d-1f3ed","๐ง๐ฟโ๐ญ":"1f9d1-1f3ff-200d-1f3ed","๐จ๐ปโ๐ญ":"1f468-1f3fb-200d-1f3ed","๐จ๐ผโ๐ญ":"1f468-1f3fc-200d-1f3ed","๐จ๐ฝโ๐ญ":"1f468-1f3fd-200d-1f3ed","๐จ๐พโ๐ญ":"1f468-1f3fe-200d-1f3ed","๐จ๐ฟโ๐ญ":"1f468-1f3ff-200d-1f3ed","๐ฉ๐ปโ๐ญ":"1f469-1f3fb-200d-1f3ed","๐ฉ๐ผโ๐ญ":"1f469-1f3fc-200d-1f3ed","๐ฉ๐ฝโ๐ญ":"1f469-1f3fd-200d-1f3ed","๐ฉ๐พโ๐ญ":"1f469-1f3fe-200d-1f3ed","๐ฉ๐ฟโ๐ญ":"1f469-1f3ff-200d-1f3ed","๐ง๐ปโ๐ผ":"1f9d1-1f3fb-200d-1f4bc","๐ง๐ผโ๐ผ":"1f9d1-1f3fc-200d-1f4bc","๐ง๐ฝโ๐ผ":"1f9d1-1f3fd-200d-1f4bc","๐ง๐พโ๐ผ":"1f9d1-1f3fe-200d-1f4bc","๐ง๐ฟโ๐ผ":"1f9d1-1f3ff-200d-1f4bc","๐จ๐ปโ๐ผ":"1f468-1f3fb-200d-1f4bc","๐จ๐ผโ๐ผ":"1f468-1f3fc-200d-1f4bc","๐จ๐ฝโ๐ผ":"1f468-1f3fd-200d-1f4bc","๐จ๐พโ๐ผ":"1f468-1f3fe-200d-1f4bc","๐จ๐ฟโ๐ผ":"1f468-1f3ff-200d-1f4bc","๐ฉ๐ปโ๐ผ":"1f469-1f3fb-200d-1f4bc","๐ฉ๐ผโ๐ผ":"1f469-1f3fc-200d-1f4bc","๐ฉ๐ฝโ๐ผ":"1f469-1f3fd-200d-1f4bc","๐ฉ๐พโ๐ผ":"1f469-1f3fe-200d-1f4bc","๐ฉ๐ฟโ๐ผ":"1f469-1f3ff-200d-1f4bc","๐ง๐ปโ๐ฌ":"1f9d1-1f3fb-200d-1f52c","๐ง๐ผโ๐ฌ":"1f9d1-1f3fc-200d-1f52c","๐ง๐ฝโ๐ฌ":"1f9d1-1f3fd-200d-1f52c","๐ง๐พโ๐ฌ":"1f9d1-1f3fe-200d-1f52c","๐ง๐ฟโ๐ฌ":"1f9d1-1f3ff-200d-1f52c","๐จ๐ปโ๐ฌ":"1f468-1f3fb-200d-1f52c","๐จ๐ผโ๐ฌ":"1f468-1f3fc-200d-1f52c","๐จ๐ฝโ๐ฌ":"1f468-1f3fd-200d-1f52c","๐จ๐พโ๐ฌ":"1f468-1f3fe-200d-1f52c","๐จ๐ฟโ๐ฌ":"1f468-1f3ff-200d-1f52c","๐ฉ๐ปโ๐ฌ":"1f469-1f3fb-200d-1f52c","๐ฉ๐ผโ๐ฌ":"1f469-1f3fc-200d-1f52c","๐ฉ๐ฝโ๐ฌ":"1f469-1f3fd-200d-1f52c","๐ฉ๐พโ๐ฌ":"1f469-1f3fe-200d-1f52c","๐ฉ๐ฟโ๐ฌ":"1f469-1f3ff-200d-1f52c","๐ง๐ปโ๐ป":"1f9d1-1f3fb-200d-1f4bb","๐ง๐ผโ๐ป":"1f9d1-1f3fc-200d-1f4bb","๐ง๐ฝโ๐ป":"1f9d1-1f3fd-200d-1f4bb","๐ง๐พโ๐ป":"1f9d1-1f3fe-200d-1f4bb","๐ง๐ฟโ๐ป":"1f9d1-1f3ff-200d-1f4bb","๐จ๐ปโ๐ป":"1f468-1f3fb-200d-1f4bb","๐จ๐ผโ๐ป":"1f468-1f3fc-200d-1f4bb","๐จ๐ฝโ๐ป":"1f468-1f3fd-200d-1f4bb","๐จ๐พโ๐ป":"1f468-1f3fe-200d-1f4bb","๐จ๐ฟโ๐ป":"1f468-1f3ff-200d-1f4bb","๐ฉ๐ปโ๐ป":"1f469-1f3fb-200d-1f4bb","๐ฉ๐ผโ๐ป":"1f469-1f3fc-200d-1f4bb","๐ฉ๐ฝโ๐ป":"1f469-1f3fd-200d-1f4bb","๐ฉ๐พโ๐ป":"1f469-1f3fe-200d-1f4bb","๐ฉ๐ฟโ๐ป":"1f469-1f3ff-200d-1f4bb","๐ง๐ปโ๐ค":"1f9d1-1f3fb-200d-1f3a4","๐ง๐ผโ๐ค":"1f9d1-1f3fc-200d-1f3a4","๐ง๐ฝโ๐ค":"1f9d1-1f3fd-200d-1f3a4","๐ง๐พโ๐ค":"1f9d1-1f3fe-200d-1f3a4","๐ง๐ฟโ๐ค":"1f9d1-1f3ff-200d-1f3a4","๐จ๐ปโ๐ค":"1f468-1f3fb-200d-1f3a4","๐จ๐ผโ๐ค":"1f468-1f3fc-200d-1f3a4","๐จ๐ฝโ๐ค":"1f468-1f3fd-200d-1f3a4","๐จ๐พโ๐ค":"1f468-1f3fe-200d-1f3a4","๐จ๐ฟโ๐ค":"1f468-1f3ff-200d-1f3a4","๐ฉ๐ปโ๐ค":"1f469-1f3fb-200d-1f3a4","๐ฉ๐ผโ๐ค":"1f469-1f3fc-200d-1f3a4","๐ฉ๐ฝโ๐ค":"1f469-1f3fd-200d-1f3a4","๐ฉ๐พโ๐ค":"1f469-1f3fe-200d-1f3a4","๐ฉ๐ฟโ๐ค":"1f469-1f3ff-200d-1f3a4","๐ง๐ปโ๐จ":"1f9d1-1f3fb-200d-1f3a8","๐ง๐ผโ๐จ":"1f9d1-1f3fc-200d-1f3a8","๐ง๐ฝโ๐จ":"1f9d1-1f3fd-200d-1f3a8","๐ง๐พโ๐จ":"1f9d1-1f3fe-200d-1f3a8","๐ง๐ฟโ๐จ":"1f9d1-1f3ff-200d-1f3a8","๐จ๐ปโ๐จ":"1f468-1f3fb-200d-1f3a8","๐จ๐ผโ๐จ":"1f468-1f3fc-200d-1f3a8","๐จ๐ฝโ๐จ":"1f468-1f3fd-200d-1f3a8","๐จ๐พโ๐จ":"1f468-1f3fe-200d-1f3a8","๐จ๐ฟโ๐จ":"1f468-1f3ff-200d-1f3a8","๐ฉ๐ปโ๐จ":"1f469-1f3fb-200d-1f3a8","๐ฉ๐ผโ๐จ":"1f469-1f3fc-200d-1f3a8","๐ฉ๐ฝโ๐จ":"1f469-1f3fd-200d-1f3a8","๐ฉ๐พโ๐จ":"1f469-1f3fe-200d-1f3a8","๐ฉ๐ฟโ๐จ":"1f469-1f3ff-200d-1f3a8","๐งโโ๏ธ":"1f9d1-200d-2708-fe0f","๐ง๐ปโโ":"1f9d1-1f3fb-200d-2708-fe0f","๐ง๐ผโโ":"1f9d1-1f3fc-200d-2708-fe0f","๐ง๐ฝโโ":"1f9d1-1f3fd-200d-2708-fe0f","๐ง๐พโโ":"1f9d1-1f3fe-200d-2708-fe0f","๐ง๐ฟโโ":"1f9d1-1f3ff-200d-2708-fe0f","๐จโโ๏ธ":"1f468-200d-2708-fe0f","๐จ๐ปโโ":"1f468-1f3fb-200d-2708-fe0f","๐จ๐ผโโ":"1f468-1f3fc-200d-2708-fe0f","๐จ๐ฝโโ":"1f468-1f3fd-200d-2708-fe0f","๐จ๐พโโ":"1f468-1f3fe-200d-2708-fe0f","๐จ๐ฟโโ":"1f468-1f3ff-200d-2708-fe0f","๐ฉโโ๏ธ":"1f469-200d-2708-fe0f","๐ฉ๐ปโโ":"1f469-1f3fb-200d-2708-fe0f","๐ฉ๐ผโโ":"1f469-1f3fc-200d-2708-fe0f","๐ฉ๐ฝโโ":"1f469-1f3fd-200d-2708-fe0f","๐ฉ๐พโโ":"1f469-1f3fe-200d-2708-fe0f","๐ฉ๐ฟโโ":"1f469-1f3ff-200d-2708-fe0f","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f680","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f680","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f680","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f680","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f680","๐จ๐ปโ๐":"1f468-1f3fb-200d-1f680","๐จ๐ผโ๐":"1f468-1f3fc-200d-1f680","๐จ๐ฝโ๐":"1f468-1f3fd-200d-1f680","๐จ๐พโ๐":"1f468-1f3fe-200d-1f680","๐จ๐ฟโ๐":"1f468-1f3ff-200d-1f680","๐ฉ๐ปโ๐":"1f469-1f3fb-200d-1f680","๐ฉ๐ผโ๐":"1f469-1f3fc-200d-1f680","๐ฉ๐ฝโ๐":"1f469-1f3fd-200d-1f680","๐ฉ๐พโ๐":"1f469-1f3fe-200d-1f680","๐ฉ๐ฟโ๐":"1f469-1f3ff-200d-1f680","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f692","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f692","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f692","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f692","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f692","๐จ๐ปโ๐":"1f468-1f3fb-200d-1f692","๐จ๐ผโ๐":"1f468-1f3fc-200d-1f692","๐จ๐ฝโ๐":"1f468-1f3fd-200d-1f692","๐จ๐พโ๐":"1f468-1f3fe-200d-1f692","๐จ๐ฟโ๐":"1f468-1f3ff-200d-1f692","๐ฉ๐ปโ๐":"1f469-1f3fb-200d-1f692","๐ฉ๐ผโ๐":"1f469-1f3fc-200d-1f692","๐ฉ๐ฝโ๐":"1f469-1f3fd-200d-1f692","๐ฉ๐พโ๐":"1f469-1f3fe-200d-1f692","๐ฉ๐ฟโ๐":"1f469-1f3ff-200d-1f692","๐ฎโโ๏ธ":"1f46e-200d-2642-fe0f","๐ฎ๐ปโโ":"1f46e-1f3fb-200d-2642-fe0f","๐ฎ๐ผโโ":"1f46e-1f3fc-200d-2642-fe0f","๐ฎ๐ฝโโ":"1f46e-1f3fd-200d-2642-fe0f","๐ฎ๐พโโ":"1f46e-1f3fe-200d-2642-fe0f","๐ฎ๐ฟโโ":"1f46e-1f3ff-200d-2642-fe0f","๐ฎโโ๏ธ":"1f46e-200d-2640-fe0f","๐ฎ๐ปโโ":"1f46e-1f3fb-200d-2640-fe0f","๐ฎ๐ผโโ":"1f46e-1f3fc-200d-2640-fe0f","๐ฎ๐ฝโโ":"1f46e-1f3fd-200d-2640-fe0f","๐ฎ๐พโโ":"1f46e-1f3fe-200d-2640-fe0f","๐ฎ๐ฟโโ":"1f46e-1f3ff-200d-2640-fe0f","๐ตโโ๏ธ":"1f575-fe0f-200d-2642-fe0f","๐ต๏ธโโ":"1f575-fe0f-200d-2642-fe0f","๐ต๐ปโโ":"1f575-1f3fb-200d-2642-fe0f","๐ต๐ผโโ":"1f575-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ":"1f575-1f3fd-200d-2642-fe0f","๐ต๐พโโ":"1f575-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ":"1f575-1f3ff-200d-2642-fe0f","๐ตโโ๏ธ":"1f575-fe0f-200d-2640-fe0f","๐ต๏ธโโ":"1f575-fe0f-200d-2640-fe0f","๐ต๐ปโโ":"1f575-1f3fb-200d-2640-fe0f","๐ต๐ผโโ":"1f575-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ":"1f575-1f3fd-200d-2640-fe0f","๐ต๐พโโ":"1f575-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ":"1f575-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f482-200d-2642-fe0f","๐๐ปโโ":"1f482-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f482-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f482-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f482-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f482-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f482-200d-2640-fe0f","๐๐ปโโ":"1f482-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f482-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f482-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f482-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f482-1f3ff-200d-2640-fe0f","๐ทโโ๏ธ":"1f477-200d-2642-fe0f","๐ท๐ปโโ":"1f477-1f3fb-200d-2642-fe0f","๐ท๐ผโโ":"1f477-1f3fc-200d-2642-fe0f","๐ท๐ฝโโ":"1f477-1f3fd-200d-2642-fe0f","๐ท๐พโโ":"1f477-1f3fe-200d-2642-fe0f","๐ท๐ฟโโ":"1f477-1f3ff-200d-2642-fe0f","๐ทโโ๏ธ":"1f477-200d-2640-fe0f","๐ท๐ปโโ":"1f477-1f3fb-200d-2640-fe0f","๐ท๐ผโโ":"1f477-1f3fc-200d-2640-fe0f","๐ท๐ฝโโ":"1f477-1f3fd-200d-2640-fe0f","๐ท๐พโโ":"1f477-1f3fe-200d-2640-fe0f","๐ท๐ฟโโ":"1f477-1f3ff-200d-2640-fe0f","๐ณโโ๏ธ":"1f473-200d-2642-fe0f","๐ณ๐ปโโ":"1f473-1f3fb-200d-2642-fe0f","๐ณ๐ผโโ":"1f473-1f3fc-200d-2642-fe0f","๐ณ๐ฝโโ":"1f473-1f3fd-200d-2642-fe0f","๐ณ๐พโโ":"1f473-1f3fe-200d-2642-fe0f","๐ณ๐ฟโโ":"1f473-1f3ff-200d-2642-fe0f","๐ณโโ๏ธ":"1f473-200d-2640-fe0f","๐ณ๐ปโโ":"1f473-1f3fb-200d-2640-fe0f","๐ณ๐ผโโ":"1f473-1f3fc-200d-2640-fe0f","๐ณ๐ฝโโ":"1f473-1f3fd-200d-2640-fe0f","๐ณ๐พโโ":"1f473-1f3fe-200d-2640-fe0f","๐ณ๐ฟโโ":"1f473-1f3ff-200d-2640-fe0f","๐คตโโ๏ธ":"1f935-200d-2642-fe0f","๐คต๐ปโโ":"1f935-1f3fb-200d-2642-fe0f","๐คต๐ผโโ":"1f935-1f3fc-200d-2642-fe0f","๐คต๐ฝโโ":"1f935-1f3fd-200d-2642-fe0f","๐คต๐พโโ":"1f935-1f3fe-200d-2642-fe0f","๐คต๐ฟโโ":"1f935-1f3ff-200d-2642-fe0f","๐คตโโ๏ธ":"1f935-200d-2640-fe0f","๐คต๐ปโโ":"1f935-1f3fb-200d-2640-fe0f","๐คต๐ผโโ":"1f935-1f3fc-200d-2640-fe0f","๐คต๐ฝโโ":"1f935-1f3fd-200d-2640-fe0f","๐คต๐พโโ":"1f935-1f3fe-200d-2640-fe0f","๐คต๐ฟโโ":"1f935-1f3ff-200d-2640-fe0f","๐ฐโโ๏ธ":"1f470-200d-2642-fe0f","๐ฐ๐ปโโ":"1f470-1f3fb-200d-2642-fe0f","๐ฐ๐ผโโ":"1f470-1f3fc-200d-2642-fe0f","๐ฐ๐ฝโโ":"1f470-1f3fd-200d-2642-fe0f","๐ฐ๐พโโ":"1f470-1f3fe-200d-2642-fe0f","๐ฐ๐ฟโโ":"1f470-1f3ff-200d-2642-fe0f","๐ฐโโ๏ธ":"1f470-200d-2640-fe0f","๐ฐ๐ปโโ":"1f470-1f3fb-200d-2640-fe0f","๐ฐ๐ผโโ":"1f470-1f3fc-200d-2640-fe0f","๐ฐ๐ฝโโ":"1f470-1f3fd-200d-2640-fe0f","๐ฐ๐พโโ":"1f470-1f3fe-200d-2640-fe0f","๐ฐ๐ฟโโ":"1f470-1f3ff-200d-2640-fe0f","๐ฉ๐ปโ๐ผ":"1f469-1f3fb-200d-1f37c","๐ฉ๐ผโ๐ผ":"1f469-1f3fc-200d-1f37c","๐ฉ๐ฝโ๐ผ":"1f469-1f3fd-200d-1f37c","๐ฉ๐พโ๐ผ":"1f469-1f3fe-200d-1f37c","๐ฉ๐ฟโ๐ผ":"1f469-1f3ff-200d-1f37c","๐จ๐ปโ๐ผ":"1f468-1f3fb-200d-1f37c","๐จ๐ผโ๐ผ":"1f468-1f3fc-200d-1f37c","๐จ๐ฝโ๐ผ":"1f468-1f3fd-200d-1f37c","๐จ๐พโ๐ผ":"1f468-1f3fe-200d-1f37c","๐จ๐ฟโ๐ผ":"1f468-1f3ff-200d-1f37c","๐ง๐ปโ๐ผ":"1f9d1-1f3fb-200d-1f37c","๐ง๐ผโ๐ผ":"1f9d1-1f3fc-200d-1f37c","๐ง๐ฝโ๐ผ":"1f9d1-1f3fd-200d-1f37c","๐ง๐พโ๐ผ":"1f9d1-1f3fe-200d-1f37c","๐ง๐ฟโ๐ผ":"1f9d1-1f3ff-200d-1f37c","๐ง๐ปโ๐":"1f9d1-1f3fb-200d-1f384","๐ง๐ผโ๐":"1f9d1-1f3fc-200d-1f384","๐ง๐ฝโ๐":"1f9d1-1f3fd-200d-1f384","๐ง๐พโ๐":"1f9d1-1f3fe-200d-1f384","๐ง๐ฟโ๐":"1f9d1-1f3ff-200d-1f384","๐ฆธโโ๏ธ":"1f9b8-200d-2642-fe0f","๐ฆธ๐ปโโ":"1f9b8-1f3fb-200d-2642-fe0f","๐ฆธ๐ผโโ":"1f9b8-1f3fc-200d-2642-fe0f","๐ฆธ๐ฝโโ":"1f9b8-1f3fd-200d-2642-fe0f","๐ฆธ๐พโโ":"1f9b8-1f3fe-200d-2642-fe0f","๐ฆธ๐ฟโโ":"1f9b8-1f3ff-200d-2642-fe0f","๐ฆธโโ๏ธ":"1f9b8-200d-2640-fe0f","๐ฆธ๐ปโโ":"1f9b8-1f3fb-200d-2640-fe0f","๐ฆธ๐ผโโ":"1f9b8-1f3fc-200d-2640-fe0f","๐ฆธ๐ฝโโ":"1f9b8-1f3fd-200d-2640-fe0f","๐ฆธ๐พโโ":"1f9b8-1f3fe-200d-2640-fe0f","๐ฆธ๐ฟโโ":"1f9b8-1f3ff-200d-2640-fe0f","๐ฆนโโ๏ธ":"1f9b9-200d-2642-fe0f","๐ฆน๐ปโโ":"1f9b9-1f3fb-200d-2642-fe0f","๐ฆน๐ผโโ":"1f9b9-1f3fc-200d-2642-fe0f","๐ฆน๐ฝโโ":"1f9b9-1f3fd-200d-2642-fe0f","๐ฆน๐พโโ":"1f9b9-1f3fe-200d-2642-fe0f","๐ฆน๐ฟโโ":"1f9b9-1f3ff-200d-2642-fe0f","๐ฆนโโ๏ธ":"1f9b9-200d-2640-fe0f","๐ฆน๐ปโโ":"1f9b9-1f3fb-200d-2640-fe0f","๐ฆน๐ผโโ":"1f9b9-1f3fc-200d-2640-fe0f","๐ฆน๐ฝโโ":"1f9b9-1f3fd-200d-2640-fe0f","๐ฆน๐พโโ":"1f9b9-1f3fe-200d-2640-fe0f","๐ฆน๐ฟโโ":"1f9b9-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d9-200d-2642-fe0f","๐ง๐ปโโ":"1f9d9-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d9-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d9-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d9-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d9-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d9-200d-2640-fe0f","๐ง๐ปโโ":"1f9d9-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d9-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d9-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d9-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d9-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9da-200d-2642-fe0f","๐ง๐ปโโ":"1f9da-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9da-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9da-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9da-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9da-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9da-200d-2640-fe0f","๐ง๐ปโโ":"1f9da-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9da-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9da-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9da-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9da-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9db-200d-2642-fe0f","๐ง๐ปโโ":"1f9db-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9db-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9db-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9db-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9db-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9db-200d-2640-fe0f","๐ง๐ปโโ":"1f9db-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9db-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9db-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9db-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9db-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9dc-200d-2642-fe0f","๐ง๐ปโโ":"1f9dc-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9dc-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9dc-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9dc-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9dc-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9dc-200d-2640-fe0f","๐ง๐ปโโ":"1f9dc-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9dc-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9dc-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9dc-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9dc-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9dd-200d-2642-fe0f","๐ง๐ปโโ":"1f9dd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9dd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9dd-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9dd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9dd-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9dd-200d-2640-fe0f","๐ง๐ปโโ":"1f9dd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9dd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9dd-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9dd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9dd-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9de-200d-2642-fe0f","๐งโโ๏ธ":"1f9de-200d-2640-fe0f","๐งโโ๏ธ":"1f9df-200d-2642-fe0f","๐งโโ๏ธ":"1f9df-200d-2640-fe0f","๐โโ๏ธ":"1f486-200d-2642-fe0f","๐๐ปโโ":"1f486-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f486-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f486-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f486-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f486-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f486-200d-2640-fe0f","๐๐ปโโ":"1f486-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f486-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f486-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f486-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f486-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f487-200d-2642-fe0f","๐๐ปโโ":"1f487-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f487-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f487-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f487-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f487-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f487-200d-2640-fe0f","๐๐ปโโ":"1f487-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f487-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f487-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f487-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f487-1f3ff-200d-2640-fe0f","๐ถโโ๏ธ":"1f6b6-200d-2642-fe0f","๐ถ๐ปโโ":"1f6b6-1f3fb-200d-2642-fe0f","๐ถ๐ผโโ":"1f6b6-1f3fc-200d-2642-fe0f","๐ถ๐ฝโโ":"1f6b6-1f3fd-200d-2642-fe0f","๐ถ๐พโโ":"1f6b6-1f3fe-200d-2642-fe0f","๐ถ๐ฟโโ":"1f6b6-1f3ff-200d-2642-fe0f","๐ถโโ๏ธ":"1f6b6-200d-2640-fe0f","๐ถ๐ปโโ":"1f6b6-1f3fb-200d-2640-fe0f","๐ถ๐ผโโ":"1f6b6-1f3fc-200d-2640-fe0f","๐ถ๐ฝโโ":"1f6b6-1f3fd-200d-2640-fe0f","๐ถ๐พโโ":"1f6b6-1f3fe-200d-2640-fe0f","๐ถ๐ฟโโ":"1f6b6-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9cd-200d-2642-fe0f","๐ง๐ปโโ":"1f9cd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9cd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9cd-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9cd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9cd-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9cd-200d-2640-fe0f","๐ง๐ปโโ":"1f9cd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9cd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9cd-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9cd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9cd-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9ce-200d-2642-fe0f","๐ง๐ปโโ":"1f9ce-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9ce-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9ce-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9ce-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9ce-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9ce-200d-2640-fe0f","๐ง๐ปโโ":"1f9ce-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9ce-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9ce-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9ce-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9ce-1f3ff-200d-2640-fe0f","๐ง๐ปโ๐ฆฏ":"1f9d1-1f3fb-200d-1f9af","๐ง๐ผโ๐ฆฏ":"1f9d1-1f3fc-200d-1f9af","๐ง๐ฝโ๐ฆฏ":"1f9d1-1f3fd-200d-1f9af","๐ง๐พโ๐ฆฏ":"1f9d1-1f3fe-200d-1f9af","๐ง๐ฟโ๐ฆฏ":"1f9d1-1f3ff-200d-1f9af","๐จ๐ปโ๐ฆฏ":"1f468-1f3fb-200d-1f9af","๐จ๐ผโ๐ฆฏ":"1f468-1f3fc-200d-1f9af","๐จ๐ฝโ๐ฆฏ":"1f468-1f3fd-200d-1f9af","๐จ๐พโ๐ฆฏ":"1f468-1f3fe-200d-1f9af","๐จ๐ฟโ๐ฆฏ":"1f468-1f3ff-200d-1f9af","๐ฉ๐ปโ๐ฆฏ":"1f469-1f3fb-200d-1f9af","๐ฉ๐ผโ๐ฆฏ":"1f469-1f3fc-200d-1f9af","๐ฉ๐ฝโ๐ฆฏ":"1f469-1f3fd-200d-1f9af","๐ฉ๐พโ๐ฆฏ":"1f469-1f3fe-200d-1f9af","๐ฉ๐ฟโ๐ฆฏ":"1f469-1f3ff-200d-1f9af","๐ง๐ปโ๐ฆผ":"1f9d1-1f3fb-200d-1f9bc","๐ง๐ผโ๐ฆผ":"1f9d1-1f3fc-200d-1f9bc","๐ง๐ฝโ๐ฆผ":"1f9d1-1f3fd-200d-1f9bc","๐ง๐พโ๐ฆผ":"1f9d1-1f3fe-200d-1f9bc","๐ง๐ฟโ๐ฆผ":"1f9d1-1f3ff-200d-1f9bc","๐จ๐ปโ๐ฆผ":"1f468-1f3fb-200d-1f9bc","๐จ๐ผโ๐ฆผ":"1f468-1f3fc-200d-1f9bc","๐จ๐ฝโ๐ฆผ":"1f468-1f3fd-200d-1f9bc","๐จ๐พโ๐ฆผ":"1f468-1f3fe-200d-1f9bc","๐จ๐ฟโ๐ฆผ":"1f468-1f3ff-200d-1f9bc","๐ฉ๐ปโ๐ฆผ":"1f469-1f3fb-200d-1f9bc","๐ฉ๐ผโ๐ฆผ":"1f469-1f3fc-200d-1f9bc","๐ฉ๐ฝโ๐ฆผ":"1f469-1f3fd-200d-1f9bc","๐ฉ๐พโ๐ฆผ":"1f469-1f3fe-200d-1f9bc","๐ฉ๐ฟโ๐ฆผ":"1f469-1f3ff-200d-1f9bc","๐ง๐ปโ๐ฆฝ":"1f9d1-1f3fb-200d-1f9bd","๐ง๐ผโ๐ฆฝ":"1f9d1-1f3fc-200d-1f9bd","๐ง๐ฝโ๐ฆฝ":"1f9d1-1f3fd-200d-1f9bd","๐ง๐พโ๐ฆฝ":"1f9d1-1f3fe-200d-1f9bd","๐ง๐ฟโ๐ฆฝ":"1f9d1-1f3ff-200d-1f9bd","๐จ๐ปโ๐ฆฝ":"1f468-1f3fb-200d-1f9bd","๐จ๐ผโ๐ฆฝ":"1f468-1f3fc-200d-1f9bd","๐จ๐ฝโ๐ฆฝ":"1f468-1f3fd-200d-1f9bd","๐จ๐พโ๐ฆฝ":"1f468-1f3fe-200d-1f9bd","๐จ๐ฟโ๐ฆฝ":"1f468-1f3ff-200d-1f9bd","๐ฉ๐ปโ๐ฆฝ":"1f469-1f3fb-200d-1f9bd","๐ฉ๐ผโ๐ฆฝ":"1f469-1f3fc-200d-1f9bd","๐ฉ๐ฝโ๐ฆฝ":"1f469-1f3fd-200d-1f9bd","๐ฉ๐พโ๐ฆฝ":"1f469-1f3fe-200d-1f9bd","๐ฉ๐ฟโ๐ฆฝ":"1f469-1f3ff-200d-1f9bd","๐โโ๏ธ":"1f3c3-200d-2642-fe0f","๐๐ปโโ":"1f3c3-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3c3-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3c3-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3c3-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3c3-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3c3-200d-2640-fe0f","๐๐ปโโ":"1f3c3-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3c3-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3c3-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3c3-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3c3-1f3ff-200d-2640-fe0f","๐ฏโโ๏ธ":"1f46f-200d-2642-fe0f","๐ฏโโ๏ธ":"1f46f-200d-2640-fe0f","๐งโโ๏ธ":"1f9d6-200d-2642-fe0f","๐ง๐ปโโ":"1f9d6-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d6-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d6-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d6-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d6-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d6-200d-2640-fe0f","๐ง๐ปโโ":"1f9d6-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d6-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d6-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d6-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d6-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d7-200d-2642-fe0f","๐ง๐ปโโ":"1f9d7-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d7-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d7-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d7-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d7-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d7-200d-2640-fe0f","๐ง๐ปโโ":"1f9d7-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d7-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d7-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d7-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d7-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3cc-fe0f-200d-2642-fe0f","๐๏ธโโ":"1f3cc-fe0f-200d-2642-fe0f","๐๐ปโโ":"1f3cc-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3cc-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3cc-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3cc-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3cc-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3cc-fe0f-200d-2640-fe0f","๐๏ธโโ":"1f3cc-fe0f-200d-2640-fe0f","๐๐ปโโ":"1f3cc-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3cc-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3cc-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3cc-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3cc-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3c4-200d-2642-fe0f","๐๐ปโโ":"1f3c4-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3c4-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3c4-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3c4-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3c4-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3c4-200d-2640-fe0f","๐๐ปโโ":"1f3c4-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3c4-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3c4-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3c4-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3c4-1f3ff-200d-2640-fe0f","๐ฃโโ๏ธ":"1f6a3-200d-2642-fe0f","๐ฃ๐ปโโ":"1f6a3-1f3fb-200d-2642-fe0f","๐ฃ๐ผโโ":"1f6a3-1f3fc-200d-2642-fe0f","๐ฃ๐ฝโโ":"1f6a3-1f3fd-200d-2642-fe0f","๐ฃ๐พโโ":"1f6a3-1f3fe-200d-2642-fe0f","๐ฃ๐ฟโโ":"1f6a3-1f3ff-200d-2642-fe0f","๐ฃโโ๏ธ":"1f6a3-200d-2640-fe0f","๐ฃ๐ปโโ":"1f6a3-1f3fb-200d-2640-fe0f","๐ฃ๐ผโโ":"1f6a3-1f3fc-200d-2640-fe0f","๐ฃ๐ฝโโ":"1f6a3-1f3fd-200d-2640-fe0f","๐ฃ๐พโโ":"1f6a3-1f3fe-200d-2640-fe0f","๐ฃ๐ฟโโ":"1f6a3-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3ca-200d-2642-fe0f","๐๐ปโโ":"1f3ca-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3ca-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3ca-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3ca-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3ca-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3ca-200d-2640-fe0f","๐๐ปโโ":"1f3ca-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3ca-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3ca-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3ca-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3ca-1f3ff-200d-2640-fe0f","โนโโ๏ธ":"26f9-fe0f-200d-2642-fe0f","โน๏ธโโ":"26f9-fe0f-200d-2642-fe0f","โน๐ปโโ":"26f9-1f3fb-200d-2642-fe0f","โน๐ผโโ":"26f9-1f3fc-200d-2642-fe0f","โน๐ฝโโ":"26f9-1f3fd-200d-2642-fe0f","โน๐พโโ":"26f9-1f3fe-200d-2642-fe0f","โน๐ฟโโ":"26f9-1f3ff-200d-2642-fe0f","โนโโ๏ธ":"26f9-fe0f-200d-2640-fe0f","โน๏ธโโ":"26f9-fe0f-200d-2640-fe0f","โน๐ปโโ":"26f9-1f3fb-200d-2640-fe0f","โน๐ผโโ":"26f9-1f3fc-200d-2640-fe0f","โน๐ฝโโ":"26f9-1f3fd-200d-2640-fe0f","โน๐พโโ":"26f9-1f3fe-200d-2640-fe0f","โน๐ฟโโ":"26f9-1f3ff-200d-2640-fe0f","๐โโ๏ธ":"1f3cb-fe0f-200d-2642-fe0f","๐๏ธโโ":"1f3cb-fe0f-200d-2642-fe0f","๐๐ปโโ":"1f3cb-1f3fb-200d-2642-fe0f","๐๐ผโโ":"1f3cb-1f3fc-200d-2642-fe0f","๐๐ฝโโ":"1f3cb-1f3fd-200d-2642-fe0f","๐๐พโโ":"1f3cb-1f3fe-200d-2642-fe0f","๐๐ฟโโ":"1f3cb-1f3ff-200d-2642-fe0f","๐โโ๏ธ":"1f3cb-fe0f-200d-2640-fe0f","๐๏ธโโ":"1f3cb-fe0f-200d-2640-fe0f","๐๐ปโโ":"1f3cb-1f3fb-200d-2640-fe0f","๐๐ผโโ":"1f3cb-1f3fc-200d-2640-fe0f","๐๐ฝโโ":"1f3cb-1f3fd-200d-2640-fe0f","๐๐พโโ":"1f3cb-1f3fe-200d-2640-fe0f","๐๐ฟโโ":"1f3cb-1f3ff-200d-2640-fe0f","๐ดโโ๏ธ":"1f6b4-200d-2642-fe0f","๐ด๐ปโโ":"1f6b4-1f3fb-200d-2642-fe0f","๐ด๐ผโโ":"1f6b4-1f3fc-200d-2642-fe0f","๐ด๐ฝโโ":"1f6b4-1f3fd-200d-2642-fe0f","๐ด๐พโโ":"1f6b4-1f3fe-200d-2642-fe0f","๐ด๐ฟโโ":"1f6b4-1f3ff-200d-2642-fe0f","๐ดโโ๏ธ":"1f6b4-200d-2640-fe0f","๐ด๐ปโโ":"1f6b4-1f3fb-200d-2640-fe0f","๐ด๐ผโโ":"1f6b4-1f3fc-200d-2640-fe0f","๐ด๐ฝโโ":"1f6b4-1f3fd-200d-2640-fe0f","๐ด๐พโโ":"1f6b4-1f3fe-200d-2640-fe0f","๐ด๐ฟโโ":"1f6b4-1f3ff-200d-2640-fe0f","๐ตโโ๏ธ":"1f6b5-200d-2642-fe0f","๐ต๐ปโโ":"1f6b5-1f3fb-200d-2642-fe0f","๐ต๐ผโโ":"1f6b5-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ":"1f6b5-1f3fd-200d-2642-fe0f","๐ต๐พโโ":"1f6b5-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ":"1f6b5-1f3ff-200d-2642-fe0f","๐ตโโ๏ธ":"1f6b5-200d-2640-fe0f","๐ต๐ปโโ":"1f6b5-1f3fb-200d-2640-fe0f","๐ต๐ผโโ":"1f6b5-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ":"1f6b5-1f3fd-200d-2640-fe0f","๐ต๐พโโ":"1f6b5-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ":"1f6b5-1f3ff-200d-2640-fe0f","๐คธโโ๏ธ":"1f938-200d-2642-fe0f","๐คธ๐ปโโ":"1f938-1f3fb-200d-2642-fe0f","๐คธ๐ผโโ":"1f938-1f3fc-200d-2642-fe0f","๐คธ๐ฝโโ":"1f938-1f3fd-200d-2642-fe0f","๐คธ๐พโโ":"1f938-1f3fe-200d-2642-fe0f","๐คธ๐ฟโโ":"1f938-1f3ff-200d-2642-fe0f","๐คธโโ๏ธ":"1f938-200d-2640-fe0f","๐คธ๐ปโโ":"1f938-1f3fb-200d-2640-fe0f","๐คธ๐ผโโ":"1f938-1f3fc-200d-2640-fe0f","๐คธ๐ฝโโ":"1f938-1f3fd-200d-2640-fe0f","๐คธ๐พโโ":"1f938-1f3fe-200d-2640-fe0f","๐คธ๐ฟโโ":"1f938-1f3ff-200d-2640-fe0f","๐คผโโ๏ธ":"1f93c-200d-2642-fe0f","๐คผโโ๏ธ":"1f93c-200d-2640-fe0f","๐คฝโโ๏ธ":"1f93d-200d-2642-fe0f","๐คฝ๐ปโโ":"1f93d-1f3fb-200d-2642-fe0f","๐คฝ๐ผโโ":"1f93d-1f3fc-200d-2642-fe0f","๐คฝ๐ฝโโ":"1f93d-1f3fd-200d-2642-fe0f","๐คฝ๐พโโ":"1f93d-1f3fe-200d-2642-fe0f","๐คฝ๐ฟโโ":"1f93d-1f3ff-200d-2642-fe0f","๐คฝโโ๏ธ":"1f93d-200d-2640-fe0f","๐คฝ๐ปโโ":"1f93d-1f3fb-200d-2640-fe0f","๐คฝ๐ผโโ":"1f93d-1f3fc-200d-2640-fe0f","๐คฝ๐ฝโโ":"1f93d-1f3fd-200d-2640-fe0f","๐คฝ๐พโโ":"1f93d-1f3fe-200d-2640-fe0f","๐คฝ๐ฟโโ":"1f93d-1f3ff-200d-2640-fe0f","๐คพโโ๏ธ":"1f93e-200d-2642-fe0f","๐คพ๐ปโโ":"1f93e-1f3fb-200d-2642-fe0f","๐คพ๐ผโโ":"1f93e-1f3fc-200d-2642-fe0f","๐คพ๐ฝโโ":"1f93e-1f3fd-200d-2642-fe0f","๐คพ๐พโโ":"1f93e-1f3fe-200d-2642-fe0f","๐คพ๐ฟโโ":"1f93e-1f3ff-200d-2642-fe0f","๐คพโโ๏ธ":"1f93e-200d-2640-fe0f","๐คพ๐ปโโ":"1f93e-1f3fb-200d-2640-fe0f","๐คพ๐ผโโ":"1f93e-1f3fc-200d-2640-fe0f","๐คพ๐ฝโโ":"1f93e-1f3fd-200d-2640-fe0f","๐คพ๐พโโ":"1f93e-1f3fe-200d-2640-fe0f","๐คพ๐ฟโโ":"1f93e-1f3ff-200d-2640-fe0f","๐คนโโ๏ธ":"1f939-200d-2642-fe0f","๐คน๐ปโโ":"1f939-1f3fb-200d-2642-fe0f","๐คน๐ผโโ":"1f939-1f3fc-200d-2642-fe0f","๐คน๐ฝโโ":"1f939-1f3fd-200d-2642-fe0f","๐คน๐พโโ":"1f939-1f3fe-200d-2642-fe0f","๐คน๐ฟโโ":"1f939-1f3ff-200d-2642-fe0f","๐คนโโ๏ธ":"1f939-200d-2640-fe0f","๐คน๐ปโโ":"1f939-1f3fb-200d-2640-fe0f","๐คน๐ผโโ":"1f939-1f3fc-200d-2640-fe0f","๐คน๐ฝโโ":"1f939-1f3fd-200d-2640-fe0f","๐คน๐พโโ":"1f939-1f3fe-200d-2640-fe0f","๐คน๐ฟโโ":"1f939-1f3ff-200d-2640-fe0f","๐งโโ๏ธ":"1f9d8-200d-2642-fe0f","๐ง๐ปโโ":"1f9d8-1f3fb-200d-2642-fe0f","๐ง๐ผโโ":"1f9d8-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ":"1f9d8-1f3fd-200d-2642-fe0f","๐ง๐พโโ":"1f9d8-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ":"1f9d8-1f3ff-200d-2642-fe0f","๐งโโ๏ธ":"1f9d8-200d-2640-fe0f","๐ง๐ปโโ":"1f9d8-1f3fb-200d-2640-fe0f","๐ง๐ผโโ":"1f9d8-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ":"1f9d8-1f3fd-200d-2640-fe0f","๐ง๐พโโ":"1f9d8-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ":"1f9d8-1f3ff-200d-2640-fe0f","๐ปโโ๏ธ":"1f43b-200d-2744-fe0f","๐ณ๏ธโ๐":"1f3f3-fe0f-200d-1f308","๐ณโโง๏ธ":"1f3f3-fe0f-200d-26a7-fe0f","๐ณ๏ธโโง":"1f3f3-fe0f-200d-26a7-fe0f","๐ดโโ ๏ธ":"1f3f4-200d-2620-fe0f","๐๏ธโ๐จ๏ธ":"1f441-200d-1f5e8","๐ง๐ปโโ๏ธ":"1f9d4-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d4-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d4-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d4-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d4-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d4-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d4-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d4-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d4-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d4-1f3ff-200d-2640-fe0f","๐ฑ๐ปโโ๏ธ":"1f471-1f3fb-200d-2640-fe0f","๐ฑ๐ผโโ๏ธ":"1f471-1f3fc-200d-2640-fe0f","๐ฑ๐ฝโโ๏ธ":"1f471-1f3fd-200d-2640-fe0f","๐ฑ๐พโโ๏ธ":"1f471-1f3fe-200d-2640-fe0f","๐ฑ๐ฟโโ๏ธ":"1f471-1f3ff-200d-2640-fe0f","๐ฑ๐ปโโ๏ธ":"1f471-1f3fb-200d-2642-fe0f","๐ฑ๐ผโโ๏ธ":"1f471-1f3fc-200d-2642-fe0f","๐ฑ๐ฝโโ๏ธ":"1f471-1f3fd-200d-2642-fe0f","๐ฑ๐พโโ๏ธ":"1f471-1f3fe-200d-2642-fe0f","๐ฑ๐ฟโโ๏ธ":"1f471-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64d-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f64d-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f64d-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f64d-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f64d-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64d-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f64d-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f64d-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f64d-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f64d-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f64e-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f64e-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f64e-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f64e-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f64e-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64e-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f64e-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f64e-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f64e-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f64e-1f3ff-200d-2640-fe0f","๐ ๐ปโโ๏ธ":"1f645-1f3fb-200d-2642-fe0f","๐ ๐ผโโ๏ธ":"1f645-1f3fc-200d-2642-fe0f","๐ ๐ฝโโ๏ธ":"1f645-1f3fd-200d-2642-fe0f","๐ ๐พโโ๏ธ":"1f645-1f3fe-200d-2642-fe0f","๐ ๐ฟโโ๏ธ":"1f645-1f3ff-200d-2642-fe0f","๐ ๐ปโโ๏ธ":"1f645-1f3fb-200d-2640-fe0f","๐ ๐ผโโ๏ธ":"1f645-1f3fc-200d-2640-fe0f","๐ ๐ฝโโ๏ธ":"1f645-1f3fd-200d-2640-fe0f","๐ ๐พโโ๏ธ":"1f645-1f3fe-200d-2640-fe0f","๐ ๐ฟโโ๏ธ":"1f645-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f646-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f646-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f646-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f646-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f646-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f646-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f646-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f646-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f646-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f646-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f481-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f481-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f481-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f481-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f481-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f481-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f481-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f481-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f481-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f481-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f64b-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f64b-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f64b-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f64b-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f64b-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f64b-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f64b-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f64b-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f64b-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f64b-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9cf-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9cf-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9cf-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9cf-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9cf-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9cf-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9cf-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9cf-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9cf-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9cf-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f647-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f647-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f647-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f647-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f647-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f647-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f647-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f647-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f647-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f647-1f3ff-200d-2640-fe0f","๐คฆ๐ปโโ๏ธ":"1f926-1f3fb-200d-2642-fe0f","๐คฆ๐ผโโ๏ธ":"1f926-1f3fc-200d-2642-fe0f","๐คฆ๐ฝโโ๏ธ":"1f926-1f3fd-200d-2642-fe0f","๐คฆ๐พโโ๏ธ":"1f926-1f3fe-200d-2642-fe0f","๐คฆ๐ฟโโ๏ธ":"1f926-1f3ff-200d-2642-fe0f","๐คฆ๐ปโโ๏ธ":"1f926-1f3fb-200d-2640-fe0f","๐คฆ๐ผโโ๏ธ":"1f926-1f3fc-200d-2640-fe0f","๐คฆ๐ฝโโ๏ธ":"1f926-1f3fd-200d-2640-fe0f","๐คฆ๐พโโ๏ธ":"1f926-1f3fe-200d-2640-fe0f","๐คฆ๐ฟโโ๏ธ":"1f926-1f3ff-200d-2640-fe0f","๐คท๐ปโโ๏ธ":"1f937-1f3fb-200d-2642-fe0f","๐คท๐ผโโ๏ธ":"1f937-1f3fc-200d-2642-fe0f","๐คท๐ฝโโ๏ธ":"1f937-1f3fd-200d-2642-fe0f","๐คท๐พโโ๏ธ":"1f937-1f3fe-200d-2642-fe0f","๐คท๐ฟโโ๏ธ":"1f937-1f3ff-200d-2642-fe0f","๐คท๐ปโโ๏ธ":"1f937-1f3fb-200d-2640-fe0f","๐คท๐ผโโ๏ธ":"1f937-1f3fc-200d-2640-fe0f","๐คท๐ฝโโ๏ธ":"1f937-1f3fd-200d-2640-fe0f","๐คท๐พโโ๏ธ":"1f937-1f3fe-200d-2640-fe0f","๐คท๐ฟโโ๏ธ":"1f937-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d1-1f3fb-200d-2695-fe0f","๐ง๐ผโโ๏ธ":"1f9d1-1f3fc-200d-2695-fe0f","๐ง๐ฝโโ๏ธ":"1f9d1-1f3fd-200d-2695-fe0f","๐ง๐พโโ๏ธ":"1f9d1-1f3fe-200d-2695-fe0f","๐ง๐ฟโโ๏ธ":"1f9d1-1f3ff-200d-2695-fe0f","๐จ๐ปโโ๏ธ":"1f468-1f3fb-200d-2695-fe0f","๐จ๐ผโโ๏ธ":"1f468-1f3fc-200d-2695-fe0f","๐จ๐ฝโโ๏ธ":"1f468-1f3fd-200d-2695-fe0f","๐จ๐พโโ๏ธ":"1f468-1f3fe-200d-2695-fe0f","๐จ๐ฟโโ๏ธ":"1f468-1f3ff-200d-2695-fe0f","๐ฉ๐ปโโ๏ธ":"1f469-1f3fb-200d-2695-fe0f","๐ฉ๐ผโโ๏ธ":"1f469-1f3fc-200d-2695-fe0f","๐ฉ๐ฝโโ๏ธ":"1f469-1f3fd-200d-2695-fe0f","๐ฉ๐พโโ๏ธ":"1f469-1f3fe-200d-2695-fe0f","๐ฉ๐ฟโโ๏ธ":"1f469-1f3ff-200d-2695-fe0f","๐ง๐ปโโ๏ธ":"1f9d1-1f3fb-200d-2696-fe0f","๐ง๐ผโโ๏ธ":"1f9d1-1f3fc-200d-2696-fe0f","๐ง๐ฝโโ๏ธ":"1f9d1-1f3fd-200d-2696-fe0f","๐ง๐พโโ๏ธ":"1f9d1-1f3fe-200d-2696-fe0f","๐ง๐ฟโโ๏ธ":"1f9d1-1f3ff-200d-2696-fe0f","๐จ๐ปโโ๏ธ":"1f468-1f3fb-200d-2696-fe0f","๐จ๐ผโโ๏ธ":"1f468-1f3fc-200d-2696-fe0f","๐จ๐ฝโโ๏ธ":"1f468-1f3fd-200d-2696-fe0f","๐จ๐พโโ๏ธ":"1f468-1f3fe-200d-2696-fe0f","๐จ๐ฟโโ๏ธ":"1f468-1f3ff-200d-2696-fe0f","๐ฉ๐ปโโ๏ธ":"1f469-1f3fb-200d-2696-fe0f","๐ฉ๐ผโโ๏ธ":"1f469-1f3fc-200d-2696-fe0f","๐ฉ๐ฝโโ๏ธ":"1f469-1f3fd-200d-2696-fe0f","๐ฉ๐พโโ๏ธ":"1f469-1f3fe-200d-2696-fe0f","๐ฉ๐ฟโโ๏ธ":"1f469-1f3ff-200d-2696-fe0f","๐ง๐ปโโ๏ธ":"1f9d1-1f3fb-200d-2708-fe0f","๐ง๐ผโโ๏ธ":"1f9d1-1f3fc-200d-2708-fe0f","๐ง๐ฝโโ๏ธ":"1f9d1-1f3fd-200d-2708-fe0f","๐ง๐พโโ๏ธ":"1f9d1-1f3fe-200d-2708-fe0f","๐ง๐ฟโโ๏ธ":"1f9d1-1f3ff-200d-2708-fe0f","๐จ๐ปโโ๏ธ":"1f468-1f3fb-200d-2708-fe0f","๐จ๐ผโโ๏ธ":"1f468-1f3fc-200d-2708-fe0f","๐จ๐ฝโโ๏ธ":"1f468-1f3fd-200d-2708-fe0f","๐จ๐พโโ๏ธ":"1f468-1f3fe-200d-2708-fe0f","๐จ๐ฟโโ๏ธ":"1f468-1f3ff-200d-2708-fe0f","๐ฉ๐ปโโ๏ธ":"1f469-1f3fb-200d-2708-fe0f","๐ฉ๐ผโโ๏ธ":"1f469-1f3fc-200d-2708-fe0f","๐ฉ๐ฝโโ๏ธ":"1f469-1f3fd-200d-2708-fe0f","๐ฉ๐พโโ๏ธ":"1f469-1f3fe-200d-2708-fe0f","๐ฉ๐ฟโโ๏ธ":"1f469-1f3ff-200d-2708-fe0f","๐ฎ๐ปโโ๏ธ":"1f46e-1f3fb-200d-2642-fe0f","๐ฎ๐ผโโ๏ธ":"1f46e-1f3fc-200d-2642-fe0f","๐ฎ๐ฝโโ๏ธ":"1f46e-1f3fd-200d-2642-fe0f","๐ฎ๐พโโ๏ธ":"1f46e-1f3fe-200d-2642-fe0f","๐ฎ๐ฟโโ๏ธ":"1f46e-1f3ff-200d-2642-fe0f","๐ฎ๐ปโโ๏ธ":"1f46e-1f3fb-200d-2640-fe0f","๐ฎ๐ผโโ๏ธ":"1f46e-1f3fc-200d-2640-fe0f","๐ฎ๐ฝโโ๏ธ":"1f46e-1f3fd-200d-2640-fe0f","๐ฎ๐พโโ๏ธ":"1f46e-1f3fe-200d-2640-fe0f","๐ฎ๐ฟโโ๏ธ":"1f46e-1f3ff-200d-2640-fe0f","๐ต๏ธโโ๏ธ":"1f575-fe0f-200d-2642-fe0f","๐ต๐ปโโ๏ธ":"1f575-1f3fb-200d-2642-fe0f","๐ต๐ผโโ๏ธ":"1f575-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ๏ธ":"1f575-1f3fd-200d-2642-fe0f","๐ต๐พโโ๏ธ":"1f575-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ๏ธ":"1f575-1f3ff-200d-2642-fe0f","๐ต๏ธโโ๏ธ":"1f575-fe0f-200d-2640-fe0f","๐ต๐ปโโ๏ธ":"1f575-1f3fb-200d-2640-fe0f","๐ต๐ผโโ๏ธ":"1f575-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ๏ธ":"1f575-1f3fd-200d-2640-fe0f","๐ต๐พโโ๏ธ":"1f575-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ๏ธ":"1f575-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f482-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f482-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f482-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f482-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f482-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f482-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f482-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f482-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f482-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f482-1f3ff-200d-2640-fe0f","๐ท๐ปโโ๏ธ":"1f477-1f3fb-200d-2642-fe0f","๐ท๐ผโโ๏ธ":"1f477-1f3fc-200d-2642-fe0f","๐ท๐ฝโโ๏ธ":"1f477-1f3fd-200d-2642-fe0f","๐ท๐พโโ๏ธ":"1f477-1f3fe-200d-2642-fe0f","๐ท๐ฟโโ๏ธ":"1f477-1f3ff-200d-2642-fe0f","๐ท๐ปโโ๏ธ":"1f477-1f3fb-200d-2640-fe0f","๐ท๐ผโโ๏ธ":"1f477-1f3fc-200d-2640-fe0f","๐ท๐ฝโโ๏ธ":"1f477-1f3fd-200d-2640-fe0f","๐ท๐พโโ๏ธ":"1f477-1f3fe-200d-2640-fe0f","๐ท๐ฟโโ๏ธ":"1f477-1f3ff-200d-2640-fe0f","๐ณ๐ปโโ๏ธ":"1f473-1f3fb-200d-2642-fe0f","๐ณ๐ผโโ๏ธ":"1f473-1f3fc-200d-2642-fe0f","๐ณ๐ฝโโ๏ธ":"1f473-1f3fd-200d-2642-fe0f","๐ณ๐พโโ๏ธ":"1f473-1f3fe-200d-2642-fe0f","๐ณ๐ฟโโ๏ธ":"1f473-1f3ff-200d-2642-fe0f","๐ณ๐ปโโ๏ธ":"1f473-1f3fb-200d-2640-fe0f","๐ณ๐ผโโ๏ธ":"1f473-1f3fc-200d-2640-fe0f","๐ณ๐ฝโโ๏ธ":"1f473-1f3fd-200d-2640-fe0f","๐ณ๐พโโ๏ธ":"1f473-1f3fe-200d-2640-fe0f","๐ณ๐ฟโโ๏ธ":"1f473-1f3ff-200d-2640-fe0f","๐คต๐ปโโ๏ธ":"1f935-1f3fb-200d-2642-fe0f","๐คต๐ผโโ๏ธ":"1f935-1f3fc-200d-2642-fe0f","๐คต๐ฝโโ๏ธ":"1f935-1f3fd-200d-2642-fe0f","๐คต๐พโโ๏ธ":"1f935-1f3fe-200d-2642-fe0f","๐คต๐ฟโโ๏ธ":"1f935-1f3ff-200d-2642-fe0f","๐คต๐ปโโ๏ธ":"1f935-1f3fb-200d-2640-fe0f","๐คต๐ผโโ๏ธ":"1f935-1f3fc-200d-2640-fe0f","๐คต๐ฝโโ๏ธ":"1f935-1f3fd-200d-2640-fe0f","๐คต๐พโโ๏ธ":"1f935-1f3fe-200d-2640-fe0f","๐คต๐ฟโโ๏ธ":"1f935-1f3ff-200d-2640-fe0f","๐ฐ๐ปโโ๏ธ":"1f470-1f3fb-200d-2642-fe0f","๐ฐ๐ผโโ๏ธ":"1f470-1f3fc-200d-2642-fe0f","๐ฐ๐ฝโโ๏ธ":"1f470-1f3fd-200d-2642-fe0f","๐ฐ๐พโโ๏ธ":"1f470-1f3fe-200d-2642-fe0f","๐ฐ๐ฟโโ๏ธ":"1f470-1f3ff-200d-2642-fe0f","๐ฐ๐ปโโ๏ธ":"1f470-1f3fb-200d-2640-fe0f","๐ฐ๐ผโโ๏ธ":"1f470-1f3fc-200d-2640-fe0f","๐ฐ๐ฝโโ๏ธ":"1f470-1f3fd-200d-2640-fe0f","๐ฐ๐พโโ๏ธ":"1f470-1f3fe-200d-2640-fe0f","๐ฐ๐ฟโโ๏ธ":"1f470-1f3ff-200d-2640-fe0f","๐ฆธ๐ปโโ๏ธ":"1f9b8-1f3fb-200d-2642-fe0f","๐ฆธ๐ผโโ๏ธ":"1f9b8-1f3fc-200d-2642-fe0f","๐ฆธ๐ฝโโ๏ธ":"1f9b8-1f3fd-200d-2642-fe0f","๐ฆธ๐พโโ๏ธ":"1f9b8-1f3fe-200d-2642-fe0f","๐ฆธ๐ฟโโ๏ธ":"1f9b8-1f3ff-200d-2642-fe0f","๐ฆธ๐ปโโ๏ธ":"1f9b8-1f3fb-200d-2640-fe0f","๐ฆธ๐ผโโ๏ธ":"1f9b8-1f3fc-200d-2640-fe0f","๐ฆธ๐ฝโโ๏ธ":"1f9b8-1f3fd-200d-2640-fe0f","๐ฆธ๐พโโ๏ธ":"1f9b8-1f3fe-200d-2640-fe0f","๐ฆธ๐ฟโโ๏ธ":"1f9b8-1f3ff-200d-2640-fe0f","๐ฆน๐ปโโ๏ธ":"1f9b9-1f3fb-200d-2642-fe0f","๐ฆน๐ผโโ๏ธ":"1f9b9-1f3fc-200d-2642-fe0f","๐ฆน๐ฝโโ๏ธ":"1f9b9-1f3fd-200d-2642-fe0f","๐ฆน๐พโโ๏ธ":"1f9b9-1f3fe-200d-2642-fe0f","๐ฆน๐ฟโโ๏ธ":"1f9b9-1f3ff-200d-2642-fe0f","๐ฆน๐ปโโ๏ธ":"1f9b9-1f3fb-200d-2640-fe0f","๐ฆน๐ผโโ๏ธ":"1f9b9-1f3fc-200d-2640-fe0f","๐ฆน๐ฝโโ๏ธ":"1f9b9-1f3fd-200d-2640-fe0f","๐ฆน๐พโโ๏ธ":"1f9b9-1f3fe-200d-2640-fe0f","๐ฆน๐ฟโโ๏ธ":"1f9b9-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d9-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d9-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d9-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d9-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d9-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d9-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d9-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d9-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d9-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d9-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9da-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9da-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9da-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9da-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9da-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9da-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9da-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9da-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9da-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9da-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9db-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9db-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9db-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9db-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9db-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9db-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9db-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9db-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9db-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9db-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9dc-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9dc-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9dc-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9dc-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9dc-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9dc-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9dc-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9dc-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9dc-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9dc-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9dd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9dd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9dd-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9dd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9dd-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9dd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9dd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9dd-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9dd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9dd-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f486-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f486-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f486-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f486-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f486-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f486-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f486-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f486-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f486-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f486-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f487-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f487-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f487-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f487-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f487-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f487-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f487-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f487-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f487-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f487-1f3ff-200d-2640-fe0f","๐ถ๐ปโโ๏ธ":"1f6b6-1f3fb-200d-2642-fe0f","๐ถ๐ผโโ๏ธ":"1f6b6-1f3fc-200d-2642-fe0f","๐ถ๐ฝโโ๏ธ":"1f6b6-1f3fd-200d-2642-fe0f","๐ถ๐พโโ๏ธ":"1f6b6-1f3fe-200d-2642-fe0f","๐ถ๐ฟโโ๏ธ":"1f6b6-1f3ff-200d-2642-fe0f","๐ถ๐ปโโ๏ธ":"1f6b6-1f3fb-200d-2640-fe0f","๐ถ๐ผโโ๏ธ":"1f6b6-1f3fc-200d-2640-fe0f","๐ถ๐ฝโโ๏ธ":"1f6b6-1f3fd-200d-2640-fe0f","๐ถ๐พโโ๏ธ":"1f6b6-1f3fe-200d-2640-fe0f","๐ถ๐ฟโโ๏ธ":"1f6b6-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9cd-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9cd-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9cd-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9cd-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9cd-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9cd-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9cd-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9cd-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9cd-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9cd-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9ce-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9ce-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9ce-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9ce-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9ce-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9ce-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9ce-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9ce-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9ce-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9ce-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3c3-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3c3-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3c3-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3c3-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3c3-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3c3-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3c3-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3c3-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3c3-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3c3-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d6-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d6-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d6-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d6-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d6-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d6-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d6-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d6-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d6-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d6-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d7-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d7-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d7-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d7-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d7-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d7-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d7-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d7-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d7-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d7-1f3ff-200d-2640-fe0f","๐๏ธโโ๏ธ":"1f3cc-fe0f-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3cc-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3cc-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3cc-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3cc-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3cc-1f3ff-200d-2642-fe0f","๐๏ธโโ๏ธ":"1f3cc-fe0f-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3cc-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3cc-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3cc-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3cc-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3cc-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3c4-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3c4-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3c4-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3c4-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3c4-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3c4-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3c4-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3c4-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3c4-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3c4-1f3ff-200d-2640-fe0f","๐ฃ๐ปโโ๏ธ":"1f6a3-1f3fb-200d-2642-fe0f","๐ฃ๐ผโโ๏ธ":"1f6a3-1f3fc-200d-2642-fe0f","๐ฃ๐ฝโโ๏ธ":"1f6a3-1f3fd-200d-2642-fe0f","๐ฃ๐พโโ๏ธ":"1f6a3-1f3fe-200d-2642-fe0f","๐ฃ๐ฟโโ๏ธ":"1f6a3-1f3ff-200d-2642-fe0f","๐ฃ๐ปโโ๏ธ":"1f6a3-1f3fb-200d-2640-fe0f","๐ฃ๐ผโโ๏ธ":"1f6a3-1f3fc-200d-2640-fe0f","๐ฃ๐ฝโโ๏ธ":"1f6a3-1f3fd-200d-2640-fe0f","๐ฃ๐พโโ๏ธ":"1f6a3-1f3fe-200d-2640-fe0f","๐ฃ๐ฟโโ๏ธ":"1f6a3-1f3ff-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3ca-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3ca-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3ca-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3ca-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3ca-1f3ff-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3ca-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3ca-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3ca-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3ca-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3ca-1f3ff-200d-2640-fe0f","โน๏ธโโ๏ธ":"26f9-fe0f-200d-2642-fe0f","โน๐ปโโ๏ธ":"26f9-1f3fb-200d-2642-fe0f","โน๐ผโโ๏ธ":"26f9-1f3fc-200d-2642-fe0f","โน๐ฝโโ๏ธ":"26f9-1f3fd-200d-2642-fe0f","โน๐พโโ๏ธ":"26f9-1f3fe-200d-2642-fe0f","โน๐ฟโโ๏ธ":"26f9-1f3ff-200d-2642-fe0f","โน๏ธโโ๏ธ":"26f9-fe0f-200d-2640-fe0f","โน๐ปโโ๏ธ":"26f9-1f3fb-200d-2640-fe0f","โน๐ผโโ๏ธ":"26f9-1f3fc-200d-2640-fe0f","โน๐ฝโโ๏ธ":"26f9-1f3fd-200d-2640-fe0f","โน๐พโโ๏ธ":"26f9-1f3fe-200d-2640-fe0f","โน๐ฟโโ๏ธ":"26f9-1f3ff-200d-2640-fe0f","๐๏ธโโ๏ธ":"1f3cb-fe0f-200d-2642-fe0f","๐๐ปโโ๏ธ":"1f3cb-1f3fb-200d-2642-fe0f","๐๐ผโโ๏ธ":"1f3cb-1f3fc-200d-2642-fe0f","๐๐ฝโโ๏ธ":"1f3cb-1f3fd-200d-2642-fe0f","๐๐พโโ๏ธ":"1f3cb-1f3fe-200d-2642-fe0f","๐๐ฟโโ๏ธ":"1f3cb-1f3ff-200d-2642-fe0f","๐๏ธโโ๏ธ":"1f3cb-fe0f-200d-2640-fe0f","๐๐ปโโ๏ธ":"1f3cb-1f3fb-200d-2640-fe0f","๐๐ผโโ๏ธ":"1f3cb-1f3fc-200d-2640-fe0f","๐๐ฝโโ๏ธ":"1f3cb-1f3fd-200d-2640-fe0f","๐๐พโโ๏ธ":"1f3cb-1f3fe-200d-2640-fe0f","๐๐ฟโโ๏ธ":"1f3cb-1f3ff-200d-2640-fe0f","๐ด๐ปโโ๏ธ":"1f6b4-1f3fb-200d-2642-fe0f","๐ด๐ผโโ๏ธ":"1f6b4-1f3fc-200d-2642-fe0f","๐ด๐ฝโโ๏ธ":"1f6b4-1f3fd-200d-2642-fe0f","๐ด๐พโโ๏ธ":"1f6b4-1f3fe-200d-2642-fe0f","๐ด๐ฟโโ๏ธ":"1f6b4-1f3ff-200d-2642-fe0f","๐ด๐ปโโ๏ธ":"1f6b4-1f3fb-200d-2640-fe0f","๐ด๐ผโโ๏ธ":"1f6b4-1f3fc-200d-2640-fe0f","๐ด๐ฝโโ๏ธ":"1f6b4-1f3fd-200d-2640-fe0f","๐ด๐พโโ๏ธ":"1f6b4-1f3fe-200d-2640-fe0f","๐ด๐ฟโโ๏ธ":"1f6b4-1f3ff-200d-2640-fe0f","๐ต๐ปโโ๏ธ":"1f6b5-1f3fb-200d-2642-fe0f","๐ต๐ผโโ๏ธ":"1f6b5-1f3fc-200d-2642-fe0f","๐ต๐ฝโโ๏ธ":"1f6b5-1f3fd-200d-2642-fe0f","๐ต๐พโโ๏ธ":"1f6b5-1f3fe-200d-2642-fe0f","๐ต๐ฟโโ๏ธ":"1f6b5-1f3ff-200d-2642-fe0f","๐ต๐ปโโ๏ธ":"1f6b5-1f3fb-200d-2640-fe0f","๐ต๐ผโโ๏ธ":"1f6b5-1f3fc-200d-2640-fe0f","๐ต๐ฝโโ๏ธ":"1f6b5-1f3fd-200d-2640-fe0f","๐ต๐พโโ๏ธ":"1f6b5-1f3fe-200d-2640-fe0f","๐ต๐ฟโโ๏ธ":"1f6b5-1f3ff-200d-2640-fe0f","๐คธ๐ปโโ๏ธ":"1f938-1f3fb-200d-2642-fe0f","๐คธ๐ผโโ๏ธ":"1f938-1f3fc-200d-2642-fe0f","๐คธ๐ฝโโ๏ธ":"1f938-1f3fd-200d-2642-fe0f","๐คธ๐พโโ๏ธ":"1f938-1f3fe-200d-2642-fe0f","๐คธ๐ฟโโ๏ธ":"1f938-1f3ff-200d-2642-fe0f","๐คธ๐ปโโ๏ธ":"1f938-1f3fb-200d-2640-fe0f","๐คธ๐ผโโ๏ธ":"1f938-1f3fc-200d-2640-fe0f","๐คธ๐ฝโโ๏ธ":"1f938-1f3fd-200d-2640-fe0f","๐คธ๐พโโ๏ธ":"1f938-1f3fe-200d-2640-fe0f","๐คธ๐ฟโโ๏ธ":"1f938-1f3ff-200d-2640-fe0f","๐คฝ๐ปโโ๏ธ":"1f93d-1f3fb-200d-2642-fe0f","๐คฝ๐ผโโ๏ธ":"1f93d-1f3fc-200d-2642-fe0f","๐คฝ๐ฝโโ๏ธ":"1f93d-1f3fd-200d-2642-fe0f","๐คฝ๐พโโ๏ธ":"1f93d-1f3fe-200d-2642-fe0f","๐คฝ๐ฟโโ๏ธ":"1f93d-1f3ff-200d-2642-fe0f","๐คฝ๐ปโโ๏ธ":"1f93d-1f3fb-200d-2640-fe0f","๐คฝ๐ผโโ๏ธ":"1f93d-1f3fc-200d-2640-fe0f","๐คฝ๐ฝโโ๏ธ":"1f93d-1f3fd-200d-2640-fe0f","๐คฝ๐พโโ๏ธ":"1f93d-1f3fe-200d-2640-fe0f","๐คฝ๐ฟโโ๏ธ":"1f93d-1f3ff-200d-2640-fe0f","๐คพ๐ปโโ๏ธ":"1f93e-1f3fb-200d-2642-fe0f","๐คพ๐ผโโ๏ธ":"1f93e-1f3fc-200d-2642-fe0f","๐คพ๐ฝโโ๏ธ":"1f93e-1f3fd-200d-2642-fe0f","๐คพ๐พโโ๏ธ":"1f93e-1f3fe-200d-2642-fe0f","๐คพ๐ฟโโ๏ธ":"1f93e-1f3ff-200d-2642-fe0f","๐คพ๐ปโโ๏ธ":"1f93e-1f3fb-200d-2640-fe0f","๐คพ๐ผโโ๏ธ":"1f93e-1f3fc-200d-2640-fe0f","๐คพ๐ฝโโ๏ธ":"1f93e-1f3fd-200d-2640-fe0f","๐คพ๐พโโ๏ธ":"1f93e-1f3fe-200d-2640-fe0f","๐คพ๐ฟโโ๏ธ":"1f93e-1f3ff-200d-2640-fe0f","๐คน๐ปโโ๏ธ":"1f939-1f3fb-200d-2642-fe0f","๐คน๐ผโโ๏ธ":"1f939-1f3fc-200d-2642-fe0f","๐คน๐ฝโโ๏ธ":"1f939-1f3fd-200d-2642-fe0f","๐คน๐พโโ๏ธ":"1f939-1f3fe-200d-2642-fe0f","๐คน๐ฟโโ๏ธ":"1f939-1f3ff-200d-2642-fe0f","๐คน๐ปโโ๏ธ":"1f939-1f3fb-200d-2640-fe0f","๐คน๐ผโโ๏ธ":"1f939-1f3fc-200d-2640-fe0f","๐คน๐ฝโโ๏ธ":"1f939-1f3fd-200d-2640-fe0f","๐คน๐พโโ๏ธ":"1f939-1f3fe-200d-2640-fe0f","๐คน๐ฟโโ๏ธ":"1f939-1f3ff-200d-2640-fe0f","๐ง๐ปโโ๏ธ":"1f9d8-1f3fb-200d-2642-fe0f","๐ง๐ผโโ๏ธ":"1f9d8-1f3fc-200d-2642-fe0f","๐ง๐ฝโโ๏ธ":"1f9d8-1f3fd-200d-2642-fe0f","๐ง๐พโโ๏ธ":"1f9d8-1f3fe-200d-2642-fe0f","๐ง๐ฟโโ๏ธ":"1f9d8-1f3ff-200d-2642-fe0f","๐ง๐ปโโ๏ธ":"1f9d8-1f3fb-200d-2640-fe0f","๐ง๐ผโโ๏ธ":"1f9d8-1f3fc-200d-2640-fe0f","๐ง๐ฝโโ๏ธ":"1f9d8-1f3fd-200d-2640-fe0f","๐ง๐พโโ๏ธ":"1f9d8-1f3fe-200d-2640-fe0f","๐ง๐ฟโโ๏ธ":"1f9d8-1f3ff-200d-2640-fe0f","๐งโ๐คโ๐ง":"1f9d1-200d-1f91d-200d-1f9d1","๐ฉโโคโ๐จ":"1f469-200d-2764-fe0f-200d-1f468","๐จโโคโ๐จ":"1f468-200d-2764-fe0f-200d-1f468","๐ฉโโคโ๐ฉ":"1f469-200d-2764-fe0f-200d-1f469","๐จโ๐ฉโ๐ฆ":"1f468-200d-1f469-200d-1f466","๐จโ๐ฉโ๐ง":"1f468-200d-1f469-200d-1f467","๐จโ๐จโ๐ฆ":"1f468-200d-1f468-200d-1f466","๐จโ๐จโ๐ง":"1f468-200d-1f468-200d-1f467","๐ฉโ๐ฉโ๐ฆ":"1f469-200d-1f469-200d-1f466","๐ฉโ๐ฉโ๐ง":"1f469-200d-1f469-200d-1f467","๐จโ๐ฆโ๐ฆ":"1f468-200d-1f466-200d-1f466","๐จโ๐งโ๐ฆ":"1f468-200d-1f467-200d-1f466","๐จโ๐งโ๐ง":"1f468-200d-1f467-200d-1f467","๐ฉโ๐ฆโ๐ฆ":"1f469-200d-1f466-200d-1f466","๐ฉโ๐งโ๐ฆ":"1f469-200d-1f467-200d-1f466","๐ฉโ๐งโ๐ง":"1f469-200d-1f467-200d-1f467","๐ณ๏ธโโง๏ธ":"1f3f3-fe0f-200d-26a7-fe0f","๐ฉโโค๏ธโ๐จ":"1f469-200d-2764-fe0f-200d-1f468","๐จโโค๏ธโ๐จ":"1f468-200d-2764-fe0f-200d-1f468","๐ฉโโค๏ธโ๐ฉ":"1f469-200d-2764-fe0f-200d-1f469","๐ง๐ปโ๐คโ๐ง๐ป":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ปโ๐คโ๐ง๐ผ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ปโ๐คโ๐ง๐ฝ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ปโ๐คโ๐ง๐พ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ปโ๐คโ๐ง๐ฟ":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐ผโ๐คโ๐ง๐ป":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ผโ๐คโ๐ง๐ผ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ผโ๐คโ๐ง๐ฝ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ผโ๐คโ๐ง๐พ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ผโ๐คโ๐ง๐ฟ":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐ฝโ๐คโ๐ง๐ป":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ฝโ๐คโ๐ง๐ผ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ฝโ๐คโ๐ง๐ฝ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ฝโ๐คโ๐ง๐พ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ฝโ๐คโ๐ง๐ฟ":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐พโ๐คโ๐ง๐ป":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐พโ๐คโ๐ง๐ผ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐พโ๐คโ๐ง๐ฝ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐พโ๐คโ๐ง๐พ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐พโ๐คโ๐ง๐ฟ":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","๐ง๐ฟโ๐คโ๐ง๐ป":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","๐ง๐ฟโ๐คโ๐ง๐ผ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","๐ง๐ฟโ๐คโ๐ง๐ฝ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","๐ง๐ฟโ๐คโ๐ง๐พ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","๐ง๐ฟโ๐คโ๐ง๐ฟ":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","๐ฉ๐ปโ๐คโ๐ฉ๐ผ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐ปโ๐คโ๐ฉ๐ฝ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐ปโ๐คโ๐ฉ๐พ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ปโ๐คโ๐ฉ๐ฟ":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐ผโ๐คโ๐ฉ๐ป":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐ผโ๐คโ๐ฉ๐ฝ":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐ผโ๐คโ๐ฉ๐พ":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ผโ๐คโ๐ฉ๐ฟ":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐ฝโ๐คโ๐ฉ๐ป":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐ฝโ๐คโ๐ฉ๐ผ":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐ฝโ๐คโ๐ฉ๐พ":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ฝโ๐คโ๐ฉ๐ฟ":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐พโ๐คโ๐ฉ๐ป":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐พโ๐คโ๐ฉ๐ผ":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐พโ๐คโ๐ฉ๐ฝ":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐พโ๐คโ๐ฉ๐ฟ":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","๐ฉ๐ฟโ๐คโ๐ฉ๐ป":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","๐ฉ๐ฟโ๐คโ๐ฉ๐ผ":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","๐ฉ๐ฟโ๐คโ๐ฉ๐ฝ":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","๐ฉ๐ฟโ๐คโ๐ฉ๐พ":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","๐ฉ๐ปโ๐คโ๐จ๐ผ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐ปโ๐คโ๐จ๐ฝ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐ปโ๐คโ๐จ๐พ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","๐ฉ๐ปโ๐คโ๐จ๐ฟ":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐ผโ๐คโ๐จ๐ป":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐ผโ๐คโ๐จ๐ฝ":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐ผโ๐คโ๐จ๐พ":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","๐ฉ๐ผโ๐คโ๐จ๐ฟ":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐ฝโ๐คโ๐จ๐ป":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐ฝโ๐คโ๐จ๐ผ":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐ฝโ๐คโ๐จ๐พ":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","๐ฉ๐ฝโ๐คโ๐จ๐ฟ":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐พโ๐คโ๐จ๐ป":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐พโ๐คโ๐จ๐ผ":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐พโ๐คโ๐จ๐ฝ":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐พโ๐คโ๐จ๐ฟ":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","๐ฉ๐ฟโ๐คโ๐จ๐ป":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","๐ฉ๐ฟโ๐คโ๐จ๐ผ":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","๐ฉ๐ฟโ๐คโ๐จ๐ฝ":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","๐ฉ๐ฟโ๐คโ๐จ๐พ":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","๐จ๐ปโ๐คโ๐จ๐ผ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","๐จ๐ปโ๐คโ๐จ๐ฝ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","๐จ๐ปโ๐คโ๐จ๐พ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","๐จ๐ปโ๐คโ๐จ๐ฟ":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","๐จ๐ผโ๐คโ๐จ๐ป":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","๐จ๐ผโ๐คโ๐จ๐ฝ":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","๐จ๐ผโ๐คโ๐จ๐พ":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","๐จ๐ผโ๐คโ๐จ๐ฟ":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","๐จ๐ฝโ๐คโ๐จ๐ป":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","๐จ๐ฝโ๐คโ๐จ๐ผ":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","๐จ๐ฝโ๐คโ๐จ๐พ":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","๐จ๐ฝโ๐คโ๐จ๐ฟ":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","๐จ๐พโ๐คโ๐จ๐ป":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","๐จ๐พโ๐คโ๐จ๐ผ":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","๐จ๐พโ๐คโ๐จ๐ฝ":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","๐จ๐พโ๐คโ๐จ๐ฟ":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","๐จ๐ฟโ๐คโ๐จ๐ป":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","๐จ๐ฟโ๐คโ๐จ๐ผ":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","๐จ๐ฟโ๐คโ๐จ๐ฝ":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","๐จ๐ฟโ๐คโ๐จ๐พ":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","๐ฉโโคโ๐โ๐จ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","๐จโโคโ๐โ๐จ":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","๐ฉโโคโ๐โ๐ฉ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","๐ง๐ปโโคโ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ปโโคโ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ปโโคโ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ปโโคโ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ผโโคโ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ผโโคโ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ผโโคโ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ผโโคโ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฝโโคโ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฝโโคโ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฝโโคโ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ฝโโคโ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐พโโคโ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐พโโคโ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐พโโคโ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐พโโคโ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฟโโคโ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฟโโคโ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฟโโคโ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ฟโโคโ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ฉ๐ปโโคโ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ปโโคโ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ปโโคโ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ปโโคโ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ปโโคโ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ผโโคโ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ผโโคโ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ผโโคโ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ผโโคโ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ผโโคโ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฝโโคโ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฝโโคโ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฝโโคโ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฝโโคโ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฝโโคโ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐พโโคโ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐พโโคโ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐พโโคโ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐พโโคโ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐พโโคโ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฟโโคโ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฟโโคโ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฟโโคโ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฟโโคโ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฟโโคโ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ปโโคโ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ปโโคโ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ปโโคโ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ปโโคโ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ปโโคโ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ผโโคโ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ผโโคโ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ผโโคโ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ผโโคโ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ผโโคโ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฝโโคโ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฝโโคโ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฝโโคโ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฝโโคโ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฝโโคโ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐พโโคโ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐พโโคโ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐พโโคโ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐พโโคโ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐พโโคโ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฟโโคโ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฟโโคโ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฟโโคโ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฟโโคโ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฟโโคโ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ปโโคโ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ปโโคโ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ปโโคโ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ปโโคโ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ปโโคโ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ผโโคโ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ผโโคโ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ผโโคโ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ผโโคโ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ผโโคโ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฝโโคโ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฝโโคโ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฝโโคโ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฝโโคโ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฝโโคโ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐พโโคโ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐พโโคโ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐พโโคโ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐พโโคโ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐พโโคโ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฟโโคโ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฟโโคโ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฟโโคโ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฟโโคโ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฟโโคโ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","๐จโ๐ฉโ๐งโ๐ฆ":"1f468-200d-1f469-200d-1f467-200d-1f466","๐จโ๐ฉโ๐ฆโ๐ฆ":"1f468-200d-1f469-200d-1f466-200d-1f466","๐จโ๐ฉโ๐งโ๐ง":"1f468-200d-1f469-200d-1f467-200d-1f467","๐จโ๐จโ๐งโ๐ฆ":"1f468-200d-1f468-200d-1f467-200d-1f466","๐จโ๐จโ๐ฆโ๐ฆ":"1f468-200d-1f468-200d-1f466-200d-1f466","๐จโ๐จโ๐งโ๐ง":"1f468-200d-1f468-200d-1f467-200d-1f467","๐ฉโ๐ฉโ๐งโ๐ฆ":"1f469-200d-1f469-200d-1f467-200d-1f466","๐ฉโ๐ฉโ๐ฆโ๐ฆ":"1f469-200d-1f469-200d-1f466-200d-1f466","๐ฉโ๐ฉโ๐งโ๐ง":"1f469-200d-1f469-200d-1f467-200d-1f467","๐ด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","๐ด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","๐ด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","๐ฉโโค๏ธโ๐โ๐จ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","๐จโโค๏ธโ๐โ๐จ":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","๐ฉโโค๏ธโ๐โ๐ฉ":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","๐ง๐ปโโค๏ธโ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ปโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ปโโค๏ธโ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ปโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ผโโค๏ธโ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ผโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ผโโค๏ธโ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ผโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฝโโค๏ธโ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฝโโค๏ธโ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฝโโค๏ธโ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ง๐ฝโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐พโโค๏ธโ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐พโโค๏ธโ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐พโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐พโโค๏ธโ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","๐ง๐ฟโโค๏ธโ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","๐ง๐ฟโโค๏ธโ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","๐ง๐ฟโโค๏ธโ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","๐ง๐ฟโโค๏ธโ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","๐ฉ๐ปโโค๏ธโ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ปโโค๏ธโ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ปโโค๏ธโ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ปโโค๏ธโ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ปโโค๏ธโ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ผโโค๏ธโ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ผโโค๏ธโ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ผโโค๏ธโ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ผโโค๏ธโ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ผโโค๏ธโ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฝโโค๏ธโ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฝโโค๏ธโ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฝโโค๏ธโ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฝโโค๏ธโ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฝโโค๏ธโ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐พโโค๏ธโ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐พโโค๏ธโ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐พโโค๏ธโ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐พโโค๏ธโ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐พโโค๏ธโ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ฟโโค๏ธโ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐ฉ๐ฟโโค๏ธโ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐ฉ๐ฟโโค๏ธโ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐ฉ๐ฟโโค๏ธโ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐ฉ๐ฟโโค๏ธโ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ปโโค๏ธโ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ปโโค๏ธโ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ปโโค๏ธโ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ปโโค๏ธโ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ปโโค๏ธโ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ผโโค๏ธโ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ผโโค๏ธโ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ผโโค๏ธโ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ผโโค๏ธโ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ผโโค๏ธโ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฝโโค๏ธโ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฝโโค๏ธโ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฝโโค๏ธโ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฝโโค๏ธโ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฝโโค๏ธโ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐พโโค๏ธโ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐พโโค๏ธโ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐พโโค๏ธโ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐พโโค๏ธโ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐พโโค๏ธโ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","๐จ๐ฟโโค๏ธโ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","๐จ๐ฟโโค๏ธโ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","๐จ๐ฟโโค๏ธโ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","๐จ๐ฟโโค๏ธโ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","๐จ๐ฟโโค๏ธโ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","๐ฉ๐ปโโค๏ธโ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ปโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ปโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ปโโค๏ธโ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ปโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ผโโค๏ธโ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ผโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ผโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ผโโค๏ธโ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ผโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฝโโค๏ธโ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฝโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐พโโค๏ธโ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐พโโค๏ธโ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐พโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐พโโค๏ธโ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐พโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","๐ฉ๐ฟโโค๏ธโ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","๐ฉ๐ฟโโค๏ธโ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","๐ง๐ปโโคโ๐โ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ปโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ปโโคโ๐โ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ปโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ผโโคโ๐โ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ผโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ผโโคโ๐โ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ผโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฝโโคโ๐โ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฝโโคโ๐โ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฝโโคโ๐โ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ฝโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐พโโคโ๐โ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐พโโคโ๐โ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐พโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐พโโคโ๐โ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฟโโคโ๐โ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฟโโคโ๐โ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฟโโคโ๐โ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ฟโโคโ๐โ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ฉ๐ปโโคโ๐โ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ปโโคโ๐โ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ปโโคโ๐โ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ปโโคโ๐โ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ปโโคโ๐โ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ผโโคโ๐โ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ผโโคโ๐โ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ผโโคโ๐โ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ผโโคโ๐โ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ผโโคโ๐โ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฝโโคโ๐โ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฝโโคโ๐โ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฝโโคโ๐โ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฝโโคโ๐โ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฝโโคโ๐โ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐พโโคโ๐โ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐พโโคโ๐โ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐พโโคโ๐โ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐พโโคโ๐โ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐พโโคโ๐โ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฟโโคโ๐โ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฟโโคโ๐โ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฟโโคโ๐โ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฟโโคโ๐โ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฟโโคโ๐โ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ปโโคโ๐โ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ปโโคโ๐โ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ปโโคโ๐โ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ปโโคโ๐โ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ปโโคโ๐โ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ผโโคโ๐โ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ผโโคโ๐โ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ผโโคโ๐โ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ผโโคโ๐โ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ผโโคโ๐โ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฝโโคโ๐โ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฝโโคโ๐โ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฝโโคโ๐โ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฝโโคโ๐โ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฝโโคโ๐โ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐พโโคโ๐โ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐พโโคโ๐โ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐พโโคโ๐โ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐พโโคโ๐โ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐พโโคโ๐โ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฟโโคโ๐โ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฟโโคโ๐โ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฟโโคโ๐โ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฟโโคโ๐โ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฟโโคโ๐โ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ปโโคโ๐โ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ปโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ปโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ปโโคโ๐โ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ปโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ผโโคโ๐โ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ผโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ผโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ผโโคโ๐โ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ผโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฝโโคโ๐โ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฝโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐พโโคโ๐โ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐พโโคโ๐โ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐พโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐พโโคโ๐โ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐พโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฟโโคโ๐โ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฟโโคโ๐โ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ง๐ปโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ปโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ปโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ปโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ผโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ผโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ผโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ผโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฝโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฝโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฝโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ง๐ฝโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐พโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐พโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐พโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐พโโค๏ธโ๐โ๐ง๐ฟ":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","๐ง๐ฟโโค๏ธโ๐โ๐ง๐ป":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","๐ง๐ฟโโค๏ธโ๐โ๐ง๐ผ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","๐ง๐ฟโโค๏ธโ๐โ๐ง๐ฝ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","๐ง๐ฟโโค๏ธโ๐โ๐ง๐พ":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ปโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ผโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฝโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐พโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐พโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐ฉ๐ฟโโค๏ธโ๐โ๐จ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ปโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ปโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ปโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ปโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ปโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ผโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ผโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ผโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ผโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ผโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฝโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฝโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐พโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐พโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐พโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐พโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐พโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ป":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ผ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ฝ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","๐จ๐ฟโโค๏ธโ๐โ๐จ๐พ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","๐จ๐ฟโโค๏ธโ๐โ๐จ๐ฟ":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ปโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ผโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฝโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐พโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ป":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ผ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ฝ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐พ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","๐ฉ๐ฟโโค๏ธโ๐โ๐ฉ๐ฟ":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff"} \ No newline at end of file diff --git a/app/javascript/flavours/glitch/util/initial_state.js b/app/javascript/flavours/glitch/util/initial_state.js deleted file mode 100644 index 90dada4b3..000000000 --- a/app/javascript/flavours/glitch/util/initial_state.js +++ /dev/null @@ -1,41 +0,0 @@ -const element = document.getElementById('initial-state'); -const initialState = element && function () { - const result = JSON.parse(element.textContent); - try { - result.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); - } catch (e) { - result.local_settings = {}; - } - return result; -}(); - -const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop]; - -export const reduceMotion = getMeta('reduce_motion'); -export const autoPlayGif = getMeta('auto_play_gif'); -export const displayMedia = getMeta('display_media') || (getMeta('display_sensitive_media') ? 'show_all' : 'default'); -export const expandSpoilers = getMeta('expand_spoilers'); -export const unfollowModal = getMeta('unfollow_modal'); -export const boostModal = getMeta('boost_modal'); -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 pollLimits = (initialState && initialState.poll_limits); -export const limitedFederationMode = getMeta('limited_federation_mode'); -export const repository = getMeta('repository'); -export const source_url = getMeta('source_url'); -export const version = getMeta('version'); -export const mascot = getMeta('mascot'); -export const profile_directory = getMeta('profile_directory'); -export const defaultContentType = getMeta('default_content_type'); -export const forceSingleColumn = getMeta('advanced_layout') === false; -export const useBlurhash = getMeta('use_blurhash'); -export const usePendingItems = getMeta('use_pending_items'); -export const useSystemEmojiFont = getMeta('system_emoji_font'); -export const showTrends = getMeta('trends'); -export const disableSwiping = getMeta('disable_swiping'); -export const languages = initialState && initialState.languages; - -export default initialState; diff --git a/app/javascript/flavours/glitch/util/is_mobile.js b/app/javascript/flavours/glitch/util/is_mobile.js deleted file mode 100644 index 7e584e8fa..000000000 --- a/app/javascript/flavours/glitch/util/is_mobile.js +++ /dev/null @@ -1,35 +0,0 @@ -import { supportsPassiveEvents } from 'detect-passive-events'; -import { forceSingleColumn } from 'flavours/glitch/util/initial_state'; - -const LAYOUT_BREAKPOINT = 630; - -export function isMobile(width, columns) { - switch (columns) { - case 'multiple': - return false; - case 'single': - return true; - default: - return forceSingleColumn || width <= LAYOUT_BREAKPOINT; - } -}; - -const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; - -let userTouching = false; -let listenerOptions = supportsPassiveEvents ? { passive: true } : false; - -function touchListener() { - userTouching = true; - window.removeEventListener('touchstart', touchListener, listenerOptions); -} - -window.addEventListener('touchstart', touchListener, listenerOptions); - -export function isUserTouching() { - return userTouching; -} - -export function isIOS() { - return iOS; -}; diff --git a/app/javascript/flavours/glitch/util/main.js b/app/javascript/flavours/glitch/util/main.js deleted file mode 100644 index 6577b70c2..000000000 --- a/app/javascript/flavours/glitch/util/main.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as registerPushNotifications from 'flavours/glitch/actions/push_notifications'; -import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications'; -import { default as Mastodon, store } from 'flavours/glitch/containers/mastodon'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import ready from './ready'; - -const perf = require('./performance'); - -function main() { - perf.start('main()'); - - if (window.history && history.replaceState) { - const { pathname, search, hash } = window.location; - const path = pathname + search + hash; - if (!(/^\/web($|\/)/).test(path)) { - history.replaceState(null, document.title, `/web${path}`); - } - } - - ready(() => { - const mountNode = document.getElementById('mastodon'); - const props = JSON.parse(mountNode.getAttribute('data-props')); - - ReactDOM.render(<Mastodon {...props} />, mountNode); - store.dispatch(setupBrowserNotifications()); - if (process.env.NODE_ENV === 'production') { - // avoid offline in dev mode because it's harder to debug - require('offline-plugin/runtime').install(); - store.dispatch(registerPushNotifications.register()); - } - perf.stop('main()'); - }); -} - -export default main; diff --git a/app/javascript/flavours/glitch/util/ready.js b/app/javascript/flavours/glitch/util/ready.js deleted file mode 100644 index dd543910b..000000000 --- a/app/javascript/flavours/glitch/util/ready.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function ready(loaded) { - if (['interactive', 'complete'].includes(document.readyState)) { - loaded(); - } else { - document.addEventListener('DOMContentLoaded', loaded); - } -} diff --git a/app/javascript/flavours/glitch/util/redux_helpers.js b/app/javascript/flavours/glitch/util/redux_helpers.js deleted file mode 100644 index 8eb338da7..000000000 --- a/app/javascript/flavours/glitch/util/redux_helpers.js +++ /dev/null @@ -1,8 +0,0 @@ -import { injectIntl } from 'react-intl'; -import { connect } from 'react-redux'; - -// Connects a component. -export function wrap (Component, mapStateToProps, mapDispatchToProps, options) { - const withIntl = typeof options === 'object' ? options.withIntl : !!options; - return (withIntl ? injectIntl : i => i)(connect(mapStateToProps, mapDispatchToProps)(Component)); -} diff --git a/app/javascript/flavours/glitch/util/backend_links.js b/app/javascript/flavours/glitch/utils/backend_links.js index 5b2dd8dbf..2028a1e60 100644 --- a/app/javascript/flavours/glitch/util/backend_links.js +++ b/app/javascript/flavours/glitch/utils/backend_links.js @@ -1,9 +1,9 @@ export const preferencesLink = '/settings/preferences'; export const profileLink = '/settings/profile'; export const signOutLink = '/auth/sign_out'; -export const termsLink = '/terms'; +export const privacyPolicyLink = '/privacy-policy'; export const accountAdminLink = (id) => `/admin/accounts/${id}`; -export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses?id=${status_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'; diff --git a/app/javascript/flavours/glitch/util/base64.js b/app/javascript/flavours/glitch/utils/base64.js index 8226e2c54..8226e2c54 100644 --- a/app/javascript/flavours/glitch/util/base64.js +++ b/app/javascript/flavours/glitch/utils/base64.js diff --git a/app/javascript/flavours/glitch/util/config.js b/app/javascript/flavours/glitch/utils/config.js index c3e2b73ae..932cd0cbf 100644 --- a/app/javascript/flavours/glitch/util/config.js +++ b/app/javascript/flavours/glitch/utils/config.js @@ -1,4 +1,4 @@ -import ready from './ready'; +import ready from '../ready'; export let assetHost = ''; diff --git a/app/javascript/flavours/glitch/util/content_warning.js b/app/javascript/flavours/glitch/utils/content_warning.js index 383a34226..91d452baa 100644 --- a/app/javascript/flavours/glitch/util/content_warning.js +++ b/app/javascript/flavours/glitch/utils/content_warning.js @@ -1,4 +1,4 @@ -import { expandSpoilers } from 'flavours/glitch/util/initial_state'; +import { expandSpoilers } from 'flavours/glitch/initial_state'; function _autoUnfoldCW(spoiler_text, skip_unfold_regex) { if (!expandSpoilers) diff --git a/app/javascript/flavours/glitch/util/dom_helpers.js b/app/javascript/flavours/glitch/utils/dom_helpers.js index d94aeb9d4..d94aeb9d4 100644 --- a/app/javascript/flavours/glitch/util/dom_helpers.js +++ b/app/javascript/flavours/glitch/utils/dom_helpers.js diff --git a/app/javascript/flavours/glitch/util/filters.js b/app/javascript/flavours/glitch/utils/filters.js index 97b433a99..97b433a99 100644 --- a/app/javascript/flavours/glitch/util/filters.js +++ b/app/javascript/flavours/glitch/utils/filters.js diff --git a/app/javascript/flavours/glitch/util/hashtag.js b/app/javascript/flavours/glitch/utils/hashtag.js index 9b663487f..9b663487f 100644 --- a/app/javascript/flavours/glitch/util/hashtag.js +++ b/app/javascript/flavours/glitch/utils/hashtag.js diff --git a/app/javascript/flavours/glitch/util/html.js b/app/javascript/flavours/glitch/utils/html.js index 5159df9db..5159df9db 100644 --- a/app/javascript/flavours/glitch/util/html.js +++ b/app/javascript/flavours/glitch/utils/html.js diff --git a/app/javascript/flavours/glitch/util/icons.js b/app/javascript/flavours/glitch/utils/icons.js index be566032e..c3e362e39 100644 --- a/app/javascript/flavours/glitch/util/icons.js +++ b/app/javascript/flavours/glitch/utils/icons.js @@ -1,3 +1,5 @@ +import React from 'react'; + // Copied from emoji-mart for consistency with emoji picker and since // they don't export the icons in the package export const loupeIcon = ( diff --git a/app/javascript/flavours/glitch/util/idna.js b/app/javascript/flavours/glitch/utils/idna.js index efab5bacf..efab5bacf 100644 --- a/app/javascript/flavours/glitch/util/idna.js +++ b/app/javascript/flavours/glitch/utils/idna.js diff --git a/app/javascript/flavours/glitch/util/js_helpers.js b/app/javascript/flavours/glitch/utils/js_helpers.js index 2ebd5b6c5..2ebd5b6c5 100644 --- a/app/javascript/flavours/glitch/util/js_helpers.js +++ b/app/javascript/flavours/glitch/utils/js_helpers.js diff --git a/app/javascript/flavours/glitch/util/log_out.js b/app/javascript/flavours/glitch/utils/log_out.js index 42dcee03e..f82041150 100644 --- a/app/javascript/flavours/glitch/util/log_out.js +++ b/app/javascript/flavours/glitch/utils/log_out.js @@ -1,5 +1,5 @@ import Rails from '@rails/ujs'; -import { signOutLink } from 'flavours/glitch/util/backend_links'; +import { signOutLink } from 'flavours/glitch/utils/backend_links'; export const logOut = () => { const form = document.createElement('form'); diff --git a/app/javascript/flavours/glitch/util/notifications.js b/app/javascript/flavours/glitch/utils/notifications.js index 7634cac21..7634cac21 100644 --- a/app/javascript/flavours/glitch/util/notifications.js +++ b/app/javascript/flavours/glitch/utils/notifications.js diff --git a/app/javascript/flavours/glitch/util/numbers.js b/app/javascript/flavours/glitch/utils/numbers.js index 6ef563ad8..6ef563ad8 100644 --- a/app/javascript/flavours/glitch/util/numbers.js +++ b/app/javascript/flavours/glitch/utils/numbers.js diff --git a/app/javascript/flavours/glitch/util/privacy_preference.js b/app/javascript/flavours/glitch/utils/privacy_preference.js index 7781ca7fa..7781ca7fa 100644 --- a/app/javascript/flavours/glitch/util/privacy_preference.js +++ b/app/javascript/flavours/glitch/utils/privacy_preference.js diff --git a/app/javascript/flavours/glitch/util/react_helpers.js b/app/javascript/flavours/glitch/utils/react_helpers.js index 082a58e62..082a58e62 100644 --- a/app/javascript/flavours/glitch/util/react_helpers.js +++ b/app/javascript/flavours/glitch/utils/react_helpers.js diff --git a/app/javascript/flavours/glitch/util/resize_image.js b/app/javascript/flavours/glitch/utils/resize_image.js index fb8c3c11e..fb8c3c11e 100644 --- a/app/javascript/flavours/glitch/util/resize_image.js +++ b/app/javascript/flavours/glitch/utils/resize_image.js diff --git a/app/javascript/flavours/glitch/util/scrollbar.js b/app/javascript/flavours/glitch/utils/scrollbar.js index 929b036d6..929b036d6 100644 --- a/app/javascript/flavours/glitch/util/scrollbar.js +++ b/app/javascript/flavours/glitch/utils/scrollbar.js diff --git a/app/javascript/flavours/glitch/util/uuid.js b/app/javascript/flavours/glitch/uuid.js index be1899305..be1899305 100644 --- a/app/javascript/flavours/glitch/util/uuid.js +++ b/app/javascript/flavours/glitch/uuid.js diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml index 9173d4ec9..5cb76b721 100644 --- a/app/javascript/flavours/vanilla/theme.yml +++ b/app/javascript/flavours/vanilla/theme.yml @@ -1,6 +1,5 @@ # (REQUIRED) The location of the pack files inside `pack_directory`. pack: - about: about.js admin: - admin.js - public.js |