mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 19:15:29 +08:00
Merge pull request #1335 from coralproject/keep-drafts
Keep comment drafts
This commit is contained in:
@@ -10,7 +10,7 @@ import jwtDecode from 'jwt-decode';
|
||||
export const handleLogin = (email, password, recaptchaResponse) => (
|
||||
dispatch,
|
||||
_,
|
||||
{ rest, client, storage }
|
||||
{ rest, client, localStorage }
|
||||
) => {
|
||||
dispatch({ type: actions.LOGIN_REQUEST });
|
||||
|
||||
@@ -31,9 +31,9 @@ export const handleLogin = (email, password, recaptchaResponse) => (
|
||||
return rest('/auth/local', params)
|
||||
.then(({ user, token }) => {
|
||||
if (!user) {
|
||||
if (!bowser.safari && !bowser.ios && storage) {
|
||||
storage.removeItem('token');
|
||||
storage.removeItem('exp');
|
||||
if (!bowser.safari && !bowser.ios && localStorage) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('exp');
|
||||
}
|
||||
return dispatch(checkLoginFailure('not logged in'));
|
||||
}
|
||||
@@ -122,14 +122,18 @@ const checkLoginFailure = error => ({
|
||||
error,
|
||||
});
|
||||
|
||||
export const checkLogin = () => (dispatch, _, { rest, client, storage }) => {
|
||||
export const checkLogin = () => (
|
||||
dispatch,
|
||||
_,
|
||||
{ rest, client, localStorage }
|
||||
) => {
|
||||
dispatch(checkLoginRequest());
|
||||
return rest('/auth')
|
||||
.then(({ user }) => {
|
||||
if (!user) {
|
||||
if (!bowser.safari && !bowser.ios && storage) {
|
||||
storage.removeItem('token');
|
||||
storage.removeItem('exp');
|
||||
if (!bowser.safari && !bowser.ios && localStorage) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('exp');
|
||||
}
|
||||
return dispatch(checkLoginFailure('not logged in'));
|
||||
}
|
||||
@@ -150,11 +154,11 @@ export const checkLogin = () => (dispatch, _, { rest, client, storage }) => {
|
||||
// LOGOUT
|
||||
//==============================================================================
|
||||
|
||||
export const logout = () => (dispatch, _, { rest, client, storage }) => {
|
||||
export const logout = () => (dispatch, _, { rest, client, localStorage }) => {
|
||||
return rest('/auth', { method: 'DELETE' }).then(() => {
|
||||
if (storage) {
|
||||
storage.removeItem('token');
|
||||
storage.removeItem('exp');
|
||||
if (localStorage) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('exp');
|
||||
}
|
||||
|
||||
// Reset the websocket.
|
||||
@@ -168,10 +172,10 @@ export const logout = () => (dispatch, _, { rest, client, storage }) => {
|
||||
// AUTH TOKEN
|
||||
//==============================================================================
|
||||
|
||||
export const handleAuthToken = token => (dispatch, _, { storage }) => {
|
||||
if (storage) {
|
||||
storage.setItem('exp', jwtDecode(token).exp);
|
||||
storage.setItem('token', token);
|
||||
export const handleAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
if (localStorage) {
|
||||
localStorage.setItem('exp', jwtDecode(token).exp);
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
dispatch({ type: 'HANDLE_AUTH_TOKEN' });
|
||||
};
|
||||
|
||||
@@ -4,10 +4,10 @@ export const toggleModal = open => ({ type: actions.TOGGLE_MODAL, open });
|
||||
export const singleView = () => ({ type: actions.SINGLE_VIEW });
|
||||
|
||||
// hide shortcuts note
|
||||
export const hideShortcutsNote = () => (dispatch, _, { storage }) => {
|
||||
export const hideShortcutsNote = () => (dispatch, _, { localStorage }) => {
|
||||
try {
|
||||
if (storage) {
|
||||
storage.setItem('coral:shortcutsNote', 'hide');
|
||||
if (localStorage) {
|
||||
localStorage.setItem('coral:shortcutsNote', 'hide');
|
||||
}
|
||||
} catch (e) {
|
||||
// above will fail in Safari private mode
|
||||
|
||||
@@ -14,8 +14,8 @@ import { hideShortcutsNote } from './actions/moderation';
|
||||
|
||||
smoothscroll.polyfill();
|
||||
|
||||
function init({ store, storage }) {
|
||||
if (storage && storage.getItem('coral:shortcutsNote') === 'hide') {
|
||||
function init({ store, localStorage }) {
|
||||
if (localStorage && localStorage.getItem('coral:shortcutsNote') === 'hide') {
|
||||
store.dispatch(hideShortcutsNote());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,10 +90,10 @@ const signInFailure = error => ({
|
||||
// AUTH TOKEN
|
||||
//==============================================================================
|
||||
|
||||
export const handleAuthToken = token => (dispatch, _, { storage }) => {
|
||||
if (storage) {
|
||||
storage.setItem('exp', jwtDecode(token).exp);
|
||||
storage.setItem('token', token);
|
||||
export const handleAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
if (localStorage) {
|
||||
localStorage.setItem('exp', jwtDecode(token).exp);
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
|
||||
dispatch({ type: 'HANDLE_AUTH_TOKEN' });
|
||||
@@ -260,13 +260,13 @@ export const fetchForgotPassword = email => (dispatch, getState, { rest }) => {
|
||||
export const logout = () => async (
|
||||
dispatch,
|
||||
_,
|
||||
{ rest, client, pym, storage }
|
||||
{ rest, client, pym, localStorage }
|
||||
) => {
|
||||
await rest('/auth', { method: 'DELETE' });
|
||||
|
||||
if (storage) {
|
||||
storage.removeItem('token');
|
||||
storage.removeItem('exp');
|
||||
if (localStorage) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('exp');
|
||||
}
|
||||
|
||||
// Reset the websocket.
|
||||
@@ -297,15 +297,15 @@ const ErrNotLoggedIn = new Error('Not logged in');
|
||||
export const checkLogin = () => (
|
||||
dispatch,
|
||||
_,
|
||||
{ rest, client, pym, storage }
|
||||
{ rest, client, pym, localStorage }
|
||||
) => {
|
||||
dispatch(checkLoginRequest());
|
||||
rest('/auth')
|
||||
.then(result => {
|
||||
if (!result.user) {
|
||||
if (storage) {
|
||||
storage.removeItem('token');
|
||||
storage.removeItem('exp');
|
||||
if (localStorage) {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('exp');
|
||||
}
|
||||
throw ErrNotLoggedIn;
|
||||
}
|
||||
@@ -328,10 +328,10 @@ export const checkLogin = () => (
|
||||
if (error !== ErrNotLoggedIn) {
|
||||
console.error(error);
|
||||
}
|
||||
if (error.status && error.status === 401 && storage) {
|
||||
if (error.status && error.status === 401 && localStorage) {
|
||||
// Unauthorized.
|
||||
storage.removeItem('token');
|
||||
storage.removeItem('exp');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('exp');
|
||||
}
|
||||
const errorMessage = error.translation_key
|
||||
? t(`error.${error.translation_key}`)
|
||||
|
||||
@@ -14,18 +14,10 @@ import reducers from './reducers';
|
||||
import TalkProvider from 'coral-framework/components/TalkProvider';
|
||||
import pluginsConfig from 'pluginsConfig';
|
||||
|
||||
function inIframe() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move init code into `bootstrap` service after auth has been refactored.
|
||||
function preInit({ store, pym }) {
|
||||
function preInit({ store, pym, inIframe }) {
|
||||
// TODO: This is popup specific code and needs to be refactored.
|
||||
if (!inIframe()) {
|
||||
if (!inIframe) {
|
||||
store.dispatch(addExternalConfig({}));
|
||||
store.dispatch(checkLogin());
|
||||
return;
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from 'coral-ui';
|
||||
import cn from 'classnames';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
import { name } from '../containers/CommentBox';
|
||||
import styles from './CommentForm.css';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import DraftArea from '../containers/DraftArea';
|
||||
|
||||
/**
|
||||
* Common UI for Creating or Editing a Comment
|
||||
@@ -61,10 +61,6 @@ export class CommentForm extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
onBodyChange = e => {
|
||||
this.props.onBodyChange(e.target.value);
|
||||
};
|
||||
|
||||
onClickSubmit = () => {
|
||||
this.props.onSubmit();
|
||||
};
|
||||
@@ -107,37 +103,16 @@ export class CommentForm extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`${name}-container`}>
|
||||
<label
|
||||
htmlFor={this.props.bodyInputId}
|
||||
className="screen-reader-text"
|
||||
aria-hidden={true}
|
||||
>
|
||||
{this.props.bodyLabel}
|
||||
</label>
|
||||
<textarea
|
||||
className={`${name}-textarea`}
|
||||
value={body}
|
||||
placeholder={this.props.bodyPlaceholder}
|
||||
id={this.props.bodyInputId}
|
||||
onChange={this.onBodyChange}
|
||||
rows={3}
|
||||
disabled={disableTextArea}
|
||||
/>
|
||||
<Slot fill="commentInputArea" />
|
||||
</div>
|
||||
{this.props.charCountEnable && (
|
||||
<div
|
||||
className={`${name}-char-count ${
|
||||
length > maxCharCount ? `${name}-char-max` : ''
|
||||
}`}
|
||||
>
|
||||
{maxCharCount &&
|
||||
`${maxCharCount - length} ${t(
|
||||
'comment_box.characters_remaining'
|
||||
)}`}
|
||||
</div>
|
||||
)}
|
||||
<DraftArea
|
||||
id={this.props.bodyInputId}
|
||||
label={this.props.bodyLabel}
|
||||
value={body}
|
||||
placeholder={this.props.bodyPlaceholder}
|
||||
onChange={this.props.onBodyChange}
|
||||
disabled={disableTextArea}
|
||||
charCountEnable={this.props.charCountEnable}
|
||||
maxCharCount={this.props.maxCharCount}
|
||||
/>
|
||||
<div className={`${name}-button-container`}>
|
||||
{this.props.buttonContainerStart}
|
||||
{typeof this.props.onCancel === 'function' && (
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import Slot from 'coral-framework/components/Slot';
|
||||
|
||||
// TODO: (kiwi) Need to adapt CSS classes post refactor to match the rest.
|
||||
|
||||
/**
|
||||
* An enhanced textarea to make comment drafts.
|
||||
*/
|
||||
export default class DraftArea extends React.Component {
|
||||
renderCharCount() {
|
||||
const { value, maxCharCount } = this.props;
|
||||
const className = cn('talk-plugin-commentbox-char-count', {
|
||||
['talk-plugin-commentbox-char-max']: value.length > maxCharCount,
|
||||
});
|
||||
const remaining = maxCharCount - value.length;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{remaining} {t('comment_box.characters_remaining')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
placeholder,
|
||||
id,
|
||||
disabled,
|
||||
rows,
|
||||
label,
|
||||
charCountEnable,
|
||||
maxCharCount,
|
||||
onChange,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={'talk-plugin-commentbox-container'}>
|
||||
<label htmlFor={id} className="screen-reader-text" aria-hidden={true}>
|
||||
{label}
|
||||
</label>
|
||||
<textarea
|
||||
className={'talk-plugin-commentbox-textarea'}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
id={id}
|
||||
onChange={onChange}
|
||||
rows={rows}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Slot fill="commentInputArea" />
|
||||
</div>
|
||||
{charCountEnable && maxCharCount > 0 && this.renderCharCount()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DraftArea.defaultProps = {
|
||||
rows: 3,
|
||||
};
|
||||
|
||||
DraftArea.propTypes = {
|
||||
charCountEnable: PropTypes.bool,
|
||||
maxCharCount: PropTypes.number,
|
||||
id: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
rows: PropTypes.number,
|
||||
};
|
||||
@@ -21,6 +21,7 @@ export class EditableCommentContent extends React.Component {
|
||||
|
||||
// comment that is being edited
|
||||
comment: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
body: PropTypes.string,
|
||||
editing: PropTypes.shape({
|
||||
edited: PropTypes.bool,
|
||||
@@ -120,10 +121,12 @@ export class EditableCommentContent extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const id = `edit-draft_${this.props.comment.id}`;
|
||||
return (
|
||||
<div className={styles.editCommentForm}>
|
||||
<CommentForm
|
||||
defaultValue={this.props.comment.body}
|
||||
bodyInputId={id}
|
||||
charCountEnable={this.props.charCountEnable}
|
||||
maxCharCount={this.props.maxCharCount}
|
||||
submitEnabled={this.isSubmitEnabled}
|
||||
|
||||
@@ -7,7 +7,8 @@ const name = 'talk-plugin-replies';
|
||||
|
||||
class ReplyBox extends Component {
|
||||
componentDidMount() {
|
||||
document.getElementById('replyText').focus();
|
||||
// TODO: (kiwi) This does not follow best practices, better to move this logic into the component.
|
||||
document.getElementById(`comment-draft_${this.props.parentId}`).focus();
|
||||
}
|
||||
|
||||
cancelReply = () => {
|
||||
@@ -54,6 +55,8 @@ ReplyBox.propTypes = {
|
||||
notify: PropTypes.func.isRequired,
|
||||
postComment: PropTypes.func.isRequired,
|
||||
assetId: PropTypes.string.isRequired,
|
||||
currentUser: PropTypes.object,
|
||||
styles: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ReplyBox;
|
||||
|
||||
@@ -20,7 +20,6 @@ class CommentBox extends React.Component {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
body: '',
|
||||
loadingState: '',
|
||||
|
||||
@@ -129,7 +128,7 @@ class CommentBox extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isReply, maxCharCount } = this.props;
|
||||
const { isReply, maxCharCount, assetId, parentId } = this.props;
|
||||
let { onCancel } = this.props;
|
||||
|
||||
if (isReply && typeof onCancel !== 'function') {
|
||||
@@ -139,6 +138,11 @@ class CommentBox extends React.Component {
|
||||
onCancel = () => {};
|
||||
}
|
||||
|
||||
// Generate id for the DraftArea.
|
||||
const id = parentId
|
||||
? `comment-draft_${parentId}`
|
||||
: `comment-draft_${assetId}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CommentForm
|
||||
@@ -147,7 +151,7 @@ class CommentBox extends React.Component {
|
||||
maxCharCount={maxCharCount}
|
||||
charCountEnable={this.props.charCountEnable}
|
||||
bodyPlaceholder={t('comment.comment')}
|
||||
bodyInputId={isReply ? 'replyText' : 'commentText'}
|
||||
bodyInputId={id}
|
||||
body={this.state.body}
|
||||
buttonContainerStart={
|
||||
<Slot
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import DraftArea from '../components/DraftArea';
|
||||
|
||||
const STORAGE_PATH = 'DraftArea';
|
||||
|
||||
/**
|
||||
* An enhanced textarea to make comment drafts.
|
||||
*/
|
||||
export default class DraftAreaContainer extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.initValue();
|
||||
}
|
||||
|
||||
async initValue() {
|
||||
const value = await this.context.pymSessionStorage.getItem(this.getPath());
|
||||
if (value && this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
getPath = () => {
|
||||
return `${STORAGE_PATH}_${this.props.id}`;
|
||||
};
|
||||
|
||||
onChange = e => {
|
||||
this.props.onChange && this.props.onChange(e.target.value);
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.value !== nextProps.value) {
|
||||
if (nextProps.value) {
|
||||
this.context.pymSessionStorage.setItem(this.getPath(), nextProps.value);
|
||||
} else {
|
||||
this.context.pymSessionStorage.removeItem(this.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DraftArea
|
||||
value={this.props.value}
|
||||
placeholder={this.props.placeholder}
|
||||
id={this.props.id}
|
||||
onChange={this.onChange}
|
||||
rows={this.props.rows}
|
||||
disabled={this.props.disabled}
|
||||
charCountEnable={this.props.charCountEnable}
|
||||
maxCharCount={this.props.maxCharCount}
|
||||
label={this.props.label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DraftAreaContainer.contextTypes = {
|
||||
// We use pymSessionStorage instead to persist the data directly on the parent page,
|
||||
// in order to mitigate strict cross domain security settings.
|
||||
pymSessionStorage: PropTypes.object,
|
||||
};
|
||||
|
||||
DraftAreaContainer.propTypes = {
|
||||
charCountEnable: PropTypes.bool,
|
||||
maxCharCount: PropTypes.number,
|
||||
id: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
rows: PropTypes.number,
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -146,8 +146,19 @@ export default class Stream {
|
||||
// If the user clicks outside the embed, then tell the embed.
|
||||
document.addEventListener('click', this.handleClick.bind(this), true);
|
||||
|
||||
// Listens to storage requests on pym and relay it to local storage.
|
||||
connectStorageToPym(createStorage(), this.pym);
|
||||
// Listens to local storage requests on pym and relay it to local storage.
|
||||
connectStorageToPym(
|
||||
createStorage('localStorage'),
|
||||
this.pym,
|
||||
'localStorage'
|
||||
);
|
||||
|
||||
// Listens to session storage requests on pym and relay it to session storage.
|
||||
connectStorageToPym(
|
||||
createStorage('sessionStorage'),
|
||||
this.pym,
|
||||
'sessionStorage'
|
||||
);
|
||||
}
|
||||
|
||||
login(token) {
|
||||
|
||||
@@ -11,9 +11,12 @@ class TalkProvider extends React.Component {
|
||||
rest: this.props.rest,
|
||||
graphql: this.props.graphql,
|
||||
notification: this.props.notification,
|
||||
storage: this.props.storage,
|
||||
localStorage: this.props.localStorage,
|
||||
sessionStorage: this.props.sessionStorage,
|
||||
history: this.props.history,
|
||||
store: this.props.store,
|
||||
pymLocalStorage: this.props.pymLocalStorage,
|
||||
pymSessionStorage: this.props.pymSessionStorage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,9 +33,14 @@ TalkProvider.childContextTypes = {
|
||||
rest: PropTypes.func,
|
||||
graphql: PropTypes.object,
|
||||
notification: PropTypes.object,
|
||||
storage: PropTypes.object,
|
||||
localStorage: PropTypes.object,
|
||||
sessionStorage: PropTypes.object,
|
||||
pymLocalStorage: PropTypes.object,
|
||||
pymSessionStorage: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
store: PropTypes.object,
|
||||
};
|
||||
|
||||
TalkProvider.propTypes = TalkProvider.childContextTypes;
|
||||
|
||||
export default TalkProvider;
|
||||
|
||||
+23
-5
@@ -43,6 +43,14 @@ const getAuthToken = (store, storage) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
function areWeInIframe() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createContext setups and returns Talk dependencies that should be
|
||||
* passed to `TalkProvider`.
|
||||
@@ -63,9 +71,16 @@ export async function createContext({
|
||||
preInit,
|
||||
init = noop,
|
||||
} = {}) {
|
||||
const inIframe = areWeInIframe();
|
||||
const eventEmitter = new EventEmitter({ wildcard: true });
|
||||
const storage = createStorage();
|
||||
const pymStorage = createPymStorage(pym);
|
||||
const localStorage = createStorage('localStorage');
|
||||
const sessionStorage = createStorage('sessionStorage');
|
||||
const pymLocalStorage = inIframe
|
||||
? createPymStorage(pym, 'localStorage')
|
||||
: localStorage;
|
||||
const pymSessionStorage = inIframe
|
||||
? createPymStorage(pym, 'sessionStorage')
|
||||
: sessionStorage;
|
||||
const history = createHistory(BASE_PATH);
|
||||
const introspection = createIntrospection(introspectionData);
|
||||
let store = null;
|
||||
@@ -75,7 +90,7 @@ export async function createContext({
|
||||
|
||||
// NOTE: THIS IS ONLY EVER EVALUATED ONCE, IN ORDER TO SEND A DIFFERNT
|
||||
// TOKEN YOU MUST DISCONNECT AND RECONNECT THE WEBSOCKET CLIENT.
|
||||
return getAuthToken(store, storage);
|
||||
return getAuthToken(store, localStorage);
|
||||
};
|
||||
|
||||
const rest = createRestClient({
|
||||
@@ -116,10 +131,13 @@ export async function createContext({
|
||||
rest,
|
||||
graphql,
|
||||
notification,
|
||||
storage,
|
||||
localStorage,
|
||||
sessionStorage,
|
||||
history,
|
||||
introspection,
|
||||
pymStorage,
|
||||
pymLocalStorage,
|
||||
pymSessionStorage,
|
||||
inIframe,
|
||||
};
|
||||
|
||||
// Load framework fragments.
|
||||
|
||||
@@ -68,7 +68,7 @@ function getLocale(storage) {
|
||||
|
||||
export function setupTranslations() {
|
||||
// Setup the translation framework with the storage.
|
||||
const storage = createStorage();
|
||||
const storage = createStorage('localStorage');
|
||||
|
||||
const locale = getLocale(storage);
|
||||
setLocale(storage, locale);
|
||||
|
||||
@@ -34,8 +34,8 @@ function getStorage(type) {
|
||||
* createStorage returns a localStorage wrapper if available
|
||||
* @return {Object} localStorage wrapper
|
||||
*/
|
||||
export function createStorage() {
|
||||
return getStorage('localStorage');
|
||||
export function createStorage(type = 'localStorage') {
|
||||
return getStorage(type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ export function createStorage() {
|
||||
* @param {string} pym pym
|
||||
* @return {Object} storage
|
||||
*/
|
||||
export function createPymStorage(pym) {
|
||||
export function createPymStorage(pym, type = 'localStorage') {
|
||||
// A Map of requestID => {resolve, reject}
|
||||
const requests = {};
|
||||
|
||||
@@ -54,21 +54,21 @@ export function createPymStorage(pym) {
|
||||
return new Promise((resolve, reject) => {
|
||||
requests[id] = { resolve, reject };
|
||||
pym.sendMessage(
|
||||
'pymStorage.request',
|
||||
`pymStorage.${type}.request`,
|
||||
JSON.stringify({ id, method, parameters })
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Receive successful responses.
|
||||
pym.onMessage('pymStorage.response', msg => {
|
||||
pym.onMessage(`pymStorage.${type}.response`, msg => {
|
||||
const { id, result } = JSON.parse(msg);
|
||||
requests[id].resolve(result);
|
||||
delete requests[id];
|
||||
});
|
||||
|
||||
// Receive error responses.
|
||||
pym.onMessage('pymStorage.error', msg => {
|
||||
pym.onMessage(`pymStorage.${type}.error`, msg => {
|
||||
const { id, error } = JSON.parse(msg);
|
||||
requests[id].reject(error);
|
||||
delete requests[id];
|
||||
@@ -88,8 +88,13 @@ export function createPymStorage(pym) {
|
||||
* @param {Object} pym pym to listen to storage requests
|
||||
* @param {string} prefix namespace requests by prepending a prefix to the keys
|
||||
*/
|
||||
export function connectStorageToPym(storage, pym, prefix = 'talkPymStorage:') {
|
||||
pym.onMessage('pymStorage.request', msg => {
|
||||
export function connectStorageToPym(
|
||||
storage,
|
||||
pym,
|
||||
type = 'localStorage',
|
||||
prefix = 'talkPymStorage:'
|
||||
) {
|
||||
pym.onMessage(`pymStorage.${type}.request`, msg => {
|
||||
const { id, method, parameters } = JSON.parse(msg);
|
||||
const { key, value } = parameters;
|
||||
const prefixedKey = `${prefix}${key}`;
|
||||
@@ -99,7 +104,10 @@ export function connectStorageToPym(storage, pym, prefix = 'talkPymStorage:') {
|
||||
|
||||
const sendError = error => {
|
||||
console.error(error);
|
||||
pym.sendMessage('pymStorage.error', JSON.stringify({ id, error }));
|
||||
pym.sendMessage(
|
||||
`pymStorage.${type}.error`,
|
||||
JSON.stringify({ id, error })
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -122,6 +130,9 @@ export function connectStorageToPym(storage, pym, prefix = 'talkPymStorage:') {
|
||||
return;
|
||||
}
|
||||
|
||||
pym.sendMessage('pymStorage.response', JSON.stringify({ id, result }));
|
||||
pym.sendMessage(
|
||||
`pymStorage.${type}.response`,
|
||||
JSON.stringify({ id, result })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
const STORAGE_PATH = 'talkPluginRememberSort';
|
||||
|
||||
export default {
|
||||
init: async ({ store, pymStorage, introspection }) => {
|
||||
init: async ({ store, pymLocalStorage, introspection }) => {
|
||||
// TODO: workaround as this plugin is included in any target and
|
||||
// embeds (e.g. admin), but should only be included inside the stream.
|
||||
|
||||
@@ -16,10 +16,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// We use pymStorage instead to persist the data directly on the parent page,
|
||||
// We use pymLocalStorage instead to persist the data directly on the parent page,
|
||||
// in order to mitigate strict cross domain security settings.
|
||||
|
||||
let sort = JSON.parse(await pymStorage.getItem(STORAGE_PATH));
|
||||
let sort = JSON.parse(await pymLocalStorage.getItem(STORAGE_PATH));
|
||||
if (
|
||||
sort &&
|
||||
introspection.isValidEnumValue('SORT_ORDER', sort.sortOrder) &&
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
// Save sorting choice to storage if it has changed.
|
||||
if (!sort || sort.sortOrder !== sortOrder || sort.sortBy !== sortBy) {
|
||||
sort = { sortOrder, sortBy };
|
||||
pymStorage.setItem(STORAGE_PATH, JSON.stringify(sort));
|
||||
pymLocalStorage.setItem(STORAGE_PATH, JSON.stringify(sort));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -97,7 +97,7 @@ module.exports = {
|
||||
elements: {
|
||||
logoutButton: '.talk-stream-userbox-logout',
|
||||
signInButton: '#coralSignInButton',
|
||||
commentBoxTextarea: '#commentText',
|
||||
commentBoxTextarea: '.talk-plugin-commentbox-textarea',
|
||||
commentBoxPostButton: '.talk-plugin-commentbox-button',
|
||||
firstComment: '.talk-stream-comment.talk-stream-comment-level-0',
|
||||
firstCommentContent:
|
||||
|
||||
Reference in New Issue
Block a user