--- /dev/null
+import api from 'flavours/glitch/util/api';
+import { importFetchedAccounts } from './importer';
+import { fetchRelationships } from './accounts';
+
+export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST';
+export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS';
+export const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL';
+
+export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
+export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
+export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL';
+
+export const fetchDirectory = params => (dispatch, getState) => {
+ dispatch(fetchDirectoryRequest());
+
+ api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
+ dispatch(importFetchedAccounts(data));
+ dispatch(fetchDirectorySuccess(data));
+ dispatch(fetchRelationships(data.map(x => x.id)));
+ }).catch(error => dispatch(fetchDirectoryFail(error)));
+};
+
+export const fetchDirectoryRequest = () => ({
+ type: DIRECTORY_FETCH_REQUEST,
+});
+
+export const fetchDirectorySuccess = accounts => ({
+ type: DIRECTORY_FETCH_SUCCESS,
+ accounts,
+});
+
+export const fetchDirectoryFail = error => ({
+ type: DIRECTORY_FETCH_FAIL,
+ error,
+});
+
+export const expandDirectory = params => (dispatch, getState) => {
+ dispatch(expandDirectoryRequest());
+
+ const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size;
+
+ api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
+ dispatch(importFetchedAccounts(data));
+ dispatch(expandDirectorySuccess(data));
+ dispatch(fetchRelationships(data.map(x => x.id)));
+ }).catch(error => dispatch(expandDirectoryFail(error)));
+};
+
+export const expandDirectoryRequest = () => ({
+ type: DIRECTORY_EXPAND_REQUEST,
+});
+
+export const expandDirectorySuccess = accounts => ({
+ type: DIRECTORY_EXPAND_SUCCESS,
+ accounts,
+});
+
+export const expandDirectoryFail = error => ({
+ type: DIRECTORY_EXPAND_FAIL,
+ error,
+});
--- /dev/null
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+
+export default class RadioButton extends React.PureComponent {
+
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ checked: PropTypes.bool,
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ label: PropTypes.node.isRequired,
+ };
+
+ render () {
+ const { name, value, checked, onChange, label } = this.props;
+
+ return (
+ <label className='radio-button'>
+ <input
+ name={name}
+ type='radio'
+ value={value}
+ checked={checked}
+ onChange={onChange}
+ />
+
+ <span className={classNames('radio-button__input', { checked })} />
+
+ <span>{label}</span>
+ </label>
+ );
+ }
+
+}
--- /dev/null
+import React from 'react';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+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 RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
+import IconButton from 'flavours/glitch/components/icon_button';
+import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
+import { autoPlayGif, me, unfollowModal } from 'flavours/glitch/util/initial_state';
+import { shortNumberFormat } from 'flavours/glitch/util/numbers';
+import { followAccount, unfollowAccount, blockAccount, unblockAccount, unmuteAccount } from 'flavours/glitch/actions/accounts';
+import { openModal } from 'flavours/glitch/actions/modal';
+import { initMuteModal } from 'flavours/glitch/actions/mutes';
+
+const messages = defineMessages({
+ follow: { id: 'account.follow', defaultMessage: 'Follow' },
+ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
+ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+ unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+});
+
+const makeMapStateToProps = () => {
+ const getAccount = makeGetAccount();
+
+ const mapStateToProps = (state, { id }) => ({
+ account: getAccount(state, id),
+ });
+
+ return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch, { intl }) => ({
+
+ onFollow (account) {
+ if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
+ 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> }} />,
+ confirm: intl.formatMessage(messages.unfollowConfirm),
+ onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
+ }));
+ } else {
+ dispatch(unfollowAccount(account.get('id')));
+ }
+ } else {
+ dispatch(followAccount(account.get('id')));
+ }
+ },
+
+ onBlock (account) {
+ if (account.getIn(['relationship', 'blocking'])) {
+ dispatch(unblockAccount(account.get('id')));
+ } else {
+ dispatch(blockAccount(account.get('id')));
+ }
+ },
+
+ onMute (account) {
+ if (account.getIn(['relationship', 'muting'])) {
+ dispatch(unmuteAccount(account.get('id')));
+ } else {
+ dispatch(initMuteModal(account));
+ }
+ },
+
+});
+
+export default @injectIntl
+@connect(makeMapStateToProps, mapDispatchToProps)
+class AccountCard extends ImmutablePureComponent {
+
+ static propTypes = {
+ account: ImmutablePropTypes.map.isRequired,
+ intl: PropTypes.object.isRequired,
+ onFollow: PropTypes.func.isRequired,
+ onBlock: PropTypes.func.isRequired,
+ onMute: PropTypes.func.isRequired,
+ };
+
+ handleFollow = () => {
+ this.props.onFollow(this.props.account);
+ }
+
+ handleBlock = () => {
+ this.props.onBlock(this.props.account);
+ }
+
+ handleMute = () => {
+ this.props.onMute(this.props.account);
+ }
+
+ render () {
+ const { account, intl } = this.props;
+
+ let buttons;
+
+ if (account.get('id') !== me && account.get('relationship', null) !== null) {
+ const following = account.getIn(['relationship', 'following']);
+ const requested = account.getIn(['relationship', 'requested']);
+ const blocking = account.getIn(['relationship', 'blocking']);
+ const muting = account.getIn(['relationship', 'muting']);
+
+ if (requested) {
+ buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
+ } else if (blocking) {
+ buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
+ } else if (muting) {
+ buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
+ } else if (!account.get('moved') || following) {
+ buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
+ }
+ }
+
+ return (
+ <div className='directory__card'>
+ <div className='directory__card__img'>
+ <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />
+ </div>
+
+ <div className='directory__card__bar'>
+ <Permalink className='directory__card__bar__name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
+ <Avatar account={account} size={48} />
+ <DisplayName account={account} />
+ </Permalink>
+
+ <div className='directory__card__bar__relationship account__relationship'>
+ {buttons}
+ </div>
+ </div>
+
+ <div className='directory__card__extra'>
+ {account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content' dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} />}
+ </div>
+
+ <div className='directory__card__extra'>
+ <div className='accounts-table__count'>{shortNumberFormat(account.get('statuses_count'))} <small><FormattedMessage id='account.posts' defaultMessage='Toots' /></small></div>
+ <div className='accounts-table__count'>{shortNumberFormat(account.get('followers_count'))} <small><FormattedMessage id='account.followers' defaultMessage='Followers' /></small></div>
+ <div className='accounts-table__count'>{account.get('last_status_at') === null ? <FormattedMessage id='account.never_active' defaultMessage='Never' /> : <RelativeTimestamp timestamp={account.get('last_status_at')} />} <small><FormattedMessage id='account.last_status' defaultMessage='Last active' /></small></div>
+ </div>
+ </div>
+ );
+ }
+
+}
--- /dev/null
+import React from 'react';
+import { connect } from 'react-redux';
+import { defineMessages, injectIntl } from 'react-intl';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import Column from 'flavours/glitch/components/column';
+import ColumnHeader from 'flavours/glitch/components/column_header';
+import { addColumn, removeColumn, moveColumn, changeColumnParams } from 'flavours/glitch/actions/columns';
+import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directory';
+import { List as ImmutableList } from 'immutable';
+import AccountCard from './components/account_card';
+import RadioButton from 'flavours/glitch/components/radio_button';
+import classNames from 'classnames';
+import LoadMore from 'flavours/glitch/components/load_more';
+import { ScrollContainer } from 'react-router-scroll-4';
+
+const messages = defineMessages({
+ title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
+ recentlyActive: { id: 'directory.recently_active', defaultMessage: 'Recently active' },
+ newArrivals: { id: 'directory.new_arrivals', defaultMessage: 'New arrivals' },
+ local: { id: 'directory.local', defaultMessage: 'From {domain} only' },
+ federated: { id: 'directory.federated', defaultMessage: 'From known fediverse' },
+});
+
+const mapStateToProps = state => ({
+ accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()),
+ isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true),
+ domain: state.getIn(['meta', 'domain']),
+});
+
+export default @connect(mapStateToProps)
+@injectIntl
+class Directory extends React.PureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ isLoading: PropTypes.bool,
+ accountIds: ImmutablePropTypes.list.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ shouldUpdateScroll: PropTypes.func,
+ columnId: PropTypes.string,
+ intl: PropTypes.object.isRequired,
+ multiColumn: PropTypes.bool,
+ domain: PropTypes.string.isRequired,
+ params: PropTypes.shape({
+ order: PropTypes.string,
+ local: PropTypes.bool,
+ }),
+ };
+
+ state = {
+ order: null,
+ local: null,
+ };
+
+ handlePin = () => {
+ const { columnId, dispatch } = this.props;
+
+ if (columnId) {
+ dispatch(removeColumn(columnId));
+ } else {
+ dispatch(addColumn('DIRECTORY', this.getParams(this.props, this.state)));
+ }
+ }
+
+ getParams = (props, state) => ({
+ order: state.order === null ? (props.params.order || 'active') : state.order,
+ local: state.local === null ? (props.params.local || false) : state.local,
+ });
+
+ handleMove = dir => {
+ const { columnId, dispatch } = this.props;
+ dispatch(moveColumn(columnId, dir));
+ }
+
+ handleHeaderClick = () => {
+ this.column.scrollTop();
+ }
+
+ componentDidMount () {
+ const { dispatch } = this.props;
+ dispatch(fetchDirectory(this.getParams(this.props, this.state)));
+ }
+
+ componentDidUpdate (prevProps, prevState) {
+ const { dispatch } = this.props;
+ const paramsOld = this.getParams(prevProps, prevState);
+ const paramsNew = this.getParams(this.props, this.state);
+
+ if (paramsOld.order !== paramsNew.order || paramsOld.local !== paramsNew.local) {
+ dispatch(fetchDirectory(paramsNew));
+ }
+ }
+
+ setRef = c => {
+ this.column = c;
+ }
+
+ handleChangeOrder = e => {
+ const { dispatch, columnId } = this.props;
+
+ if (columnId) {
+ dispatch(changeColumnParams(columnId, ['order'], e.target.value));
+ } else {
+ this.setState({ order: e.target.value });
+ }
+ }
+
+ handleChangeLocal = e => {
+ const { dispatch, columnId } = this.props;
+
+ if (columnId) {
+ dispatch(changeColumnParams(columnId, ['local'], e.target.value === '1'));
+ } else {
+ this.setState({ local: e.target.value === '1' });
+ }
+ }
+
+ handleLoadMore = () => {
+ const { dispatch } = this.props;
+ dispatch(expandDirectory(this.getParams(this.props, this.state)));
+ }
+
+ render () {
+ const { isLoading, accountIds, intl, columnId, multiColumn, domain, shouldUpdateScroll } = this.props;
+ const { order, local } = this.getParams(this.props, this.state);
+ const pinned = !!columnId;
+
+ const scrollableArea = (
+ <div className='scrollable' style={{ background: 'transparent' }}>
+ <div className='filter-form'>
+ <div className='filter-form__column' role='group'>
+ <RadioButton name='order' value='active' label={intl.formatMessage(messages.recentlyActive)} checked={order === 'active'} onChange={this.handleChangeOrder} />
+ <RadioButton name='order' value='new' label={intl.formatMessage(messages.newArrivals)} checked={order === 'new'} onChange={this.handleChangeOrder} />
+ </div>
+
+ <div className='filter-form__column' role='group'>
+ <RadioButton name='local' value='1' label={intl.formatMessage(messages.local, { domain })} checked={local} onChange={this.handleChangeLocal} />
+ <RadioButton name='local' value='0' label={intl.formatMessage(messages.federated)} checked={!local} onChange={this.handleChangeLocal} />
+ </div>
+ </div>
+
+ <div className={classNames('directory__list', { loading: isLoading })}>
+ {accountIds.map(accountId => <AccountCard id={accountId} key={accountId} />)}
+ </div>
+
+ <LoadMore onClick={this.handleLoadMore} visible={!isLoading} />
+ </div>
+ );
+
+ return (
+ <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
+ <ColumnHeader
+ icon='address-book-o'
+ title={intl.formatMessage(messages.title)}
+ onPin={this.handlePin}
+ onMove={this.handleMove}
+ onClick={this.handleHeaderClick}
+ pinned={pinned}
+ multiColumn={multiColumn}
+ />
+
+ {multiColumn && !pinned ? <ScrollContainer scrollKey='directory' shouldUpdateScroll={shouldUpdateScroll}>{scrollableArea}</ScrollContainer> : scrollableArea}
+ </Column>
+ );
+ }
+
+}
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
-import { me } from 'flavours/glitch/util/initial_state';
+import { me, profile_directory } from 'flavours/glitch/util/initial_state';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
import { List as ImmutableList } from 'immutable';
import { createSelector } from 'reselect';
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 = () => {
navItems.push(<ColumnLink key='6' icon='user-plus' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />);
}
- navItems.push(<ColumnLink key='7' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
+ if (profile_directory) {
+ navItems.push(<ColumnLink key='7' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />);
+ }
+
+ navItems.push(<ColumnLink key='8' icon='ellipsis-h' text={intl.formatMessage(messages.misc)} to='/getting-started-misc' />);
listItems = listItems.concat([
- <div key='8'>
- <ColumnLink key='9' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
+ <div key='9'>
+ <ColumnLink key='10' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
{lists.map(list =>
- <ColumnLink key={(9 + Number(list.get('id'))).toString()} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
+ <ColumnLink key={(11 + Number(list.get('id'))).toString()} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
)}
</div>,
]);
},
onLoad (value) {
- return api().get('/api/v2/search', { params: { q: value } }).then(response => {
+ return api().get('/api/v2/search', { params: { q: value, type: 'hashtags' } }).then(response => {
return (response.data.hashtags || []).map((tag) => {
return { value: tag.name, label: `#${tag.name}` };
});
import ColumnLoading from './column_loading';
import DrawerLoading from './drawer_loading';
import BundleColumnError from './bundle_column_error';
-import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses, BookmarkedStatuses, ListTimeline } from 'flavours/glitch/util/async-components';
+import {
+ Compose,
+ Notifications,
+ HomeTimeline,
+ CommunityTimeline,
+ PublicTimeline,
+ HashtagTimeline,
+ DirectTimeline,
+ FavouritedStatuses,
+ BookmarkedStatuses,
+ ListTimeline,
+ Directory,
+} from 'flavours/glitch/util/async-components';
import ComposePanel from './compose_panel';
import NavigationPanel from './navigation_panel';
'FAVOURITES': FavouritedStatuses,
'BOOKMARKS': BookmarkedStatuses,
'LIST': ListTimeline,
+ 'DIRECTORY': Directory,
};
const shouldHideFAB = path => path.match(/^\/statuses\/|^\/search|^\/getting-started/);
<NavLink className='column-link column-link--transparent' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' icon='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/timelines/direct'><Icon className='column-link__icon' icon='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' icon='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' icon='address-book-o' fixedWidth /><FormattedMessage id='getting_started.profile_directory' defaultMessage='Profile directory' /></NavLink>}
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' icon='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
<ListPanel />
<a className='column-link column-link--transparent' href='/settings/preferences' target='_blank'><Icon className='column-link__icon' icon='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' icon='cogs' fixedWidth /><FormattedMessage id='navigation_bar.app_settings' defaultMessage='App settings' /></a>
<a className='column-link column-link--transparent' href='/relationships' target='_blank'><Icon className='column-link__icon' icon='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
- {!!profile_directory && <a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>}
</div>
);
Lists,
Search,
GettingStartedMisc,
+ Directory,
} from 'flavours/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
import { me } from 'flavours/glitch/util/initial_state';
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/search' component={Search} content={children} />
+ <WrappedRoute path='/directory' component={Directory} content={children} componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS,
} from 'flavours/glitch/actions/mutes';
+import {
+ DIRECTORY_FETCH_REQUEST,
+ DIRECTORY_FETCH_SUCCESS,
+ DIRECTORY_FETCH_FAIL,
+ DIRECTORY_EXPAND_REQUEST,
+ DIRECTORY_EXPAND_SUCCESS,
+ DIRECTORY_EXPAND_FAIL,
+} from 'flavours/glitch/actions/directory';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
return state.setIn(['mutes', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
case MUTES_EXPAND_SUCCESS:
return state.updateIn(['mutes', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
+ case DIRECTORY_FETCH_SUCCESS:
+ return state.setIn(['directory', 'items'], ImmutableList(action.accounts.map(item => item.id))).setIn(['directory', 'isLoading'], false);
+ case DIRECTORY_EXPAND_SUCCESS:
+ return state.updateIn(['directory', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['directory', 'isLoading'], false);
+ case DIRECTORY_FETCH_REQUEST:
+ case DIRECTORY_EXPAND_REQUEST:
+ return state.setIn(['directory', 'isLoading'], true);
+ case DIRECTORY_FETCH_FAIL:
+ case DIRECTORY_EXPAND_FAIL:
+ return state.setIn(['directory', 'isLoading'], false);
default:
return state;
}
}
}
}
+
+ &.directory__section-headline {
+ background: darken($ui-base-color, 2%);
+ border-bottom-color: transparent;
+
+ a,
+ button {
+ &.active {
+ &::before {
+ display: none;
+ }
+
+ &::after {
+ border-color: transparent transparent darken($ui-base-color, 7%);
+ }
+ }
+ }
+ }
}
.account__moved-note {
--- /dev/null
+.directory {
+ &__list {
+ width: 100%;
+ margin: 10px 0;
+ transition: opacity 100ms ease-in;
+
+ &.loading {
+ opacity: 0.7;
+ }
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ margin: 0;
+ }
+ }
+
+ &__card {
+ box-sizing: border-box;
+ margin-bottom: 10px;
+
+ &__img {
+ height: 125px;
+ position: relative;
+ background: darken($ui-base-color, 12%);
+ overflow: hidden;
+
+ img {
+ display: block;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ object-fit: cover;
+ }
+ }
+
+ &__bar {
+ display: flex;
+ align-items: center;
+ background: lighten($ui-base-color, 4%);
+ padding: 10px;
+
+ &__name {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+ text-decoration: none;
+ overflow: hidden;
+ }
+
+ &__relationship {
+ width: 23px;
+ min-height: 1px;
+ flex: 0 0 auto;
+ }
+
+ .avatar {
+ flex: 0 0 auto;
+ width: 48px;
+ height: 48px;
+ padding-top: 2px;
+
+ img {
+ width: 100%;
+ height: 100%;
+ display: block;
+ margin: 0;
+ border-radius: 4px;
+ background: darken($ui-base-color, 8%);
+ object-fit: cover;
+ }
+ }
+
+ .display-name {
+ margin-left: 15px;
+ text-align: left;
+
+ strong {
+ font-size: 15px;
+ color: $primary-text-color;
+ font-weight: 500;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ span {
+ display: block;
+ font-size: 14px;
+ color: $darker-text-color;
+ font-weight: 400;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+
+ &__extra {
+ background: $ui-base-color;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .accounts-table__count {
+ width: 33.33%;
+ flex: 0 0 auto;
+ padding: 15px 0;
+ }
+
+ .account__header__content {
+ box-sizing: border-box;
+ padding: 15px 10px;
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+ width: 100%;
+ min-height: 18px + 30px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ p {
+ display: none;
+
+ &:first-child {
+ display: inline;
+ }
+ }
+
+ br {
+ display: none;
+ }
+ }
+ }
+ }
+}
+
+.filter-form {
+ background: $ui-base-color;
+
+ &__column {
+ padding: 10px 15px;
+ }
+
+ .radio-button {
+ display: block;
+ }
+}
+
+.radio-button {
+ font-size: 14px;
+ position: relative;
+ display: inline-block;
+ padding: 6px 0;
+ line-height: 18px;
+ cursor: default;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ cursor: pointer;
+
+ input[type=radio],
+ input[type=checkbox] {
+ display: none;
+ }
+
+ &__input {
+ display: inline-block;
+ position: relative;
+ border: 1px solid $ui-primary-color;
+ box-sizing: border-box;
+ width: 18px;
+ height: 18px;
+ flex: 0 0 auto;
+ margin-right: 10px;
+ top: -1px;
+ border-radius: 50%;
+ vertical-align: middle;
+
+ &.checked {
+ border-color: lighten($ui-highlight-color, 8%);
+ background: lighten($ui-highlight-color, 8%);
+ }
+ }
+}
@import 'composer';
@import 'columns';
@import 'regeneration_indicator';
+@import 'directory';
@import 'search';
@import 'emoji';
@import 'doodle';
}
}
-&.detailed,
-&.fullscreen {
- .video-player__buttons {
- button {
- padding-top: 10px;
- padding-bottom: 10px;
+ &.detailed,
+ &.fullscreen {
+ .video-player__buttons {
+ button {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
}
}
}
-}
-
-.media-spoiler-video {
- background-size: cover;
- background-repeat: no-repeat;
- background-position: center;
- cursor: pointer;
- margin-top: 8px;
- position: relative;
-
- @include fullwidth-gallery;
-
- border: 0;
- display: block;
-}
-
-.media-spoiler-video-play-icon {
- border-radius: 100px;
- color: rgba($primary-text-color, 0.8);
- font-size: 36px;
- left: 50%;
- padding: 5px;
- position: absolute;
- top: 50%;
- transform: translate(-50%, -50%);
-}
padding: 0;
}
+ .directory__list {
+ display: grid;
+ grid-gap: 10px;
+ grid-template-columns: minmax(0, 50%) minmax(0, 50%);
+
+ @media screen and (max-width: $no-gap-breakpoint) {
+ display: block;
+ }
+ }
+
+ .directory__card {
+ margin-bottom: 0;
+ }
+
+ .filter-form {
+ display: flex;
+ }
+
.autosuggest-textarea__textarea {
font-size: 16px;
}
background-position: center center;
}
-.status__video-player {
- display: flex;
- align-items: center;
- background: $base-shadow-color;
- box-sizing: border-box;
- cursor: default; /* May not be needed */
- margin-top: 8px;
- overflow: hidden;
- position: relative;
-
- @include fullwidth-gallery;
-}
-
-.status__video-player-video {
- height: 100%;
- object-fit: contain;
- position: relative;
- top: 50%;
- transform: translateY(-50%);
- width: 100%;
- z-index: 1;
-
- &:not(.letterbox) {
- height: 100%;
- object-fit: cover;
- }
-}
-
-.status__video-player-expand,
-.status__video-player-mute {
- color: $primary-text-color;
- opacity: 0.8;
- position: absolute;
- right: 4px;
- text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
-}
-
-.status__video-player-spoiler {
- display: none;
- color: $primary-text-color;
- left: 4px;
- position: absolute;
- text-shadow: 0 1px 1px $base-shadow-color, 1px 0 1px $base-shadow-color;
- top: 4px;
- z-index: 100;
-
- &.status__video-player-spoiler--visible {
- display: block;
- }
-}
-
-.status__video-player-expand {
- bottom: 4px;
- z-index: 100;
-}
-
-.status__video-player-mute {
- top: 4px;
- z-index: 5;
-}
-
.attachment-list {
display: flex;
font-size: 14px;
export function Tesseract () {
return import(/*webpackChunkName: "tesseract" */'tesseract.js');
}
+
+export function Directory () {
+ return import(/* webpackChunkName: "features/glitch/async/directory" */'flavours/glitch/features/directory');
+}
export const invitesEnabled = getMeta('invites_enabled');
export const version = getMeta('version');
export const mascot = getMeta('mascot');
+export const profile_directory = getMeta('profile_directory');
export const isStaff = getMeta('is_staff');
export const defaultContentType = getMeta('default_content_type');
export const forceSingleColumn = getMeta('advanced_layout') === false;