Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { Dispatch, useReducer, useEffect, lazy, useRef, RefObject } from 'react';
- import { SelectedPostList } from 'sites/pc3/TopicPage/useSelectedPosts';
- import { YearDay } from 'artboard/DateTimePicker';
- import { useLazyQuery } from '@apollo/react-hooks';
- import { ComposerQuery, ComposerQuery_me } from '__generated__';
- import gql from 'graphql-tag';
- const COMPOSER_QUERY = gql`
- query ComposerQuery {
- me {
- id
- username
- avatar {
- url
- }
- staffRoles: roles(allowStaffPosting: true) {
- id
- title
- icon
- color
- }
- }
- }
- `;
- export const COMPOSER_POPUPS = {
- 'font.family': lazy(() => import('./modules/popups/FontFamilyPopup')),
- 'font.size': lazy(() => import('./modules/popups/FontSizePopup')),
- };
- export type ComposerPopupName = keyof typeof COMPOSER_POPUPS;
- export enum ComposerFlags {
- SUBMIT = 1 << 0,
- ATTACH = 1 << 1,
- STAFF_POST = 1 << 2,
- DRAFT = 1 << 3,
- SCHEDULE = 1 << 4,
- QUICK_CLOSE = 1 << 5,
- QUICK_STICK = 1 << 6,
- }
- /** An object that can be edited as a post. */
- export type ComposerEditable = {
- id: string;
- type: string;
- content: string;
- }
- /** Options passed to the `useComposer` hook. */
- export type ComposerOpts = {
- /** The base name used for non-edit cache keys, such as "topic". */
- domainId: string;
- /** A list of features that are enabled for this composer. */
- flags: number;
- /** Callback to be called with the composer state when submitting. */
- onSubmit: unknown;
- /** A list of selected post ids. */
- selectedPosts: SelectedPostList;
- /** Terms used for dynamic copy about the container of the new post. */
- term?: { asTitle: string, asWord: string };
- }
- /** Composer state as formed by the reducer. */
- type ComposerInternalState = {
- /** Whether the composer is open. */
- isOpen: boolean;
- /** The post currently being edited. */
- editing: ComposerEditable | null;
- /** The content of the post being written. */
- content: string;
- /** Whether a cached value was loaded before being edited. */
- isFromCache: boolean;
- /** The day this post is scheduled to publish, if scheduled. */
- scheduleDay: YearDay | null;
- /** The visibility this post is scheduled to publish with. */
- visibility: 'published' | 'draft' | 'scheduled';
- /** The staff role this post will publish as. */
- staffRole: string | null;
- /** The current popup menu in the Write tab. */
- currentPopup: ComposerPopupName | null;
- /** Whether the topic should be closed on submitting. */
- closeOnSubmit: boolean;
- /** Whether the topic should be stickied on submitting. */
- stickyOnSubmit: boolean;
- /** Whether the composer should automatically cache changes periodically. */
- doAutoCache: boolean;
- }
- type ComposerInternalAction =
- | { type: 'OPEN_MENU' }
- | { type: 'CLOSE_MENU' }
- | { type: 'CREATE_POST' }
- | { type: 'EDIT_POST', post: ComposerEditable }
- | { type: 'SET_CONTENT', content: string }
- | { type: 'SET_SCHEDULE_DAY', scheduleDay: YearDay | null }
- | { type: 'SET_VIS', vis: ComposerState['visibility'] }
- | { type: 'SET_STAFF_ROLE', staffRole: string | null }
- | { type: 'OPEN_POPUP', popup: ComposerPopupName }
- | { type: 'CLOSE_POPUP' }
- | { type: 'UNCACHE_AND_RELOAD' }
- | { type: 'SET_STICKY_ON_SUBMIT', stickyOnSubmit: boolean }
- | { type: 'SET_CLOSE_ON_SUBMIT', closeOnSubmit: boolean }
- | { type: 'SET_AUTO_CACHE', doAutoCache: boolean }
- /**
- * The state of a composer hook.
- * Stores both stateful and computed values about everything going on
- * inside the composer, along with a `dispatch` method for applying changes.
- */
- export type ComposerState =
- & ComposerInternalState
- & { dispatch: Dispatch<ComposerInternalAction> }
- & {
- /** The current user as seen by the composer. */
- me: ComposerQuery_me | null | undefined;
- /** Whether a given flag is enabled for the composer. */
- hasFlag(flag: number): boolean;
- /** Whether the composer can be submitted. */
- canSubmit: boolean;
- /** Copy for the topic type or related. */
- term: undefined | ComposerOpts['term'];
- /** A reference to the textarea itself. */
- textareaRef: RefObject<HTMLTextAreaElement>;
- }
- function useComposer(opts: ComposerOpts): ComposerState {
- const { domainId, flags, selectedPosts, term } = opts;
- const textareaRef = useRef<HTMLTextAreaElement>(null);
- function reducer(
- state: ComposerInternalState,
- action: ComposerInternalAction,
- ): ComposerInternalState {
- switch (action.type) {
- case 'OPEN_MENU': {
- return { ...state, isOpen: true }
- }
- case 'CLOSE_MENU': {
- return { ...state, isOpen: false }
- }
- case 'SET_CONTENT': {
- return { ...state, content: action.content, isFromCache: false };
- }
- case 'EDIT_POST': {
- return {
- ...state,
- ...deriveInitialContentForEdit(action.post),
- isOpen: true,
- }
- }
- case 'CREATE_POST': {
- return {
- ...state,
- ...deriveInitialContentForNew(domainId, selectedPosts),
- isOpen: true,
- }
- }
- case 'UNCACHE_AND_RELOAD': {
- if (state.editing) {
- return {
- ...state,
- isFromCache: false,
- content: state.editing.content,
- };
- } else {
- return {
- ...state,
- isFromCache: false,
- content: baseContentForNew(selectedPosts),
- };
- }
- }
- case 'OPEN_POPUP': {
- return { ...state, currentPopup: action.popup };
- }
- case 'CLOSE_POPUP': {
- return { ...state, currentPopup: null };
- }
- case 'SET_STAFF_ROLE': {
- return { ...state, staffRole: action.staffRole };
- }
- case 'SET_SCHEDULE_DAY': {
- return { ...state, scheduleDay: action.scheduleDay };
- }
- case 'SET_VIS': {
- return { ...state, visibility: action.vis };
- }
- case 'SET_CLOSE_ON_SUBMIT': {
- return { ...state, closeOnSubmit: action.closeOnSubmit };
- }
- case 'SET_STICKY_ON_SUBMIT': {
- return { ...state, stickyOnSubmit: action.stickyOnSubmit };
- }
- case 'SET_AUTO_CACHE': {
- return { ...state, doAutoCache: action.doAutoCache };
- }
- }
- }
- const [state, dispatch] = useReducer(reducer, {
- ...deriveInitialContentForNew(domainId),
- isOpen: __DEV__,
- editing: null,
- scheduleDay: null,
- staffRole: null,
- currentPopup: null,
- visibility: 'published',
- stickyOnSubmit: false,
- closeOnSubmit: false,
- doAutoCache: false,
- });
- const [getMe, { data }] = useLazyQuery<ComposerQuery>(COMPOSER_QUERY);
- const me = data?.me;
- useEffect(() => {
- if (!state.isOpen) {
- saveToCache(state.content, state.editing ?? domainId)
- }
- },
- [state.isOpen, state.content, state.editing, domainId],
- );
- useEffect(
- () => { state.isOpen && getMe() },
- [state.isOpen, getMe],
- );
- function hasFlag(flag: number) {
- return (flags & flag) === flag;
- }
- let canSubmit = state.content.length > 0;
- if (state.visibility === 'scheduled') {
- canSubmit = canSubmit && state.scheduleDay != null;
- }
- return {
- ...state,
- dispatch,
- me,
- term,
- hasFlag,
- canSubmit,
- textareaRef,
- };
- }
- export default useComposer;
- function deriveInitialContentForNew(
- domainId: string,
- selectedPosts?: SelectedPostList,
- ) {
- const cachedContent = getCachedContent(domainId);
- if (cachedContent) {
- return { content: cachedContent, isFromCache: true };
- }
- return {
- content: baseContentForNew(selectedPosts),
- isFromCache: false,
- };
- }
- function deriveInitialContentForEdit(post: ComposerEditable) {
- const cachedEdit = getCachedContent(post);
- if (cachedEdit) {
- return { content: cachedEdit, isFromCache: true };
- }
- return { content: post.content, isFromCache: false };
- }
- function baseContentForNew(selectedPosts?: SelectedPostList) {
- return '';
- }
- function getCachedContent(domainIdOrEditable: string | ComposerEditable) {
- return localStorage.getItem(getCacheKey(domainIdOrEditable));
- }
- function saveToCache(content: string, domainIdOrEditable: string | ComposerEditable) {
- if (!content) {
- return;
- }
- const cacheKey = getCacheKey(domainIdOrEditable);
- if (__DEV__) {
- console.log(`Composer: saved ${content} to ${cacheKey}`);
- }
- localStorage.setItem(cacheKey, content);
- }
- function getCacheKey(domainIdOrEditable: string | ComposerEditable) {
- if (typeof domainIdOrEditable === 'string') {
- return `composer-cache-new-${domainIdOrEditable}`;
- }
- return `composer-cache-editing-${domainIdOrEditable.type}:${domainIdOrEditable.id}`;
- }
- function deserializeState() {
- }
Add Comment
Please, Sign In to add comment