Infer quote_id from links in status content
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
1d177831fe
commit
9668846ff0
6 changed files with 75 additions and 8 deletions
|
@ -90,6 +90,8 @@ const COMPOSE_EDITOR_STATE_SET = 'COMPOSE_EDITOR_STATE_SET' as const;
|
||||||
|
|
||||||
const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER' as const;
|
const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER' as const;
|
||||||
|
|
||||||
|
const COMPOSE_ADD_SUGGESTED_QUOTE = 'COMPOSE_ADD_SUGGESTED_QUOTE' as const;
|
||||||
|
|
||||||
const getAccount = makeGetAccount();
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -210,9 +212,9 @@ const quoteCompose = (status: Status) =>
|
||||||
dispatch(openModal('COMPOSE'));
|
dispatch(openModal('COMPOSE'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelQuoteCompose = () => ({
|
const cancelQuoteCompose = (composeId: string) => ({
|
||||||
type: COMPOSE_QUOTE_CANCEL,
|
type: COMPOSE_QUOTE_CANCEL,
|
||||||
id: 'compose-modal',
|
id: composeId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupComposeModal = (group: Group) =>
|
const groupComposeModal = (group: Group) =>
|
||||||
|
@ -868,6 +870,12 @@ const changeMediaOrder = (composeId: string, a: string, b: string) => ({
|
||||||
b,
|
b,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addSuggestedQuote = (composeId: string, quoteId: string) => ({
|
||||||
|
type: COMPOSE_ADD_SUGGESTED_QUOTE,
|
||||||
|
id: composeId,
|
||||||
|
quoteId: quoteId,
|
||||||
|
});
|
||||||
|
|
||||||
type ComposeAction =
|
type ComposeAction =
|
||||||
ComposeSetStatusAction
|
ComposeSetStatusAction
|
||||||
| ReturnType<typeof changeCompose>
|
| ReturnType<typeof changeCompose>
|
||||||
|
@ -914,6 +922,7 @@ type ComposeAction =
|
||||||
| ComposeEventReplyAction
|
| ComposeEventReplyAction
|
||||||
| ReturnType<typeof setEditorState>
|
| ReturnType<typeof setEditorState>
|
||||||
| ReturnType<typeof changeMediaOrder>
|
| ReturnType<typeof changeMediaOrder>
|
||||||
|
| ReturnType<typeof addSuggestedQuote>
|
||||||
|
|
||||||
export {
|
export {
|
||||||
COMPOSE_CHANGE,
|
COMPOSE_CHANGE,
|
||||||
|
@ -962,6 +971,7 @@ export {
|
||||||
COMPOSE_SET_STATUS,
|
COMPOSE_SET_STATUS,
|
||||||
COMPOSE_EDITOR_STATE_SET,
|
COMPOSE_EDITOR_STATE_SET,
|
||||||
COMPOSE_CHANGE_MEDIA_ORDER,
|
COMPOSE_CHANGE_MEDIA_ORDER,
|
||||||
|
COMPOSE_ADD_SUGGESTED_QUOTE,
|
||||||
setComposeToStatus,
|
setComposeToStatus,
|
||||||
changeCompose,
|
changeCompose,
|
||||||
replyCompose,
|
replyCompose,
|
||||||
|
@ -1017,5 +1027,6 @@ export {
|
||||||
eventDiscussionCompose,
|
eventDiscussionCompose,
|
||||||
setEditorState,
|
setEditorState,
|
||||||
changeMediaOrder,
|
changeMediaOrder,
|
||||||
|
addSuggestedQuote,
|
||||||
type ComposeAction,
|
type ComposeAction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -154,6 +154,8 @@ const Account = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!withRelationship) return null;
|
||||||
|
|
||||||
if (account.id !== me) {
|
if (account.id !== me) {
|
||||||
return <ActionButton account={account} actionType={actionType} />;
|
return <ActionButton account={account} actionType={actionType} />;
|
||||||
}
|
}
|
||||||
|
@ -297,7 +299,7 @@ const Account = ({
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
<div ref={actionRef}>
|
<div ref={actionRef}>
|
||||||
{(withRelationship || action) ? renderAction() : null}
|
{renderAction()}
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</HStack>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@ const QuotedStatusContainer: React.FC<IQuotedStatusContainer> = ({ composeId })
|
||||||
const status = useAppSelector(state => getStatus(state, { id: state.compose.get(composeId)?.quote! }));
|
const status = useAppSelector(state => getStatus(state, { id: state.compose.get(composeId)?.quote! }));
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
dispatch(cancelQuoteCompose());
|
dispatch(cancelQuoteCompose(composeId));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!status) {
|
if (!status) {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import { $getRoot } from 'lexical';
|
import { $getRoot } from 'lexical';
|
||||||
import { useEffect } from 'react';
|
import debounce from 'lodash/debounce';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
import { setEditorState } from 'soapbox/actions/compose';
|
import { addSuggestedQuote, setEditorState } from 'soapbox/actions/compose';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { fetchStatus } from 'soapbox/actions/statuses';
|
||||||
|
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||||
|
import { getStatusIdsFromLinksInContent } from 'soapbox/utils/status';
|
||||||
|
|
||||||
interface IStatePlugin {
|
interface IStatePlugin {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
|
@ -12,6 +15,38 @@ interface IStatePlugin {
|
||||||
const StatePlugin: React.FC<IStatePlugin> = ({ composeId }) => {
|
const StatePlugin: React.FC<IStatePlugin> = ({ composeId }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
|
const features = useFeatures();
|
||||||
|
|
||||||
|
const getQuoteSuggestions = useCallback(debounce((text: string) => {
|
||||||
|
dispatch(async (_, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const compose = state.compose.get(composeId);
|
||||||
|
|
||||||
|
if (!features.quotePosts || compose?.quote) return;
|
||||||
|
|
||||||
|
const ids = getStatusIdsFromLinksInContent(text);
|
||||||
|
|
||||||
|
let quoteId: string | undefined;
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
if (compose?.dismissed_quotes.includes(id)) continue;
|
||||||
|
|
||||||
|
if (state.statuses.get(id)) {
|
||||||
|
quoteId = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await dispatch(fetchStatus(id));
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
quoteId = status.id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quoteId) dispatch(addSuggestedQuote(composeId, quoteId));
|
||||||
|
});
|
||||||
|
}, 2000), []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor.registerUpdateListener(({ editorState }) => {
|
editor.registerUpdateListener(({ editorState }) => {
|
||||||
|
@ -19,6 +54,7 @@ const StatePlugin: React.FC<IStatePlugin> = ({ composeId }) => {
|
||||||
const isEmpty = text === '';
|
const isEmpty = text === '';
|
||||||
const data = isEmpty ? null : JSON.stringify(editorState.toJSON());
|
const data = isEmpty ? null : JSON.stringify(editorState.toJSON());
|
||||||
dispatch(setEditorState(composeId, data, text));
|
dispatch(setEditorState(composeId, data, text));
|
||||||
|
getQuoteSuggestions(text);
|
||||||
});
|
});
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ import {
|
||||||
COMPOSE_EDITOR_STATE_SET,
|
COMPOSE_EDITOR_STATE_SET,
|
||||||
ComposeAction,
|
ComposeAction,
|
||||||
COMPOSE_CHANGE_MEDIA_ORDER,
|
COMPOSE_CHANGE_MEDIA_ORDER,
|
||||||
|
COMPOSE_ADD_SUGGESTED_QUOTE,
|
||||||
} from '../actions/compose';
|
} from '../actions/compose';
|
||||||
import { EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, type EventsAction } from '../actions/events';
|
import { EVENT_COMPOSE_CANCEL, EVENT_FORM_SET, type EventsAction } from '../actions/events';
|
||||||
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, MeAction } from '../actions/me';
|
import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS, MeAction } from '../actions/me';
|
||||||
|
@ -109,6 +110,7 @@ export const ReducerCompose = ImmutableRecord({
|
||||||
text: '',
|
text: '',
|
||||||
to: ImmutableOrderedSet<string>(),
|
to: ImmutableOrderedSet<string>(),
|
||||||
parent_reblogged_by: null as string | null,
|
parent_reblogged_by: null as string | null,
|
||||||
|
dismissed_quotes: ImmutableOrderedSet<string>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type State = ImmutableMap<string, Compose>;
|
type State = ImmutableMap<string, Compose>;
|
||||||
|
@ -362,7 +364,6 @@ export default function compose(state = initialState, action: ComposeAction | Ev
|
||||||
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
||||||
return updateCompose(state, action.id, compose => compose.set('is_changing_upload', true));
|
return updateCompose(state, action.id, compose => compose.set('is_changing_upload', true));
|
||||||
case COMPOSE_REPLY_CANCEL:
|
case COMPOSE_REPLY_CANCEL:
|
||||||
case COMPOSE_QUOTE_CANCEL:
|
|
||||||
case COMPOSE_RESET:
|
case COMPOSE_RESET:
|
||||||
case COMPOSE_SUBMIT_SUCCESS:
|
case COMPOSE_SUBMIT_SUCCESS:
|
||||||
return updateCompose(state, action.id, () => state.get('default')!.withMutations(map => {
|
return updateCompose(state, action.id, () => state.get('default')!.withMutations(map => {
|
||||||
|
@ -543,6 +544,13 @@ export default function compose(state = initialState, action: ComposeAction | Ev
|
||||||
|
|
||||||
return list.splice(indexA, 1).splice(indexB, 0, moveItem);
|
return list.splice(indexA, 1).splice(indexB, 0, moveItem);
|
||||||
}));
|
}));
|
||||||
|
case COMPOSE_ADD_SUGGESTED_QUOTE:
|
||||||
|
return updateCompose(state, action.id, compose => compose
|
||||||
|
.set('quote', action.quoteId));
|
||||||
|
case COMPOSE_QUOTE_CANCEL:
|
||||||
|
return updateCompose(state, action.id, compose => compose
|
||||||
|
.update('dismissed_quotes', quotes => compose.quote ? quotes.add(compose.quote) : quotes)
|
||||||
|
.set('quote', null));
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,3 +71,13 @@ export const getActualStatus = <T extends { reblog: T | string | null }>(status:
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getStatusIdsFromLinksInContent = (content: string): string[] => {
|
||||||
|
const urls = content.match(RegExp(`${window.location.origin}/@([a-z\\d_-]+(?:@[^@\\s]+)?)/posts/[a-z0-9]+(?!\\S)`, 'gi'));
|
||||||
|
|
||||||
|
if (!urls) return [];
|
||||||
|
|
||||||
|
return Array.from(new Set(urls
|
||||||
|
.map(url => url.split('/').at(-1) as string)
|
||||||
|
.filter(url => url)));
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue