mirror of
https://github.com/wassname/talk.git
synced 2026-08-01 13:00:55 +08:00
Merge branch 'master' into server-jest
This commit is contained in:
@@ -5,14 +5,7 @@ export const singleView = () => ({ type: actions.SINGLE_VIEW });
|
||||
|
||||
// hide shortcuts note
|
||||
export const hideShortcutsNote = () => (dispatch, _, { localStorage }) => {
|
||||
try {
|
||||
if (localStorage) {
|
||||
localStorage.setItem('coral:shortcutsNote', 'hide');
|
||||
}
|
||||
} catch (e) {
|
||||
// above will fail in Safari private mode
|
||||
}
|
||||
|
||||
localStorage.setItem('coral:shortcutsNote', 'hide');
|
||||
dispatch({ type: actions.HIDE_SHORTCUTS_NOTE });
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ import { hideShortcutsNote } from './actions/moderation';
|
||||
smoothscroll.polyfill();
|
||||
|
||||
function init({ store, localStorage }) {
|
||||
if (localStorage && localStorage.getItem('coral:shortcutsNote') === 'hide') {
|
||||
const shouldHide = localStorage.getItem('coral:shortcutsNote') === 'hide';
|
||||
if (shouldHide) {
|
||||
store.dispatch(hideShortcutsNote());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ export const checkLogin = () => (
|
||||
rest('/auth')
|
||||
.then(result => {
|
||||
if (!result.user) {
|
||||
if (localStorage) {
|
||||
cleanAuthData(localStorage);
|
||||
}
|
||||
cleanAuthData(localStorage);
|
||||
dispatch(checkLoginSuccess(null));
|
||||
return;
|
||||
}
|
||||
@@ -52,10 +50,8 @@ const checkLoginSuccess = user => ({
|
||||
});
|
||||
|
||||
export const setAuthToken = token => (dispatch, _, { localStorage }) => {
|
||||
if (localStorage) {
|
||||
localStorage.setItem('exp', jwtDecode(token).exp);
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
localStorage.setItem('exp', jwtDecode(token).exp);
|
||||
localStorage.setItem('token', token);
|
||||
|
||||
// Dispatch the set auth token action. For some browsers and situations, we
|
||||
// may not be able to persist the auth token any other way. Keep it in redux!
|
||||
@@ -70,11 +66,8 @@ export const handleSuccessfulLogin = (user, token) => (
|
||||
{ client, localStorage, postMessage }
|
||||
) => {
|
||||
const { exp } = jwtDecode(token);
|
||||
|
||||
if (localStorage) {
|
||||
localStorage.setItem('exp', exp);
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
localStorage.setItem('exp', exp);
|
||||
localStorage.setItem('token', token);
|
||||
|
||||
// Send the message via the messages service to the window.opener if it
|
||||
// exists.
|
||||
@@ -105,9 +98,8 @@ export const logout = () => async (
|
||||
) => {
|
||||
await rest('/auth', { method: 'DELETE' });
|
||||
|
||||
if (localStorage) {
|
||||
cleanAuthData(localStorage);
|
||||
}
|
||||
// Clear the auth data persisted to localStorage.
|
||||
cleanAuthData(localStorage);
|
||||
|
||||
// Reset the websocket.
|
||||
client.resetWebsocket();
|
||||
|
||||
@@ -52,26 +52,41 @@ let lang;
|
||||
let timeagoInstance;
|
||||
|
||||
function setLocale(storage, locale) {
|
||||
try {
|
||||
if (storage) {
|
||||
storage.setItem('locale', locale);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
storage.setItem('locale', locale);
|
||||
}
|
||||
|
||||
function getLocale(storage) {
|
||||
// detectLanguage will try to get the locale from storage if available,
|
||||
// otherwise will try to get it from the navigator, otherwise, it will fallback
|
||||
// to the default language.
|
||||
function detectLanguage(storage) {
|
||||
try {
|
||||
return (
|
||||
(storage && storage.getItem('locale')) ||
|
||||
navigator.language ||
|
||||
defaultLanguage
|
||||
).split('-')[0];
|
||||
const lang = storage.getItem('locale') || navigator.language;
|
||||
if (lang) {
|
||||
return lang;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
console.warn(
|
||||
'Error while trying to detect language, will fallback to',
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
console.warn('Could not detect language, will fallback to', defaultLanguage);
|
||||
return defaultLanguage;
|
||||
}
|
||||
|
||||
// getLocale will get the users locale from the local detector and parse it to a
|
||||
// format we can work with.
|
||||
function getLocale(storage) {
|
||||
// Get the language from the local detector.
|
||||
const lang = detectLanguage(storage);
|
||||
|
||||
// Some language strings come with additional subtags as defined in:
|
||||
//
|
||||
// https://www.ietf.org/rfc/bcp/bcp47.txt
|
||||
//
|
||||
// So we should strip that off if we find it.
|
||||
return lang.split('-')[0];
|
||||
}
|
||||
|
||||
export function setupTranslations() {
|
||||
|
||||
@@ -1,40 +1,116 @@
|
||||
import uuid from 'uuid/v4';
|
||||
|
||||
function getStorage(type) {
|
||||
let storage;
|
||||
try {
|
||||
storage = window[type];
|
||||
const x = '__storage_test__';
|
||||
storage.setItem(x, x);
|
||||
storage.removeItem(x);
|
||||
} catch (e) {
|
||||
const ignore =
|
||||
e instanceof DOMException &&
|
||||
// everything except Firefox
|
||||
(e.code === 22 ||
|
||||
// SecurityError related to having 3rd party cookies disabled.
|
||||
e.code === 18 ||
|
||||
// Firefox
|
||||
function testStorageAccess(storage) {
|
||||
const key = '__storage_test__';
|
||||
|
||||
e.code === 1014 ||
|
||||
// test name field too, because code might not be present
|
||||
// Create a unique test value.
|
||||
const expectedValue = String(Date.now());
|
||||
|
||||
// everything except Firefox
|
||||
e.name === 'QuotaExceededError' ||
|
||||
// Firefox
|
||||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED');
|
||||
if (!ignore) {
|
||||
console.warn(e);
|
||||
// Try to set, get, and remove that item.
|
||||
storage.setItem(key, expectedValue);
|
||||
const canSetGet = expectedValue === storage.getItem(key);
|
||||
storage.removeItem(key);
|
||||
|
||||
if (!canSetGet) {
|
||||
// We can't access the desired storage!
|
||||
throw new Error('Storage access test failed');
|
||||
}
|
||||
}
|
||||
|
||||
// InMemoryStorage is a dumb implementation of the Storage interface that will
|
||||
// not persist the data at all. It implements the Storage interface found:
|
||||
//
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Storage
|
||||
//
|
||||
class InMemoryStorage {
|
||||
constructor() {
|
||||
this.storage = {};
|
||||
}
|
||||
|
||||
get length() {
|
||||
return Object.keys(this.storage).length;
|
||||
}
|
||||
|
||||
key(n) {
|
||||
if (this.length <= n) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// When third party cookies are disabled, session storage is readable/
|
||||
// writable, but localStorage is not. Try to get the sessionStorage to use.
|
||||
if (type !== 'sessionStorage') {
|
||||
return getStorage('sessionStorage');
|
||||
return this.storage[Object.keys(this.storage)[n]];
|
||||
}
|
||||
|
||||
getItem(key) {
|
||||
return this.storage[key];
|
||||
}
|
||||
|
||||
setItem(key, value) {
|
||||
this.storage[key] = value;
|
||||
|
||||
try {
|
||||
// Test sessionStorage. We could have been given access recently.
|
||||
testStorageAccess(sessionStorage);
|
||||
|
||||
// Test passed! Set the item in sessionStorage.
|
||||
sessionStorage.setItem(key, value);
|
||||
console.log(
|
||||
'Attempt to persist InMemoryStorage value to sessionStorage succeeded'
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Attempt to persist InMemoryStorage value to sessionStorage failed',
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return storage;
|
||||
removeItem(key) {
|
||||
delete this.storage[key];
|
||||
|
||||
try {
|
||||
// Test sessionStorage. We could have been given access recently.
|
||||
testStorageAccess(sessionStorage);
|
||||
|
||||
// Test passed! Remove the item from sessionStorage.
|
||||
sessionStorage.removeItem(key);
|
||||
console.log(
|
||||
'Attempt to persist InMemoryStorage delete to sessionStorage succeeded'
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Attempt to persist InMemoryStorage delete to sessionStorage failed',
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getStorage will test to see if the requested storage type is available, if it
|
||||
// is not, it will try sessionStorage, and if that is also not available, it
|
||||
// will fallback to InMemoryStorage.
|
||||
function getStorage(type) {
|
||||
try {
|
||||
// Get the desired storage from the window and test it out.
|
||||
const storage = window[type];
|
||||
testStorageAccess(storage);
|
||||
|
||||
// Storage test was successful! Return it.
|
||||
return storage;
|
||||
} catch (err) {
|
||||
// When third party cookies are disabled, session storage is readable/
|
||||
// writable, but localStorage is not. Try to get the sessionStorage to use.
|
||||
if (type !== 'sessionStorage') {
|
||||
console.warn('Could not access', type, 'trying sessionStorage', err);
|
||||
return getStorage('sessionStorage');
|
||||
}
|
||||
|
||||
console.warn(
|
||||
'Could not access sessionStorage falling back to InMemoryStorage',
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
// No acceptable storage could be found, returning the InMemoryStorage.
|
||||
return new InMemoryStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,11 +27,14 @@ const TextField = ({
|
||||
);
|
||||
|
||||
TextField.propTypes = {
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
errorMsg: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
showErrors: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default TextField;
|
||||
|
||||
@@ -29,9 +29,9 @@ const User = {
|
||||
return Comments.getByQuery(query);
|
||||
},
|
||||
|
||||
ignoredUsers({ ignoresUsers }, args, { user, loaders: { Users } }) {
|
||||
ignoredUsers({ ignoresUsers }, args, { loaders: { Users } }) {
|
||||
// Return nothing if there is nothing to query for.
|
||||
if (!user.ignoresUsers || user.ignoresUsers.length <= 0) {
|
||||
if (!ignoresUsers || ignoresUsers.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "talk",
|
||||
"version": "4.3.1",
|
||||
"version": "4.3.2",
|
||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||
"main": "app.js",
|
||||
"private": true,
|
||||
|
||||
@@ -75,6 +75,7 @@ class SignUp extends React.Component {
|
||||
showErrors={!!emailError}
|
||||
errorMsg={emailError}
|
||||
onChange={this.handleEmailChange}
|
||||
autocomplete="off"
|
||||
/>
|
||||
<TextField
|
||||
id="username"
|
||||
@@ -85,6 +86,8 @@ class SignUp extends React.Component {
|
||||
showErrors={!!usernameError}
|
||||
errorMsg={usernameError}
|
||||
onChange={this.handleUsernameChange}
|
||||
autocomplete="off"
|
||||
autocapitalize="none"
|
||||
/>
|
||||
<TextField
|
||||
id="password"
|
||||
@@ -96,6 +99,7 @@ class SignUp extends React.Component {
|
||||
errorMsg={passwordError}
|
||||
onChange={this.handlePasswordChange}
|
||||
minLength="8"
|
||||
autocomplete="off"
|
||||
/>
|
||||
{passwordError && (
|
||||
<span className={styles.hint}>
|
||||
@@ -113,6 +117,7 @@ class SignUp extends React.Component {
|
||||
errorMsg={passwordRepeatError}
|
||||
onChange={this.handlePasswordRepeatChange}
|
||||
minLength="8"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<Slot
|
||||
fill="talkPluginAuth.formField"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { get } = require('lodash');
|
||||
const { get, map } = require('lodash');
|
||||
const path = require('path');
|
||||
|
||||
const handle = async (ctx, comment) => {
|
||||
@@ -23,6 +23,9 @@ const handle = async (ctx, comment) => {
|
||||
id
|
||||
user {
|
||||
id
|
||||
ignoredUsers {
|
||||
id
|
||||
}
|
||||
notificationSettings {
|
||||
onReply
|
||||
}
|
||||
@@ -53,13 +56,23 @@ const handle = async (ctx, comment) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pull out the author of the new comment.
|
||||
const authorID = get(comment, 'author_id');
|
||||
|
||||
// Check to see if this is yourself replying to yourself, if that's the case
|
||||
// don't send a notification.
|
||||
if (userID === get(comment, 'author_id')) {
|
||||
if (userID === authorID) {
|
||||
ctx.log.info('user id of parent comment is the same as the new comment');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if this user is ignoring the user who replied to their
|
||||
// comment.
|
||||
if (map(get(comment, 'user.ignoredUsers', []), 'id').indexOf(authorID)) {
|
||||
ctx.log.info('parent user has ignored the author of the new comment');
|
||||
return;
|
||||
}
|
||||
|
||||
// The user does have notifications for replied comments enabled, queue the
|
||||
// notification to be sent.
|
||||
return { userID, date: comment.created_at, context: comment.id };
|
||||
|
||||
Reference in New Issue
Block a user