Announcements framework (#1494)
This commit is contained in:
commit
581ae13c3f
24 changed files with 299 additions and 27 deletions
1
flow-typed/content.js
vendored
1
flow-typed/content.js
vendored
|
@ -15,6 +15,7 @@ declare type ContentState = {
|
|||
// It can/should be '?Array<string>` instead -- set it to null, then clients
|
||||
// can cast it to a boolean. That, or rename the variable to `shuffle` if you
|
||||
// don't care about the URLs.
|
||||
lastViewedAnnouncement: ?string, // undefined = not seen in wallet.
|
||||
};
|
||||
|
||||
declare type WatchHistory = {
|
||||
|
|
|
@ -2259,6 +2259,8 @@
|
|||
"Still Valid Until": "Still Valid Until",
|
||||
"Active channel": "Active channel",
|
||||
"Select your default active channel": "Select your default active channel",
|
||||
"What's New": "What's New",
|
||||
"See what are the latest features and changes in Odysee.": "See what are the latest features and changes in Odysee.",
|
||||
"This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.": "This account has livestreaming disabled, please reach out to hello@odysee.com for assistance.",
|
||||
"Attach images by pasting or drag-and-drop.": "Attach images by pasting or drag-and-drop.",
|
||||
"There was a network error, but the publish may have been completed. Wait a few minutes, then check your Uploads or Wallet page to confirm.": "There was a network error, but the publish may have been completed. Wait a few minutes, then check your Uploads or Wallet page to confirm.",
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import { hot } from 'react-hot-loader/root';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectGetSyncErrorMessage, selectSyncFatalError, selectSyncIsLocked } from 'redux/selectors/sync';
|
||||
import {
|
||||
selectGetSyncErrorMessage,
|
||||
selectPrefsReady,
|
||||
selectSyncFatalError,
|
||||
selectSyncIsLocked,
|
||||
} from 'redux/selectors/sync';
|
||||
import { doUserSetReferrer } from 'redux/actions/user';
|
||||
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
||||
import {
|
||||
selectOdyseeMembershipIsPremiumPlus,
|
||||
selectUser,
|
||||
|
@ -15,7 +21,7 @@ import { selectMyChannelClaimIds } from 'redux/selectors/claims';
|
|||
import { selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||
import { selectModal, selectActiveChannelClaim, selectIsReloadRequired } from 'redux/selectors/app';
|
||||
import { selectUploadCount } from 'redux/selectors/publish';
|
||||
import { doSetLanguage } from 'redux/actions/settings';
|
||||
import { doOpenAnnouncements, doSetLanguage } from 'redux/actions/settings';
|
||||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
import { doSignIn, doSetIncognito } from 'redux/actions/app';
|
||||
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
|
||||
|
@ -28,6 +34,7 @@ const select = (state) => ({
|
|||
language: selectLanguage(state),
|
||||
languages: selectLoadedLanguages(state),
|
||||
isReloadRequired: selectIsReloadRequired(state),
|
||||
prefsReady: selectPrefsReady(state),
|
||||
syncError: selectGetSyncErrorMessage(state),
|
||||
syncIsLocked: selectSyncIsLocked(state),
|
||||
uploadCount: selectUploadCount(state),
|
||||
|
@ -51,6 +58,8 @@ const perform = {
|
|||
setIncognito: doSetIncognito,
|
||||
fetchModBlockedList: doFetchModBlockedList,
|
||||
fetchModAmIList: doFetchCommentModAmIList,
|
||||
doOpenAnnouncements,
|
||||
doSetLastViewedAnnouncement,
|
||||
};
|
||||
|
||||
export default hot(connect(select, perform)(App));
|
||||
|
|
|
@ -74,6 +74,7 @@ type Props = {
|
|||
balance: ?number,
|
||||
syncIsLocked: boolean,
|
||||
syncError: ?string,
|
||||
prefsReady: boolean,
|
||||
rewards: Array<Reward>,
|
||||
setReferrer: (string, boolean) => void,
|
||||
isAuthenticated: boolean,
|
||||
|
@ -87,6 +88,8 @@ type Props = {
|
|||
fetchModBlockedList: () => void,
|
||||
fetchModAmIList: () => void,
|
||||
homepageFetched: boolean,
|
||||
doOpenAnnouncements: () => void,
|
||||
doSetLastViewedAnnouncement: (hash: string) => void,
|
||||
};
|
||||
|
||||
function App(props: Props) {
|
||||
|
@ -103,6 +106,7 @@ function App(props: Props) {
|
|||
history,
|
||||
syncError,
|
||||
syncIsLocked,
|
||||
prefsReady,
|
||||
language,
|
||||
languages,
|
||||
setLanguage,
|
||||
|
@ -119,6 +123,8 @@ function App(props: Props) {
|
|||
hasPremiumPlus,
|
||||
fetchModAmIList,
|
||||
homepageFetched,
|
||||
doOpenAnnouncements,
|
||||
doSetLastViewedAnnouncement,
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
@ -438,7 +444,7 @@ function App(props: Props) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps, (one time after locale is fetched)
|
||||
}, [locale]);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (locale) {
|
||||
const countryCode = locale.country;
|
||||
const langs = getLanguagesForCountry(countryCode) || [];
|
||||
|
@ -471,6 +477,19 @@ function App(props: Props) {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [syncError, pathname, isAuthenticated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prefsReady) {
|
||||
doOpenAnnouncements();
|
||||
}
|
||||
}, [prefsReady]);
|
||||
|
||||
useEffect(() => {
|
||||
window.clearLastViewedAnnouncement = () => {
|
||||
console.log('Clearing history. Please wait ...');
|
||||
doSetLastViewedAnnouncement('');
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Keep this at the end to ensure initial setup effects are run first
|
||||
useEffect(() => {
|
||||
if (!hasSignedIn && hasVerifiedEmail) {
|
||||
|
|
|
@ -143,6 +143,7 @@ export const CLEAR_CONTENT_POSITION = 'CLEAR_CONTENT_POSITION';
|
|||
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
||||
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
||||
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
||||
export const SET_LAST_VIEWED_ANNOUNCEMENT = 'SET_LAST_VIEWED_ANNOUNCEMENT';
|
||||
export const RECOMMENDATION_UPDATED = 'RECOMMENDATION_UPDATED';
|
||||
export const RECOMMENDATION_CLICKED = 'RECOMMENDATION_CLICKED';
|
||||
export const TOGGLE_LOOP_LIST = 'TOGGLE_LOOP_LIST';
|
||||
|
|
|
@ -51,3 +51,4 @@ export const CONFIRM_REMOVE_COMMENT = 'CONFIRM_REMOVE_COMMENT';
|
|||
export const CONFIRM_ODYSEE_MEMBERSHIP = 'CONFIRM_ODYSEE_MEMBERSHIP';
|
||||
export const MEMBERSHIP_SPLASH = 'MEMBERSHIP_SPLASH';
|
||||
export const HIDE_RECOMMENDATION = 'HIDE_RECOMMENDATION';
|
||||
export const ANNOUNCEMENTS = 'ANNOUNCEMENTS';
|
||||
|
|
20
ui/modal/modalAnnouncements/index.js
Normal file
20
ui/modal/modalAnnouncements/index.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import ModalAnnouncements from './view';
|
||||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { doSetLastViewedAnnouncement } from 'redux/actions/content';
|
||||
import { selectLastViewedAnnouncement } from 'redux/selectors/content';
|
||||
import { selectHomepageAnnouncement } from 'redux/selectors/settings';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
|
||||
const select = (state) => ({
|
||||
authenticated: selectUserVerifiedEmail(state),
|
||||
announcement: selectHomepageAnnouncement(state),
|
||||
lastViewedHash: selectLastViewedAnnouncement(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
doHideModal,
|
||||
doSetLastViewedAnnouncement,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(ModalAnnouncements);
|
68
ui/modal/modalAnnouncements/view.jsx
Normal file
68
ui/modal/modalAnnouncements/view.jsx
Normal file
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import Card from 'component/common/card';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import { Modal } from 'modal/modal';
|
||||
import { getSimpleStrHash } from 'util/string';
|
||||
|
||||
type Props = {
|
||||
isAutoInvoked?: boolean,
|
||||
// --- redux ---
|
||||
authenticated?: boolean,
|
||||
announcement: string,
|
||||
lastViewedHash: ?string,
|
||||
doHideModal: () => void,
|
||||
doSetLastViewedAnnouncement: (hash: string) => void,
|
||||
};
|
||||
|
||||
export default function ModalAnnouncements(props: Props) {
|
||||
const {
|
||||
authenticated,
|
||||
announcement,
|
||||
lastViewedHash,
|
||||
isAutoInvoked,
|
||||
doHideModal,
|
||||
doSetLastViewedAnnouncement,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
location: { pathname },
|
||||
} = useHistory();
|
||||
|
||||
const [show, setShow] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!authenticated || (pathname !== '/' && pathname !== `/$/${PAGES.HELP}`) || announcement === '') {
|
||||
doHideModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = getSimpleStrHash(announcement);
|
||||
|
||||
if (lastViewedHash === hash) {
|
||||
if (isAutoInvoked) {
|
||||
doHideModal();
|
||||
} else {
|
||||
setShow(true);
|
||||
}
|
||||
} else {
|
||||
setShow(true);
|
||||
doSetLastViewedAnnouncement(hash);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal type="card" isOpen onAborted={doHideModal}>
|
||||
<Card
|
||||
className="announcement"
|
||||
actions={<MarkdownPreview className="markdown-preview--announcement" content={announcement} simpleLinks />}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectModal } from 'redux/selectors/app';
|
||||
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import { selectError } from 'redux/selectors/notifications'; // RENAME THIS 'selectNotificationError'
|
||||
import ModalRouter from './view';
|
||||
|
||||
|
@ -9,9 +9,8 @@ const select = (state, props) => ({
|
|||
error: selectError(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
openModal: (props) => dispatch(doOpenModal(props)),
|
||||
hideModal: (props) => dispatch(doHideModal(props)),
|
||||
});
|
||||
const perform = {
|
||||
hideModal: doHideModal,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(ModalRouter);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { lazyImport } from 'util/lazyImport';
|
|||
|
||||
// prettier-ignore
|
||||
const MAP = Object.freeze({
|
||||
[MODALS.ANNOUNCEMENTS]: lazyImport(() => import('modal/modalAnnouncements' /* webpackChunkName: "modalAnnouncements" */)),
|
||||
[MODALS.AFFIRM_PURCHASE]: lazyImport(() => import('modal/modalAffirmPurchase' /* webpackChunkName: "modalAffirmPurchase" */)),
|
||||
[MODALS.AUTO_GENERATE_THUMBNAIL]: lazyImport(() => import('modal/modalAutoGenerateThumbnail' /* webpackChunkName: "modalAutoGenerateThumbnail" */)),
|
||||
[MODALS.AUTO_UPDATE_DOWNLOADED]: lazyImport(() => import('modal/modalAutoUpdateDownloaded' /* webpackChunkName: "modalAutoUpdateDownloaded" */)),
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
import HelpPage from './view';
|
||||
import { connect } from 'react-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { selectHomepageAnnouncement } from 'redux/selectors/settings';
|
||||
|
||||
export default HelpPage;
|
||||
const select = (state) => ({
|
||||
announcement: selectHomepageAnnouncement(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
doOpenModal,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(HelpPage);
|
||||
|
|
|
@ -7,9 +7,35 @@ import Page from 'component/page';
|
|||
import Card from 'component/common/card';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
|
||||
export default function HelpPage() {
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
|
||||
type Props = {
|
||||
announcement: string,
|
||||
doOpenModal: (string, ?{}) => void,
|
||||
};
|
||||
|
||||
export default function HelpPage(props: Props) {
|
||||
const { announcement, doOpenModal } = props;
|
||||
|
||||
return (
|
||||
<Page className="card-stack">
|
||||
{announcement && (
|
||||
<Card
|
||||
title={__("What's New")}
|
||||
subtitle={__('See what are the latest features and changes in Odysee.')}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
label={__("What's New")}
|
||||
icon={ICONS.FEEDBACK}
|
||||
button="secondary"
|
||||
onClick={() => doOpenModal(MODALS.ANNOUNCEMENTS)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Card
|
||||
title={__('Visit the %SITE_NAME% Help Hub', { SITE_NAME })}
|
||||
subtitle={__('Our support posts answer many common questions.')}
|
||||
|
|
|
@ -374,3 +374,12 @@ export function doToggleShuffleList(currentUri: string, collectionId: string, sh
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetLastViewedAnnouncement(hash: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_LAST_VIEWED_ANNOUNCEMENT,
|
||||
data: hash,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { doWalletReconnect } from 'redux/actions/wallet';
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import * as DAEMON_SETTINGS from 'constants/daemon_settings';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as SHARED_PREFERENCES from 'constants/shared_preferences';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import analytics from 'analytics';
|
||||
|
@ -10,7 +11,7 @@ import SUPPORTED_LANGUAGES from 'constants/supported_languages';
|
|||
import { launcher } from 'util/autoLaunch';
|
||||
import { selectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSyncLoop, doSyncUnsubscribe, doSetSyncLock } from 'redux/actions/sync';
|
||||
import { doAlertWaitingForSync, doGetAndPopulatePreferences } from 'redux/actions/app';
|
||||
import { doAlertWaitingForSync, doGetAndPopulatePreferences, doOpenModal } from 'redux/actions/app';
|
||||
import { selectPrefsReady } from 'redux/selectors/sync';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { getDefaultLanguage } from 'util/default-languages';
|
||||
|
@ -317,23 +318,38 @@ function populateCategoryTitles(categories) {
|
|||
}
|
||||
}
|
||||
|
||||
function loadBuiltInHomepageData(dispatch) {
|
||||
const homepages = require('homepages');
|
||||
if (homepages) {
|
||||
const v2 = {};
|
||||
const homepageKeys = Object.keys(homepages);
|
||||
homepageKeys.forEach((hp) => {
|
||||
v2[hp] = homepages[hp];
|
||||
});
|
||||
|
||||
window.homepages = v2;
|
||||
populateCategoryTitles(window.homepages?.en?.categories);
|
||||
dispatch({ type: ACTIONS.FETCH_HOMEPAGES_DONE });
|
||||
}
|
||||
}
|
||||
|
||||
export function doOpenAnnouncements() {
|
||||
return (dispatch) => {
|
||||
// There is a weird useEffect in modalRouter that closes all modals on
|
||||
// initial mount. Not sure what scenario that covers, so just delay a bit
|
||||
// until it is mounted.
|
||||
setTimeout(() => {
|
||||
dispatch(doOpenModal(MODALS.ANNOUNCEMENTS, { isAutoInvoked: true }));
|
||||
}, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchHomepages() {
|
||||
return (dispatch) => {
|
||||
// -- Use this env flag to use local homepage data. Otherwise, it will grab from `/$/api/content/v*/get`.
|
||||
// -- Use this env flag to use local homepage data and meme (faster).
|
||||
// -- Otherwise, it will grab from `/$/api/content/v*/get`.
|
||||
// @if USE_LOCAL_HOMEPAGE_DATA='true'
|
||||
const homepages = require('homepages');
|
||||
if (homepages) {
|
||||
const v2 = {};
|
||||
const homepageKeys = Object.keys(homepages);
|
||||
homepageKeys.forEach((hp) => {
|
||||
v2[hp] = homepages[hp];
|
||||
});
|
||||
|
||||
window.homepages = v2;
|
||||
populateCategoryTitles(window.homepages?.en?.categories);
|
||||
dispatch({ type: ACTIONS.FETCH_HOMEPAGES_DONE });
|
||||
return;
|
||||
}
|
||||
loadBuiltInHomepageData(dispatch);
|
||||
// @endif
|
||||
|
||||
fetch('https://odysee.com/$/api/content/v2/get')
|
||||
|
|
|
@ -421,6 +421,7 @@ type SharedData = {
|
|||
editedCollections: CollectionGroup,
|
||||
builtinCollections: CollectionGroup,
|
||||
savedCollections: Array<string>,
|
||||
lastViewedAnnouncement: string,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -439,6 +440,7 @@ function extractUserState(rawObj: SharedData) {
|
|||
editedCollections,
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
lastViewedAnnouncement,
|
||||
} = rawObj.value;
|
||||
|
||||
return {
|
||||
|
@ -454,6 +456,7 @@ function extractUserState(rawObj: SharedData) {
|
|||
...(editedCollections ? { editedCollections } : {}),
|
||||
...(builtinCollections ? { builtinCollections } : {}),
|
||||
...(savedCollections ? { savedCollections } : {}),
|
||||
...(lastViewedAnnouncement ? { lastViewedAnnouncement } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -475,6 +478,7 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
|||
editedCollections,
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
lastViewedAnnouncement,
|
||||
} = extractUserState(sharedSettings);
|
||||
dispatch({
|
||||
type: ACTIONS.USER_STATE_POPULATE,
|
||||
|
@ -491,6 +495,7 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
|||
editedCollections,
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
lastViewedAnnouncement,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ const defaultState: ContentState = {
|
|||
recommendationUrls: {},
|
||||
recommendationClicks: {},
|
||||
loopList: undefined,
|
||||
lastViewedAnnouncement: '',
|
||||
};
|
||||
|
||||
reducers[ACTIONS.SET_PRIMARY_URI] = (state, action) =>
|
||||
|
@ -118,6 +119,8 @@ reducers[ACTIONS.CLEAR_CONTENT_HISTORY_URI] = (state, action) => {
|
|||
|
||||
reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = (state) => ({ ...state, history: [] });
|
||||
|
||||
reducers[ACTIONS.SET_LAST_VIEWED_ANNOUNCEMENT] = (state, action) => ({ ...state, lastViewedAnnouncement: action.data });
|
||||
|
||||
// reducers[LBRY_REDUX_ACTIONS.PURCHASE_URI_FAILED] = (state, action) => {
|
||||
// return {
|
||||
// ...state,
|
||||
|
@ -125,6 +128,11 @@ reducers[ACTIONS.CLEAR_CONTENT_HISTORY_ALL] = (state) => ({ ...state, history: [
|
|||
// };
|
||||
// };
|
||||
|
||||
reducers[ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||
const { lastViewedAnnouncement } = action.data;
|
||||
return { ...state, lastViewedAnnouncement };
|
||||
};
|
||||
|
||||
export default function reducer(state: ContentState = defaultState, action: any) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
|
|
|
@ -26,6 +26,7 @@ export const selectPlayingUri = (state: State) => selectState(state).playingUri;
|
|||
export const selectPrimaryUri = (state: State) => selectState(state).primaryUri;
|
||||
export const selectListLoop = (state: State) => selectState(state).loopList;
|
||||
export const selectListShuffle = (state: State) => selectState(state).shuffleList;
|
||||
export const selectLastViewedAnnouncement = (state: State) => selectState(state).lastViewedAnnouncement;
|
||||
|
||||
export const makeSelectIsPlaying = (uri: string) =>
|
||||
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
|
||||
|
|
|
@ -82,6 +82,18 @@ export const selectHomepageMeme = (state) => {
|
|||
return homepages ? homepages['en'].meme || {} : {};
|
||||
};
|
||||
|
||||
export const selectHomepageAnnouncement = (state) => {
|
||||
const homepageCode = selectHomepageCode(state);
|
||||
const homepages = window.homepages;
|
||||
if (homepages) {
|
||||
const news = homepages[homepageCode].announcement;
|
||||
if (news) {
|
||||
return news;
|
||||
}
|
||||
}
|
||||
return homepages ? homepages['en'].announcement || '' : '';
|
||||
};
|
||||
|
||||
export const selectInRegionByCode = (state, code) => {
|
||||
const hp = selectClientSetting(state, SETTINGS.HOMEPAGE);
|
||||
const lang = selectLanguage(state);
|
||||
|
|
|
@ -303,6 +303,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Move .announcement to `modalAnnouncements/style.scss` when structure is ready.
|
||||
.announcement {
|
||||
@media (min-width: $breakpoint-small) {
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-preview--announcement {
|
||||
@media (min-width: $breakpoint-small) {
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
img {
|
||||
max-width: 40%;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-preview {
|
||||
background-color: rgba(var(--color-header-background-base), 1);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ function enableBatching(reducer) {
|
|||
};
|
||||
}
|
||||
|
||||
const contentFilter = createFilter('content', ['positions', 'history']);
|
||||
const contentFilter = createFilter('content', ['positions', 'history', 'lastViewedAnnouncement']);
|
||||
const fileInfoFilter = createFilter('fileInfo', [
|
||||
'fileListPublishedSort',
|
||||
'fileListDownloadedSort',
|
||||
|
@ -141,6 +141,7 @@ const triggerSharedStateActions = [
|
|||
ACTIONS.COLLECTION_DELETE,
|
||||
ACTIONS.COLLECTION_NEW,
|
||||
ACTIONS.COLLECTION_PENDING,
|
||||
ACTIONS.SET_LAST_VIEWED_ANNOUNCEMENT,
|
||||
// MAYBE COLLECTOIN SAVE
|
||||
// ACTIONS.SET_WELCOME_VERSION,
|
||||
// ACTIONS.SET_ALLOW_ANALYTICS,
|
||||
|
@ -183,6 +184,7 @@ const sharedStateFilters = {
|
|||
editedCollections: { source: 'collections', property: 'edited' },
|
||||
// savedCollections: { source: 'collections', property: 'saved' },
|
||||
unpublishedCollections: { source: 'collections', property: 'unpublished' },
|
||||
lastViewedAnnouncement: { source: 'content', property: 'lastViewedAnnouncement' },
|
||||
};
|
||||
|
||||
const sharedStateCb = ({ dispatch, getState, syncId }) => {
|
||||
|
|
|
@ -23,3 +23,15 @@ export function getLocalStorageSummary() {
|
|||
return 'inaccessible';
|
||||
}
|
||||
}
|
||||
|
||||
const localStorageAvailable = isLocalStorageAvailable();
|
||||
|
||||
export function getLocalStorageItem(key) {
|
||||
return localStorageAvailable ? window.localStorage.getItem(key) : undefined;
|
||||
}
|
||||
|
||||
export function setLocalStorageItem(key, value) {
|
||||
if (localStorageAvailable) {
|
||||
window.localStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,3 +26,7 @@ export function toCompactNotation(number: string | number, lang: ?string, minThr
|
|||
export function stripLeadingAtSign(str: ?string) {
|
||||
return str && str.charAt(0) === '@' ? str.slice(1) : str;
|
||||
}
|
||||
|
||||
export function getSimpleStrHash(s: string) {
|
||||
return String(s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
const path = require('path');
|
||||
const memo = {};
|
||||
|
||||
const loadAnnouncements = (homepageKeys) => {
|
||||
const fs = require('fs');
|
||||
const announcements = {};
|
||||
|
||||
homepageKeys.forEach((key) => {
|
||||
const file = path.join(__dirname, `../dist/announcement/${key.toLowerCase()}.md`);
|
||||
const announcement = fs.readFileSync(file, 'utf8');
|
||||
announcements[key] = announcement ? announcement.trim() : '';
|
||||
});
|
||||
|
||||
return announcements;
|
||||
};
|
||||
|
||||
// this didn't seem to help.
|
||||
if (!memo.homepageData) {
|
||||
try {
|
||||
memo.homepageData = require('../../custom/homepages/v2');
|
||||
memo.announcements = loadAnnouncements(Object.keys(memo.homepageData));
|
||||
} catch (err) {
|
||||
console.log('getHomepageJSON:', err);
|
||||
}
|
||||
|
@ -30,7 +45,10 @@ const getHomepageJsonV2 = () => {
|
|||
const v2 = {};
|
||||
const homepageKeys = Object.keys(memo.homepageData);
|
||||
homepageKeys.forEach((hp) => {
|
||||
v2[hp] = memo.homepageData[hp];
|
||||
v2[hp] = {
|
||||
...memo.homepageData[hp],
|
||||
announcement: memo.announcements[hp],
|
||||
};
|
||||
});
|
||||
return v2;
|
||||
};
|
||||
|
|
|
@ -68,6 +68,10 @@ const copyWebpackCommands = [
|
|||
from: `${WEB_STATIC_ROOT}/pwa/`,
|
||||
to: `${DIST_ROOT}/public/pwa/`,
|
||||
},
|
||||
{
|
||||
from: `${STATIC_ROOT}/../custom/homepages/v2/announcement`,
|
||||
to: `${DIST_ROOT}/announcement`,
|
||||
},
|
||||
];
|
||||
|
||||
const CUSTOM_OG_PATH = `${CUSTOM_ROOT}/v2-og.png`;
|
||||
|
|
Loading…
Reference in a new issue