diff --git a/.nsprc b/.nsprc index 530a17a9f..8962db719 100644 --- a/.nsprc +++ b/.nsprc @@ -7,6 +7,7 @@ "https://nodesecurity.io/advisories/594", "https://nodesecurity.io/advisories/603", "https://nodesecurity.io/advisories/611", - "https://nodesecurity.io/advisories/612" + "https://nodesecurity.io/advisories/612", + "https://nodesecurity.io/advisories/654" ] } diff --git a/app.js b/app.js index c5507da2e..b3400b939 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,6 @@ const express = require('express'); +const nunjucks = require('nunjucks'); +const cons = require('consolidate'); const trace = require('./middleware/trace'); const logging = require('./middleware/logging'); const path = require('path'); @@ -72,7 +74,26 @@ app.use( // VIEW CONFIGURATION //============================================================================== -app.set('views', path.join(__dirname, 'views')); +// configure the default views directory. +const views = path.join(__dirname, 'views'); +app.set('views', views); + +// reconfigure nunjucks. +cons.requires.nunjucks = nunjucks.configure(views, { + autoescape: true, + trimBlocks: true, + lstripBlocks: true, + watch: process.env.NODE_ENV === 'development', +}); + +// assign the nunjucks engine to .njk files. +app.engine('njk', cons.nunjucks); + +// assign the ejs engine to .ejs and .html files. +app.engine('ejs', cons.ejs); +app.engine('html', cons.ejs); + +// set .ejs as the default extension. app.set('view engine', 'ejs'); //============================================================================== diff --git a/client/coral-admin/src/components/External.css b/client/coral-admin/src/components/External.css new file mode 100644 index 000000000..ff23c53cc --- /dev/null +++ b/client/coral-admin/src/components/External.css @@ -0,0 +1,16 @@ +.external { + margin-bottom: 20px; +} + +.separator h5 { + text-align: center; + font-size: 1.2em; +} + +.slot > * { +margin-bottom: 8px; + +&:last-child { + margin-bottom: 0px; +} +} diff --git a/client/coral-admin/src/components/External.js b/client/coral-admin/src/components/External.js new file mode 100644 index 000000000..0f133d3b9 --- /dev/null +++ b/client/coral-admin/src/components/External.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './External.css'; +import Slot from 'coral-framework/components/Slot'; +import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty'; + +const External = ({ slot }) => ( + +
+
+ +
+
+
Or
+
+
+
+); + +External.propTypes = { + slot: PropTypes.string.isRequired, +}; + +export default External; diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js index 5e1de2033..a067ace1d 100644 --- a/client/coral-admin/src/components/SignIn.js +++ b/client/coral-admin/src/components/SignIn.js @@ -4,6 +4,7 @@ import styles from './SignIn.css'; import { Button, TextField, Alert } from 'coral-ui'; import cn from 'classnames'; import Recaptcha from 'coral-framework/components/Recaptcha'; +import External from './External'; class SignIn extends React.Component { recaptcha = null; @@ -33,48 +34,55 @@ class SignIn extends React.Component { render() { const { email, password, errorMessage, requireRecaptcha } = this.props; return ( -
- {errorMessage && {errorMessage}} - - - {requireRecaptcha && ( -
- -
- )} - -

- Forgot your password?{' '} - + + + {errorMessage && {errorMessage}} + + + {requireRecaptcha && ( +

+ +
+ )} + +

+ {/* TODO: translate */} + Forgot your password?{' '} + + Request a new one. + +

+ + ); } } diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index 58faee5c4..389971b32 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -143,3 +143,7 @@ border-color: #E45241; color: white; } + +.userDetailItem { + padding: 2px 0; +} diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index c2dd3cf55..d3a90fb37 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -234,7 +234,7 @@ class UserDetail extends React.Component {
    -
  • +
  • {t('user_detail.member_since')}: @@ -242,11 +242,24 @@ class UserDetail extends React.Component { {new Date(user.created_at).toLocaleString()}
  • - {user.profiles.map(({ id }) => ( -
  • - +
  • + + + {t('user_detail.email')}: + + {user.email}{' '} + +
  • + + {user.profiles.map(({ provider, id }) => ( +
  • + - {t('user_detail.email')}: + {capitalize(provider)} {t('user_detail.id')}: {id}{' '} - {user.profiles.map(({ id }) => id)} + {user.email + ? user.email + : user.profiles.map(p => p.id).join(', ')} diff --git a/client/coral-auth-callback/src/index.js b/client/coral-auth-callback/src/index.js index 41a83dcd2..7bf75e825 100644 --- a/client/coral-auth-callback/src/index.js +++ b/client/coral-auth-callback/src/index.js @@ -3,32 +3,36 @@ import { getStaticConfiguration } from 'coral-framework/services/staticConfigura import { createPostMessage } from 'coral-framework/services/postMessage'; document.addEventListener('DOMContentLoaded', () => { - try { - const staticConfig = getStaticConfiguration(); - const { STATIC_ORIGIN: origin } = staticConfig; - const postMessage = createPostMessage(origin); + const staticConfig = getStaticConfiguration(); + const { STATIC_ORIGIN: origin } = staticConfig; + const postMessage = createPostMessage(origin); - // Get the auth element and parse it as JSON by decoding it. - const auth = document.getElementById('auth'); - const doc = document.implementation.createHTMLDocument(''); - doc.body.innerHTML = auth.innerText; + // Get the auth element and parse it as JSON by decoding it. + const auth = document.getElementById('auth'); + const doc = document.implementation.createHTMLDocument(''); + doc.body.innerHTML = auth.innerText; - // Auth state is contained within the node. - const { err, data } = JSON.parse(doc.body.textContent); - if (err) { - // TODO: send back the error message. - console.error(err); + // Auth state is contained within the node. + const { err, data } = JSON.parse(doc.body.textContent); + if (err) { + const errDiv = document.createElement('div'); + if (err.message) { + errDiv.innerText = `${err.name}: ${err.message}`; } else { - // The data will contain a user and a token. - const { user, token } = data; - - // Send the state back. - postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token }); + errDiv.innerText = JSON.stringify(err); } - } finally { - // Always close the window. - setTimeout(() => { - window.close(); - }, 50); + document.body.appendChild(errDiv); + throw err; } + + // The data will contain a user and a token. + const { user, token } = data; + + // Send the state back. + postMessage.post(HANDLE_SUCCESSFUL_LOGIN, { user, token }); + + // Close the window when all went well. + setTimeout(() => { + window.close(); + }, 50); }); diff --git a/client/coral-embed-stream/src/tabs/stream/components/Comment.js b/client/coral-embed-stream/src/tabs/stream/components/Comment.js index 32778f7ea..63740764f 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Comment.js @@ -745,10 +745,21 @@ export default class Comment extends React.Component { const id = `c_${comment.id}`; + // props that are passed down the slots. + const slotPassthrough = { + action: 'deleted', + comment, + }; + return (
    {isCommentDeleted(comment) ? ( - + ) : (
    {this.renderComment()} diff --git a/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js b/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js index 95184dffd..e08f45d95 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js +++ b/client/coral-embed-stream/src/tabs/stream/components/CommentTombstone.js @@ -39,6 +39,7 @@ class CommentTombstone extends React.Component { CommentTombstone.propTypes = { action: PropTypes.string, + comment: PropTypes.object, onUndo: PropTypes.func, }; diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Comment.js b/client/coral-embed-stream/src/tabs/stream/containers/Comment.js index dca7fadaa..028d6a478 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Comment.js @@ -24,6 +24,7 @@ const slots = [ 'commentAuthorName', 'commentAuthorTags', 'commentTimestamp', + 'commentTombstone', 'commentContent', ]; diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 7b9eee141..b147fc354 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -116,7 +116,7 @@ export const withRemoveTag = withMutation( asset_id: assetId, item_type: itemType, }, - o3timisticResponse: { + optimisticResponse: { removeTag: { __typename: 'ModifyTagResponse', errors: null, diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js index 03a0b4712..02258a585 100644 --- a/client/coral-framework/services/i18n.js +++ b/client/coral-framework/services/i18n.js @@ -1,7 +1,10 @@ -import ta from 'timeago.js'; +import { negotiateLanguages } from 'fluent-langneg/compat'; + import has from 'lodash/has'; import get from 'lodash/get'; import merge from 'lodash/merge'; +import first from 'lodash/first'; +import isUndefined from 'lodash/isUndefined'; import moment from 'moment'; import 'moment/locale/ar'; @@ -12,8 +15,8 @@ import 'moment/locale/fr'; import 'moment/locale/nl'; import 'moment/locale/pt-br'; -import { createStorage } from 'coral-framework/services/storage'; - +// timeago +import ta from 'timeago.js'; import arTA from 'timeago.js/locales/ar'; import daTA from 'timeago.js/locales/da'; import deTA from 'timeago.js/locales/de'; @@ -24,6 +27,7 @@ import pt_BRTA from 'timeago.js/locales/pt_BR'; import zh_CNTA from 'timeago.js/locales/zh_CN'; import zh_TWTA from 'timeago.js/locales/zh_TW'; +// locales import ar from '../../../locales/ar.yml'; import en from '../../../locales/en.yml'; import da from '../../../locales/da.yml'; @@ -35,8 +39,9 @@ import pt_BR from '../../../locales/pt_BR.yml'; import zh_CN from '../../../locales/zh_CN.yml'; import zh_TW from '../../../locales/zh_TW.yml'; -const defaultLanguage = process.env.TALK_DEFAULT_LANG; -const translations = { +export const defaultLocale = process.env.TALK_DEFAULT_LANG.replace(/-/g, '_'); + +export const translations = { ...ar, ...en, ...da, @@ -49,84 +54,62 @@ const translations = { ...zh_TW, }; -let lang; -let timeagoInstance; +export const supportedLocales = Object.keys(translations); -function setLocale(storage, locale) { - storage.setItem('locale', locale); -} +let LOCALE; +let TIMEAGO_INSTANCE; // 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 { - const lang = storage.getItem('locale') || navigator.language; - if (lang) { - return lang; - } - } catch (err) { - 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]; -} +const detectLanguage = () => + first( + negotiateLanguages(navigator.languages, supportedLocales, { + defaultLocale, + strategy: 'lookup', + }) + ); export function setupTranslations() { - // Setup the translation framework with the storage. - const storage = createStorage('localStorage'); + // locale + LOCALE = detectLanguage(); - const locale = getLocale(storage); - setLocale(storage, locale); - - // Setting moment - moment.locale(locale); - - // Extract language key. - lang = locale.split('-')[0]; - - // Check if we have a translation in this language. - if (!(lang in translations)) { - lang = defaultLanguage; - } + // moment + moment.locale(LOCALE); + // timeago ta.register('ar', arTA); ta.register('es', esTA); ta.register('da', daTA); ta.register('de', deTA); ta.register('fr', frTA); - ta.register('nl_NL', nlTA); - ta.register('pt_BR', pt_BRTA); - ta.register('zh_CN', zh_CNTA); - ta.register('zh_TW', zh_TWTA); - - timeagoInstance = ta(); + ta.register('nl-NL', nlTA); + ta.register('pt-BR', pt_BRTA); + ta.register('zh-CN', zh_CNTA); + ta.register('zh-TW', zh_TWTA); + TIMEAGO_INSTANCE = ta(); } +/** + * loadTranslations will load the new language pack into the existing ones. + * + * @param {Object} newTranslations translation object to merge into the existing + * languages. + */ export function loadTranslations(newTranslations) { + // Merge the new translations into the existing translations. merge(translations, newTranslations); + + // Push new languages into the supportedLocales array. + Object.keys(newTranslations).forEach(language => { + if (!supportedLocales.includes(language)) { + supportedLocales.push(language); + } + }); } export function timeago(time) { - return timeagoInstance.format(new Date(time), lang); + return TIMEAGO_INSTANCE.format(new Date(time), LOCALE); } /** @@ -140,24 +123,24 @@ export function timeago(time) { */ export function t(key, ...replacements) { let translation; - if (has(translations[lang], key)) { - translation = get(translations[lang], key); + if (has(translations[LOCALE], key)) { + translation = get(translations[LOCALE], key); } else if (has(translations['en'], key)) { translation = get(translations['en'], key); - console.warn(`${lang}.${key} language key not set`); + console.warn(`${LOCALE}.${key} language key not set`); } - if (translation) { - // replace any {n} with the arguments passed to this method - replacements.forEach((str, i) => { - translation = translation.replace(new RegExp(`\\{${i}\\}`, 'g'), str); - }); - - return translation; - } else { - console.warn(`${lang}.${key} and en.${key} language key not set`); + if (!translation) { + console.warn(`${LOCALE}.${key} and en.${key} language key not set`); return key; } + + // Handle replacements in the translation string. + return translation.replace( + /{(\d+)}/g, + (match, number) => + !isUndefined(replacements[number]) ? replacements[number] : match + ); } export default t; diff --git a/client/coral-framework/services/postMessage.js b/client/coral-framework/services/postMessage.js index ef9311e64..6bc18eccf 100644 --- a/client/coral-framework/services/postMessage.js +++ b/client/coral-framework/services/postMessage.js @@ -56,10 +56,10 @@ export function createPostMessage(origin, scope = 'client') { // Send the message. target.postMessage(msg, origin); }, - subscribe: (handler, target = window) => { + subscribe(handler, target = window) { // If this handler is already attached to the target, detach it. if (has(listeners, [target, handler])) { - this.unsubscribeFromMessages(handler, target); + this.unsubscribe(handler, target); } // Wrap the listener with a origin check. @@ -71,7 +71,7 @@ export function createPostMessage(origin, scope = 'client') { // Attach the listener to the target. target.addEventListener('message', listener); }, - unsubscribe: (handler, target = window) => { + unsubscribe(handler, target = window) { if (!has(listeners, [target, handler])) { return; } diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index e30c12e06..6351c4484 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -273,3 +273,23 @@ export function translateError(error) { } return error.toString(); } + +/** + * handlePopupAuth will optionally open a popup with the requested uri if the + * window is not already a popup. + * + * @param {String} uri the url to open the window? to + * @param {String} title the title of the new window? to open + * @param {String} features the features to use when opening a window? + */ +export function handlePopupAuth( + uri, + title = 'Login', // TODO: translate + features = 'menubar=0,resizable=0,width=500,height=550,top=200,left=500' +) { + if (window.opener) { + window.location = uri; + } else { + window.open(uri, title, features); + } +} diff --git a/config.js b/config.js index 6a1f999a7..77ed4ab2b 100644 --- a/config.js +++ b/config.js @@ -48,6 +48,10 @@ const CONFIG = { // request all of the records. Otherwise, minimum limits of 0 are enforced. ALLOW_NO_LIMIT_QUERIES: process.env.TALK_ALLOW_NO_LIMIT_QUERIES === 'TRUE', + // ENABLE_STRICT_CSP enables strict CSP enforcement, and will enforce as well + // as report CSP violations. + ENABLE_STRICT_CSP: process.env.TALK_ENABLE_STRICT_CSP === 'TRUE', + // LOGGING_LEVEL specifies the logging level used by the bunyan logger. LOGGING_LEVEL: ['fatal', 'error', 'warn', 'info', 'debug', 'trace'].includes( process.env.TALK_LOGGING_LEVEL diff --git a/docs/source/02-02-advanced-configuration.md b/docs/source/02-02-advanced-configuration.md index 5b638401d..c4f86dbb8 100644 --- a/docs/source/02-02-advanced-configuration.md +++ b/docs/source/02-02-advanced-configuration.md @@ -497,6 +497,14 @@ tracing of GraphQL requests. **Note: Apollo Engine is a premium service, charges may apply.** +## TALK_ENABLE_STRICT_CSP + +Setting this to `TRUE` will enforce the [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) +(or CSP). By default, this configuration is set to +[report only](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP#Testing_your_policy) +where the policy is not enforced, but any violations are reported to a provided +URI. (Default `FALSE`) + ## ALLOW_NO_LIMIT_QUERIES Setting this to `TRUE` will allow queries to execute without a limit (returns diff --git a/docs/source/03-08-gdpr.md b/docs/source/03-08-gdpr.md index b6689be0e..e3fd8623f 100644 --- a/docs/source/03-08-gdpr.md +++ b/docs/source/03-08-gdpr.md @@ -11,9 +11,9 @@ can enable the following plugins: - [talk-plugin-local-auth](/talk/plugin/talk-plugin-local-auth) - to facilitate email changes and email association - [talk-plugin-profile-data](/talk/plugin/talk-plugin-profile-data) - to facilitate account download and deletion -Even if you don't reside in a location where GDPR will apply, it is recommended -to enable these features as a best practice to provide your users with control over their -own data. +Even if GDPR will not apply to you, it is recommended to enable these +features as a best practice to provide your users with control over their own +data. ## GPDR Feature Overview diff --git a/docs/source/api/client.md b/docs/source/api/client.md index fd0ec42fc..e5b46a0d4 100644 --- a/docs/source/api/client.md +++ b/docs/source/api/client.md @@ -264,7 +264,7 @@ Coral UI is a set of components to help you build your UI. This powers our core. ### Import ```js -import {Button} 'plugin-api/beta/components/ui'; +import {Button} from 'plugin-api/beta/components/ui'; ``` ### Components diff --git a/docs/source/api/slots.md b/docs/source/api/slots.md index a2e7cc04d..662a025e0 100644 --- a/docs/source/api/slots.md +++ b/docs/source/api/slots.md @@ -99,6 +99,7 @@ You won't have to use this to build plugins, but it's helpful to find where to e * `commentReactions` * `commentActions` * `commentInputArea` +* `commentTombstone` * `draftArea` * `streamSettings` diff --git a/docs/source/integrating/authentication.md b/docs/source/integrating/authentication.md index b7340caf5..5b19768fe 100644 --- a/docs/source/integrating/authentication.md +++ b/docs/source/integrating/authentication.md @@ -81,5 +81,5 @@ example issuer and Talk must match: reference, the basic takeaway is that the secret used to sign the tokens issued by the issuer must be able to be verified by Talk. -For an example of implementing the plugin, refer to [`tokenUserNotFound`](/talk/reference/server/#tokenUserNotFound) +For an example of implementing the plugin, refer to [`tokenUserNotFound`](/talk/api/server/#tokenusernotfound) reference. diff --git a/locales/de.yml b/locales/de.yml index 15404856c..bec73fa5d 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -20,10 +20,13 @@ de: bio_offensive: "Diese Biographie ist unangemessen" cancel: "Abbrechen" confirm_email: + email_confirmation: "E-Mail-Bestätigung" click_to_confirm: "Unten klicken, um E-Mail-Adresse zu bestätigen" confirm: "Bestätigen" password_reset: + mail_sent: 'Falls Sie eine registriertes Konto haben, wurde Ihnen ein Zurücksetzen-Link an diese E-Mail-Adresse geschickt' set_new_password: "Passwort ändern" + change_password_help: "Bitte geben Sie ein neues Passwort ein. Benutzen Sie ein sicheres!" new_password: "Neues Passwort" new_password_help: "Das Passwort benötigt mindestens 8 Zeichen" confirm_new_password: "Neues Passwort bestätigen" @@ -120,11 +123,12 @@ de: custom_css_url: "Benutzerdefinierte CSS-URL" custom_css_url_desc: "URL eines CSS-Stylesheets zum Überschreiben des Standard-Designs" days: Tage - description: "Als Administrator können Sie die Einstellungen für den Kommentarbereich dieses Artikels anpassen:" + description: "Ändern Sie die Einstellungen für den Kommentarbereich dieses Artikels." disable_commenting_title: "Kommentieren global deaktivieren" disable_commenting_desc: "Verfassen Sie eine Nachricht, die angezeigt wird, solange das Kommentieren deaktiviert ist." domain_list_text: "Geben Sie Domains an, für die diese Talk-Instanz freigegeben werden soll, z.B. für lokale Test- oder Produktionsumgebungen (Bsp.: localhost:3000 staging.domain.com domain.com)." domain_list_title: "Zugelassene Domains" + edit_info: "Information bearbeiten" edit_comment_timeframe_heading: "Zeitlimit zur Bearbeitung von Kommentaren" edit_comment_timeframe_text_pre: "Kommentatoren haben" edit_comment_timeframe_text_post: "Sekunden Zeit, um ihre Kommentare zu bearbeiten." @@ -150,17 +154,31 @@ de: open_stream_configuration: "Dieser Kommentarbereich ist momentan geöffnet. Nach dem Schließen dieses Kommentarbereich wird es nicht mehr möglich sein, zu kommentieren. Bestehende Kommentare bleiben sichtbar." require_email_verification: "E-Mail-Bestätigung erforderlich" require_email_verification_text: "Neue Nutzer müssen ihre E-Mail-Adresse bestätigen." + save: "Speichern" save_changes: "Änderungen speichern" shortcuts: Tastaturkürzel sign_out: "Abmelden" stories: Artikel stream_settings: "Einstellungen Kommentarbereich" + access_message: "Sie müssen Administrator sein, um auf die Einstellungen zuzugreifen. Fragen Sie ggf. einen Administrator, der Ihnen mehr Recht zuweisen kann!" suspect_word_title: "Liste verdächtiger Wörter" suspect_word_text: "Kommentare, die diese Wörter oder Phrasen enthalten (unabhängig von Groß-/Kleinschreibung), werden im Kommentarbereich markiert. Geben Sie ein Wort ein und bestätigen Sie mit Eingabetaste oder Tab. Es ist auch möglich, einen komma-separierten Text einzufügen." tech_settings: "Technische Einstellungen" + organization_information: "Über die Organisation" + organization_info_copy: "Wir verwenden diese Informationen in automatisierten E-Mail-Benachrichtigungen, die Talk versendet. Damit können Nutzer Ihre Organisation identifizieren und sie haben die Möglichkeit bei Problemen in Kontakt mit Ihnen zu treten." + organization_info_copy_2: "Wir empfehlen, einee generische E-Mail-Adresse (z.B. community@yournewsroom.com) für diesen Zweck einzurichten. Die kann über die Zeit gleich bleiben, und gibt nach außen keine Namen preis, die von Nutzern im Fall von Konflikten für persönliche Angriffe missbraucht werden könnten." + organization_details: "Details zur Organisation" + organization_name: "Name der Organisation" + organization_contact_email: "E-Mail-Adresse der Organisation" title: "Kommentarbereich konfigurieren" weeks: Wochen wordlist: "Gesperrte Wörter" + save_changes_dialog: + unsaved_changes: "Ungespeicherte Änderungen" + copy: "Sie haben einen oder mehrere Änderungen vorgenommen, ohne zu speichern. Möchten Sie jetzt speichern oder die Änderungen verwerfen?" + save_settings: "Einstellungen speichern" + discard: "Verwerfen" + cancel: "Abbrechen" continue: "Fortfahren" createdisplay: check_the_form: "Ungültige Eingabe. Bitte prüfen Sie die Felder." @@ -202,11 +220,17 @@ de: we_received_a_request: "Wir haben eine Anfrage erhalten, Ihr Passwort zurückzusetzen. Sollten Sie dies nicht angefordert haben, können Sie diese Nachricht ignorieren." if_you_did: "Falls doch," please_click: "klicken Sie bitte hier zum Zurücksetzen" + subject: "Passwort zurücksetzen" + password_change: + subject: "{0} Passwort-Änderung" + body: "Das Passwort Ihres Benutzerkontos wurde geändert.\n\nFalls Sie diese Änderung nicht angefordert haben, kontaktieren Sie uns bitte unter {0}." embedlink: copy: "In die Zwischenablage kopieren" error: + PASSWORD_INCORRECT: "Ihr bestehendes Passwort wurde falsch eingegeben" COMMENT_PARENT_NOT_VISIBLE: "Der Kommentar, auf den Sie antworten möchten, wurde entfernt oder existiert nicht." EMAIL_VERIFICATION_TOKEN_INVALID: "Code zur E-Mail-Bestätigung ist ungültig." + EMAIL_ALREADY_VERIFIED: "E-Mail-Adresse ist bereits bestätigt." PASSWORD_RESET_TOKEN_INVALID: "Ihr Link zum Passwort zurücksetzen ist ungültig." COMMENT_TOO_SHORT: "Kommentare sollten mehr als ein Zeichen enthalten, bitte überprüfen Sie Ihren Kommentar und probieren Sie es erneut." NOT_AUTHORIZED: "Sie sind nicht berechtigt, diese Aktion auszuführen." @@ -230,15 +254,20 @@ de: ALREADY_EXISTS: "Ressource existiert bereits" INVALID_ASSET_URL: "Asset-URL ist ungültig" CANNOT_IGNORE_STAFF: "Mitarbeiter können nicht ignoriert werden." - email: "E-Mail-Adresse ungültig" + INCORRECT_PASSWORD: "Falsches Passwort" + email: "Bitte geben Sie eine gültige E-Mail-Adresse ein." + DELETION_NOT_SCHEDULED: "Löschvorgang wurde nicht geplant" confirm_password: "Passwörter nicht identisch. Bitte erneut überprüfen" network_error: "Server-Verbindung fehlgeschlagen. Bitte überprüfen Sie ihre Internetverbindung und versuchen Sie es erneut." email_not_verified: "E-Mail-Adresse {0} nicht bestätigt." email_password: "E-Mail und/oder Passwort inkorrekt." organization_name: "Namen von Organisationen dürfen nur Buchstaben und Zahlen enthalten." + organization_contact_email: "E-Mail-Adresse der Organisation ist ungültig." password: "Passwort muss mindestens 8 Zeichen enthalten" username: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten" unexpected: "Unerwarteter Fehler aufgetreten. Es tut uns leid!" + required_field: "Dieses Feld ist erforderlich" + temporarily_suspended: "Ihr Konto ist vorübergehend gesperrt. Es wird wieder aktiviert am {0}. Bei Fragen setzen Sie sich mit uns in Kontakt." flag_comment: "Kommentar melden" flag_reason: "Grund der Meldung (optional)" flag_username: "Nutzername melden" @@ -248,6 +277,7 @@ de: comment: Kommentar comment_is_ignored: "Dieser Kommentar ist nicht sichtbar, da Sie den Nutzer ignorieren." comment_is_rejected: "Sie haben diesen Kommentar abgelehnt." + comment_is_deleted: "Der Kommentar wurde vom Nutzer gelöscht." comment_is_hidden: "Dieser Kommentar ist nicht verfügbar." comments: Kommentare configure_stream: "Konfigurieren" @@ -336,6 +366,7 @@ de: sort: "Sortieren" show_shortcuts: "Tastaturkürzel anzeigen" singleview: "Zen-Modus" + system_withheld: "System Withheld" thismenu: "Dieses Menü öffnen" jump_to_queue: "Zu bestimmter Liste springen" thousand: T @@ -358,6 +389,9 @@ de: report_notif: "Vielen Dank für Ihre Meldung. Unsere Moderatoren wurden informiert und werden sich in Kürze darum kümmern." report_notif_remove: "Ihre Meldung wurde entfernt." reported: Gemeldet + comment_history_blank: + title: Sie haben noch keine Kommentare verfasst + info: Hier wird ein Verlauf Ihrer verfassten Kommentare erscheinen settings: from_settings_page: "Sie können auf Ihrer Profilseite Ihren Kommentarverlauf einsehen." my_comment_history: "Mein Kommentarverlauf" @@ -369,7 +403,7 @@ de: stream: all_comments: "Alle Kommentare" temporarily_suspended: "Entsprechend der Community-Regeln von {0} wurde Ihr Konto vorübergehend gesperrt. Nehmen Sie {1} wieder an der Diskussion teil." - comment_not_found: "Kommentar nicht gefunden" + comment_not_found: "Dieser Kommentar wurde entfernt oder existiert nicht." no_comments: "Es gibt noch keine Kommentare. Schreiben Sie doch einen..." no_comments_and_closed: "Es gab zu diesem Artikel keine Kommentare." step_1_header: "Ein Problem melden" @@ -396,6 +430,8 @@ de: one_hour: "1 Stunde" hours: "{0} Stunden" days: "{0} Tage" + hour: "{0} hours" + day: "{0} days" cancel: "Abbrechen" suspend_user: "Nutzer vorübergehend sperren" email_message_suspend: "Sehr geehrte/r {0}, entsprechend der Community-Richtlinien von {1} wurde Ihr Konto vorübergehend gesperrt. Während der Sperrung können Sie weder kommentieren noch andere Aktionen ausführen. Nehmen Sie {2} wieder an der Diskussion teil." @@ -435,15 +471,15 @@ de: rejected: "Abgelehnte" user_history: "Konto-Verlauf" user_history: - user_banned: "User banned" - ban_removed: "Ban removed" - username_status: "Username {0}" - suspended: "Suspended, {0}" - suspension_removed: "Suspension removed" + user_banned: "Nutzer gesperrt" + ban_removed: "Sperrung aufgehoben" + username_status: "Nutzername {0}" + suspended: "Vorübergehend gesperrt, {0}" + suspension_removed: "Vorübergehende Sperrung aufgehoben" system: "System" - date: "Date" - action: "Action" - taken_by: "Taken By" + date: "Datum" + action: "Aktion" + taken_by: "Durch" user_impersonating: "Gibt sich für jemand anderen aus" user_no_comment: "Sie haben noch keinen Kommentar abgegeben. Teilen Sie Ihre Meinung mit uns!" username_offensive: "Dieser Nutzername ist unangemessen" @@ -461,7 +497,7 @@ de: username: "Nutzername" password: "Passwort" confirm_password: "Passwort bestätigen" - organization_contact_email: "Organization Contact Email" + organization_contact_email: "Kontakt-Adresse der Organisation" save: "Speichern" permitted_domains: title: "Zugelassene Domains" diff --git a/locales/en.yml b/locales/en.yml index 8eecc40b3..dfb42ff80 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -476,6 +476,7 @@ en: all: "All" rejected: "Rejected" user_history: "User History" + id: "ID" user_history: user_banned: "User banned" ban_removed: "Ban removed" diff --git a/locales/fi_FI.yml b/locales/fi_FI.yml index 71416b4ff..494a48cdb 100644 --- a/locales/fi_FI.yml +++ b/locales/fi_FI.yml @@ -413,7 +413,7 @@ fi_FI: title_reject: "Huomasimme sinun hylänneen käyttäjänimen" suspend_user: "Aseta väliaikainen käyttökielto" yes_suspend: "Kyllä, sulje väliaikaisesti" - email_message_reject: "Toinen yhteisön jäsen on ilmiantanut käyttäjänimesi ja sen perusteella nimi on hylätty. Et voi enää osallistua keskusteluun. Ole ystävällisesti yhteydessä meihin, jos sinulla on asiasta kysyttävää." + email_message_reject: "Toinen yhteisön jäsen on ilmiantanut käyttäjänimesi ja sen perusteella nimi on hylätty. Et voi enää osallistua keskusteluun. Ole ystävällisesti yhteydessä meihin, jos sinulla on asiasta kysyttävää." write_message: "Kirjoita viesti" send: Lähetä thank_you: "Arvostamme palautettasi. Moderaattorimme käy läpi tekemäsi ilmiannon." @@ -462,4 +462,4 @@ fi_FI: close: "Sulje asennusnäkymä" admin_sidebar: view_options: "Näytä asetukset" -sort_comments: "Järjestä kommentit" \ No newline at end of file + sort_comments: "Järjestä kommentit" diff --git a/middleware/contentSecurityPolicy.js b/middleware/contentSecurityPolicy.js new file mode 100644 index 000000000..c47faa74e --- /dev/null +++ b/middleware/contentSecurityPolicy.js @@ -0,0 +1,55 @@ +const helmet = require('helmet'); +const { WEBSOCKET_LIVE_URI, ENABLE_STRICT_CSP } = require('../config'); +const { BASE_PATH, BASE_URL, STATIC_URL } = require('../url'); +const { URL } = require('url'); + +// websocketUri represents the host where we can connect for websocket requests. +const websocketUri = new URL(WEBSOCKET_LIVE_URI || BASE_URL); +websocketUri.protocol = websocketUri.protocol.startsWith('https') + ? 'wss' + : 'ws'; +const { origin: websocketSrc } = websocketUri; + +// staticSrc represents any static asset hosted on the static host. +const { host: staticSrc } = new URL(STATIC_URL); + +// nonceSrc represents the nonce source that is used to indicate a safe resource +// to load. +const nonceSrc = (req, res) => `'nonce-${res.locals.nonce}'`; + +module.exports = helmet.contentSecurityPolicy({ + directives: { + reportUri: `${BASE_PATH}api/v1/csp`, // report all policy violations to our reporting uri + defaultSrc: ["'none'"], // by default, do not allow anything at all + scriptSrc: [ + "'self'", + 'https://ajax.googleapis.com', // for jquery + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + styleSrc: [ + "'self'", + 'https://maxcdn.bootstrapcdn.com', // for bootstrap css + 'https://fonts.googleapis.com', // for google fonts + 'https://code.getmdl.io', // for mdl css + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + connectSrc: ["'self'", websocketSrc], + fontSrc: [ + "'self'", + 'https://maxcdn.bootstrapcdn.com', // for font-awesome + 'https://fonts.gstatic.com', // for google fonts + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + imgSrc: [ + "'self'", + staticSrc, // for any static files loaded from a cdn + nonceSrc, + ], + }, + browserSniff: false, + // Allow the configuration to disable strict enforcement of CSP. + reportOnly: !ENABLE_STRICT_CSP, +}); diff --git a/middleware/nonce.js b/middleware/nonce.js new file mode 100644 index 000000000..1181fac85 --- /dev/null +++ b/middleware/nonce.js @@ -0,0 +1,9 @@ +const uuid = require('uuid/v4'); + +// nonce is designed to create a random value that can be used in conjunction +// with the csp middleware. +module.exports = (req, res, next) => { + res.locals.nonce = uuid(); + + next(); +}; diff --git a/models/schema/user.js b/models/schema/user.js index a4ccfd185..a27b8dc39 100644 --- a/models/schema/user.js +++ b/models/schema/user.js @@ -339,6 +339,11 @@ User.virtual('banned') }) .set(function(status) { this.status.banned.status = status; + + if (!this.status.banned.history) { + this.status.banned.history = []; + } + this.status.banned.history.push({ status, created_at: new Date(), @@ -357,6 +362,11 @@ User.virtual('suspended') }) .set(function(until) { this.status.suspension.until = until; + + if (!this.status.suspension.history) { + this.status.suspension.history = []; + } + this.status.suspension.history.push({ until, created_at: new Date(), diff --git a/package.json b/package.json index 97f4e373d..b21e2f78a 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "dependencies": { "@coralproject/gql-merge": "^0.1.0", "@coralproject/graphql-anywhere-optimized": "^0.1.0", - "accepts": "^1.3.4", "apollo-client": "^1.9.1", "apollo-engine": "^0.8.1", "apollo-server-express": "^1.2.0", @@ -91,6 +90,7 @@ "common-tags": "^1.4.0", "compression": "1.7.1", "compression-webpack-plugin": "^1.0.0", + "consolidate": "0.14.0", "cookie-parser": "^1.4.3", "copy-webpack-plugin": "^4.0.0", "cross-spawn": "^5.1.0", @@ -108,6 +108,7 @@ "express-static-gzip": "^0.3.1", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^0.11.2", + "fluent-langneg": "^0.1.0", "form-data": "^2.3.1", "fs-extra": "^4.0.1", "graphql": "^0.10.1", @@ -149,7 +150,7 @@ "metascraper-title": "^3.9.2", "minimist": "^1.2.0", "moment": "^2.18.1", - "mongoose": "^4.12.3", + "mongoose": "^5.1.1", "ms": "^2.0.0", "murmurhash-js": "^1.0.0", "name-all-modules-plugin": "^1.0.1", @@ -231,7 +232,7 @@ "husky": "^0.14.3", "identity-obj-proxy": "^3.0.0", "jest-junit": "^3.6.0", - "lint-staged": "^7.0.0", + "lint-staged": "^7.1.0", "mocha": "^3.1.2", "mocha-junit-reporter": "^1.12.1", "nightwatch": "^0.9.16", diff --git a/plugin-api/beta/client/utils/index.js b/plugin-api/beta/client/utils/index.js index aeb9841f9..0d1893066 100644 --- a/plugin-api/beta/client/utils/index.js +++ b/plugin-api/beta/client/utils/index.js @@ -9,4 +9,5 @@ export { getDefinitionName, getShallowChanges, createDefaultResponseFragments, + handlePopupAuth, } from 'coral-framework/utils'; diff --git a/plugins/talk-plugin-akismet/server/hooks.js b/plugins/talk-plugin-akismet/server/hooks.js index b06557783..4e24af4b3 100644 --- a/plugins/talk-plugin-akismet/server/hooks.js +++ b/plugins/talk-plugin-akismet/server/hooks.js @@ -28,8 +28,11 @@ let enabled = true; module.exports = { RootMutation: { createComment: { - async pre(_, { input }, { loaders, parent: req }) { - // If the key validation failed, then we can't run with the client. + async pre(_, { input }, ctx) { + const req = ctx.parent.parent; + const loaders = ctx.loaders; + + //If the key validation failed, then we can't run with the client. if (!enabled) { debug('not enabled, passing'); return; diff --git a/plugins/talk-plugin-facebook-auth/client/actions.js b/plugins/talk-plugin-facebook-auth/client/actions.js index 79f2d2a6a..2f92ecfb6 100644 --- a/plugins/talk-plugin-facebook-auth/client/actions.js +++ b/plugins/talk-plugin-facebook-auth/client/actions.js @@ -1,3 +1,5 @@ +import { handlePopupAuth } from 'plugin-api/beta/client/utils'; + export const loginWithFacebook = () => (dispatch, _, { rest }) => { - window.location = `${rest.uri}/auth/facebook`; + handlePopupAuth(`${rest.uri}/auth/facebook`); }; diff --git a/plugins/talk-plugin-facebook-auth/client/index.js b/plugins/talk-plugin-facebook-auth/client/index.js index cb8a8f059..cf2ac32ce 100644 --- a/plugins/talk-plugin-facebook-auth/client/index.js +++ b/plugins/talk-plugin-facebook-auth/client/index.js @@ -5,6 +5,7 @@ import translations from './translations.yml'; export default { translations, slots: { + authExternalAdminSignIn: [SignIn], authExternalSignIn: [SignIn], authExternalSignUp: [SignUp], }, diff --git a/plugins/talk-plugin-google-auth/client/actions.js b/plugins/talk-plugin-google-auth/client/actions.js index 8b49bf39e..1856ddb54 100644 --- a/plugins/talk-plugin-google-auth/client/actions.js +++ b/plugins/talk-plugin-google-auth/client/actions.js @@ -1,3 +1,5 @@ +import { handlePopupAuth } from 'plugin-api/beta/client/utils'; + export const loginWithGoogle = () => (dispatch, _, { rest }) => { - window.location = `${rest.uri}/auth/google`; + handlePopupAuth(`${rest.uri}/auth/google`); }; diff --git a/plugins/talk-plugin-google-auth/client/index.js b/plugins/talk-plugin-google-auth/client/index.js index cb8a8f059..cf2ac32ce 100644 --- a/plugins/talk-plugin-google-auth/client/index.js +++ b/plugins/talk-plugin-google-auth/client/index.js @@ -5,6 +5,7 @@ import translations from './translations.yml'; export default { translations, slots: { + authExternalAdminSignIn: [SignIn], authExternalSignIn: [SignIn], authExternalSignUp: [SignUp], }, diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index ae89039c3..864af57a4 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -69,6 +69,76 @@ en: description_2: "You can change your account settings by visiting" path: "My Profile > Settings" alert: "Email Added!" +de: + email: + email_change_original: + subject: Änderung Ihrer E-Mail-Adresse + body: Ihre E-Mail-Adresse wurde von {0} zu {1} geändert. Falls Sie diese Änderung nicht selbst vorgenommen haben, kontaktieren Sie bitte zur Sicherheit {2}. + error: + NO_LOCAL_PROFILE: Mit diesem Benutzerkonto ist keine E-Mail-Adresse verbunden. + LOCAL_PROFILE: Es ist bereits eine bestätigte E-Mail-Adresse mit diesem Benutzerkonto verbunden. + INCORRECT_PASSWORD: Das Passwort war nicht korrekt. + talk-plugin-local-auth: + change_password: + change_password: "Passwort ändern" + passwords_dont_match: "Die Passwörter stimmen nicht überein" + required_field: "Diese Angabe ist erforderlich" + forgot_password: "Passwort vergessen?" + save: "Speichern" + cancel: "Abbrechen" + edit: "Ändern" + changed_password_msg: "Passwort geändert - Ihr Passwort wurde erfolgreich geändert" + forgot_password_sent: "Passwort vergessen - Wir haben Ihnen eine E-Mail zum Zurücksetzen des Passwortes geschickt" + change_username: + change_username_note: "Nutzernamen können nur alle 14 Tage geändert werden. Ihr Nutzername ist zur Zeit nicht editierbar." + save: "Speichern" + edit_profile: "Profil ändern" + cancel: "Abbrechen" + confirm_username_change: "Änderung des Nutzernamens bestätigen" + description: "Sie möchten Ihren Nutzernamen ändern: der neue Nutzername wird an allen alten und neuen Kommentaren erscheinen." + old_username: "Alter Nutzername" + new_username: "Neuer Nutzername" + bottom_note: "Achtung: die nächste Änderung des Nutzernamens ist erst nach 14 Tagen möglich" + confirm_changes: "Änderung bestätigen" + username_does_not_match: "Die Nutzernamen stimmen nicht überein" + cant_be_equal: "Der neue Nutzername {0} muss sich vom alten unterscheiden." + changed_username_success_msg: "Nutzername geändert - Ihr Nutzername wurde erfolgreich aktualisiert. Die nächste Änderung des Nutzernamens ist erst nach 14 Tagen möglich." + change_username_attempt: "Der Nutzername kann zur Zeit nicht aktualisiert werden. Änderungen sind nur nach jeweils 14 Tagen möglich." + change_email: + confirm_email_change: "Änderung der E-Mail-Adresse bestätigen" + description: "Sie versuchen, Ihre E-Mail-Adresse ändern: die neue E-Mail-Adresse wird zum Login sowie für Benachrichtigungen bzgl. Ihres Benutzerkontos verwendet." + old_email: "Alte E-Mail-Adresse" + new_email: "Neue E-Mail-Adresse" + enter_password: "Passwort" + incorrect_password: "Passwort nicht korrekt" + confirm_change: "Änderung bestätigen" + cancel: "Abbrechen" + change_email_msg: "E-Mail-Adresse erfolgreich aktualisiert - die neue E-Mail-Adresse ab sofort zum Anmelden und für Benachrichtigungen verwendet." + add_email: + add_email_address: "E-Mail-Adresse hinzufügen" + enter_email_address: "E-Mail-Adresse:" + invalid_email_address: "Ungültige E-Mail-Adresse" + confirm_email_address: "Bestätigung der E-Mail-Adresse:" + email_does_not_match: "Die E-Mail-Adressen stimmen nicht überein" + insert_password: "Passwort auswählen:" + required_field: "Dieses Feld ist erforderlich" + done: "Fertig" + content: + title: "E-Mail-Adresse hinzufügen" + description: "Aus Sicherheitsgründen benötigen wir eine E-Mail-Adresse zu jedem Benutzerkonto. Ihre E-Mail-Adresse wird für folgendes verwendet:" + item_1: "Benachrichtigungen über Änderungen am Benutzerkonto (Nutzername, E-Mail-Adresse, Passwort)" + item_2: "Ermöglicht den Download des eigenen Kommentar-Archivs" + item_3: "Kommentar-Benachrichtigungen erhalten, die Sie explizit angefordert haben" + verify: + title: "E-Mail-Adresse bestätigen" + description: "Wir haben einen E-Mail an {0} geschickt. Bitte bestätigen Sie Ihre E-Mail-Adresse, um damit Benachrichtigungen über Änderungen am Benutzerkonto zu erhalten." + added: + title: "E-Mail-Adresse hinzugefügt" + description: "Ihre E-Mail-Adresse wurde dem Benutzerkonto hinzugefügt." + subtitle: "Sie möchten Ihre E-Mail-Adresse ändern?" + description_2: "Sie können Ihre Konto-Einstellugen ändern unter" + path: "Mein Profil > Profil-Einstellungen" + alert: "E-Mail-Adresse hinzugefügt!" es: talk-plugin-local-auth: change_password: diff --git a/plugins/talk-plugin-notifications-category-featured/client/translations.yml b/plugins/talk-plugin-notifications-category-featured/client/translations.yml index 7077943ef..1249b6147 100644 --- a/plugins/talk-plugin-notifications-category-featured/client/translations.yml +++ b/plugins/talk-plugin-notifications-category-featured/client/translations.yml @@ -1,3 +1,6 @@ en: talk-plugin-notifications-category-featured: toggle_description: My comment is featured +de: + talk-plugin-notifications-category-featured: + toggle_description: Mein Kommentar wird empfohlen diff --git a/plugins/talk-plugin-notifications-category-featured/translations.yml b/plugins/talk-plugin-notifications-category-featured/translations.yml index 18b00ea71..d11b66300 100644 --- a/plugins/talk-plugin-notifications-category-featured/translations.yml +++ b/plugins/talk-plugin-notifications-category-featured/translations.yml @@ -3,4 +3,10 @@ en: categories: featured: subject: "One of your comments was featured on {0}" - body: "{0}\nA member of our team has selected this comment to be featured for other readers: {1}" \ No newline at end of file + body: "{0}\nA member of our team has selected this comment to be featured for other readers: {1}" +de: + talk-plugin-notifications: + categories: + featured: + subject: "Einer Ihrer Kommentare wurde auf {0} hervorgehoben" + body: "{0}\nEin Mitglied unseres Teams hat diesen Kommentar ausgewählt, er wird jetzt für andere Leser besonders hervorgehoben: {1}" diff --git a/plugins/talk-plugin-notifications-category-reply/client/translations.yml b/plugins/talk-plugin-notifications-category-reply/client/translations.yml index 703891de5..83fd4e1cd 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/translations.yml +++ b/plugins/talk-plugin-notifications-category-reply/client/translations.yml @@ -1,3 +1,6 @@ en: talk-plugin-notifications-category-reply: toggle_description: My comment receives a reply +de: + talk-plugin-notifications-category-reply: + toggle_description: Jemand antwortet auf meinen Kommentar diff --git a/plugins/talk-plugin-notifications-category-reply/translations.yml b/plugins/talk-plugin-notifications-category-reply/translations.yml index 0f28ca105..2138b750f 100644 --- a/plugins/talk-plugin-notifications-category-reply/translations.yml +++ b/plugins/talk-plugin-notifications-category-reply/translations.yml @@ -3,4 +3,10 @@ en: categories: reply: subject: "Someone has replied to your comment on {0}" - body: "{0}\n{1} replied to your comment: {2}" \ No newline at end of file + body: "{0}\n{1} replied to your comment: {2}" +de: + talk-plugin-notifications: + categories: + reply: + subject: "Jemand hat bei {0} auf Ihren Kommentar geantwortet" + body: "{0}\n{1} antwortete auf Ihren Kommentar: {2}" diff --git a/plugins/talk-plugin-notifications-category-staff/client/translations.yml b/plugins/talk-plugin-notifications-category-staff/client/translations.yml index ba831cf37..496d8def6 100644 --- a/plugins/talk-plugin-notifications-category-staff/client/translations.yml +++ b/plugins/talk-plugin-notifications-category-staff/client/translations.yml @@ -1,3 +1,6 @@ en: talk-plugin-notifications-category-staff: toggle_description: A staff member replies to my comment +de: + talk-plugin-notifications-category-staff: + toggle_description: Ein Redaktionsmitglied antwortet auf meinen Kommentar diff --git a/plugins/talk-plugin-notifications-category-staff/translations.yml b/plugins/talk-plugin-notifications-category-staff/translations.yml index 38b534e1d..1bfcf0210 100644 --- a/plugins/talk-plugin-notifications-category-staff/translations.yml +++ b/plugins/talk-plugin-notifications-category-staff/translations.yml @@ -3,4 +3,10 @@ en: categories: staff: subject: "Someone at {0} has replied to your comment" - body: "{0}\n{1} works for {2} and has replied to your comment: {3}" \ No newline at end of file + body: "{0}\n{1} works for {2} and has replied to your comment: {3}" +de: + talk-plugin-notifications: + categories: + staff: + subject: "Jemand hat bei {0} auf Ihren Kommentar geantwortet" + body: "{0}\n{1} arbeitet für {2} und hat auf Ihren Kommentar geantwortet: {3}" diff --git a/plugins/talk-plugin-notifications-digest-daily/client/translations.yml b/plugins/talk-plugin-notifications-digest-daily/client/translations.yml index 1744918ec..955229708 100644 --- a/plugins/talk-plugin-notifications-digest-daily/client/translations.yml +++ b/plugins/talk-plugin-notifications-digest-daily/client/translations.yml @@ -2,3 +2,7 @@ en: talk-plugin-notifications: digest_enum: DAILY: In a daily digest +de: + talk-plugin-notifications: + digest_enum: + DAILY: Einmal täglich diff --git a/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml b/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml index da4bad14f..f11812068 100644 --- a/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml +++ b/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml @@ -2,3 +2,7 @@ en: talk-plugin-notifications: digest_enum: HOURLY: In an hourly digest +de: + talk-plugin-notifications: + digest_enum: + HOURLY: Stündlich diff --git a/plugins/talk-plugin-notifications/client/translations.yml b/plugins/talk-plugin-notifications/client/translations.yml index 3ed123502..d4cc716e0 100644 --- a/plugins/talk-plugin-notifications/client/translations.yml +++ b/plugins/talk-plugin-notifications/client/translations.yml @@ -16,3 +16,21 @@ en: digest_option: Send notifications digest_enum: NONE: Immediately +de: + talk-plugin-notifications: + settings_title: Benachrichtigungen + settings_subtitle: Benachrichtige mich wenn + turn_off_all: Ich möchte keine Benachrichtigungen erhalten + banner_info: + title: Bestätigte E-Mail-Adresse benötigt + text: Um E-Mail-Benachrichtigungen zu erhalten, müssen Sie eine bestätigte E-Mail-Adresse haben. + verify_now: E-Mail-Adresse jetzt bestätigen + banner_success: + title: E-Mail-Bestätigungsanfrage verschickt + text: Eine E-Mail mit einem Bestätigungslink wurde an {0} geschickt. + banner_error: + title: Fehler + text: Beim Versand der Bestätigungsmail gab es einen Fehler. Bitte versuchen Sie es später erneut. + digest_option: Benachrichtigungen senden + digest_enum: + NONE: Sofort diff --git a/plugins/talk-plugin-notifications/server/router.js b/plugins/talk-plugin-notifications/server/router.js index 68919a936..9185c5aba 100644 --- a/plugins/talk-plugin-notifications/server/router.js +++ b/plugins/talk-plugin-notifications/server/router.js @@ -4,7 +4,7 @@ const { get, isEmpty, reduce } = require('lodash'); module.exports = router => { router.get('/account/unsubscribe-notifications', (req, res) => { - res.render(path.join(__dirname, 'views/unsubscribe-notifications')); + res.render(path.join(__dirname, 'views/unsubscribe-notifications.njk')); }); /** diff --git a/plugins/talk-plugin-notifications/server/translations.yml b/plugins/talk-plugin-notifications/server/translations.yml index c3b5a2417..e6211ffbb 100644 --- a/plugins/talk-plugin-notifications/server/translations.yml +++ b/plugins/talk-plugin-notifications/server/translations.yml @@ -11,4 +11,18 @@ en: click_to_confirm: "Click below to confirm that you would like to unsubscribe from all notifications" confirm: "Confirm" are_unsubscribed: "You are now unsubscribed from all notifications." - token_invalid: "Unsubscribe link is invalid, click the link from a more recent email or visit a comment stream and login to change your notification preferences" \ No newline at end of file + token_invalid: "Unsubscribe link is invalid, click the link from a more recent email or visit a comment stream and login to change your notification preferences" +de: + talk-plugin-notifications: + templates: + digest: + subject: "Ihre Kommentaraktivität bei {0}" + footer: "Sie erhalten diese Benachrichtigung, weil Sie Community-Mitglied bei {0} sind und diese E-Mails abonniert haben." + links: + unsubscribe: "E-Mail-Benachrichtigungen abbestellen" + unsubscribe_page: + unsubscribe: "Kommentar-Benachrichtigungen abbestellen" + click_to_confirm: "Klicken Sie folgenden Link, um zu bestätigen, dass Sie alle Benachrichtigungen abbestellen möchten" + confirm: "Bestätigen" + are_unsubscribed: "Sie haben haben alle Benachrichtigungen erfolgreich abbestellt." + token_invalid: "Der Abbestell-Link ist ungültig. Klicken Sie den Link einer neueren E-Mail oder gehen Sie zu einem Kommentarbereich, melden Sie sich an und ändern Sie dort Ihre Benachrichtigungseinstellungen" diff --git a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs deleted file mode 100644 index 129c3115c..000000000 --- a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.ejs +++ /dev/null @@ -1,64 +0,0 @@ - - - - - <%= t('talk-plugin-notifications.unsubscribe_page.unsubscribe') %> - <%- include(root + '/partials/head') %> - - - - -
    -
    <%= t('talk-plugin-notifications.unsubscribe_page.token_invalid') %>
    - -
    - <%= t('talk-plugin-notifications.unsubscribe_page.click_to_confirm') %> - -
    -
    - - - - \ No newline at end of file diff --git a/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk new file mode 100644 index 000000000..5080277f0 --- /dev/null +++ b/plugins/talk-plugin-notifications/server/views/unsubscribe-notifications.njk @@ -0,0 +1,68 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('talk-plugin-notifications.unsubscribe_page.unsubscribe') }}{% endblock %} + +{% block css %} +{{ super() }} + +{% endblock %} + +{% block html %} +
    +
    {{ t('talk-plugin-notifications.unsubscribe_page.token_invalid') }}
    +
    {{ t('talk-plugin-notifications.unsubscribe_page.are_unsubscribed') }}
    +
    + {{ t('talk-plugin-notifications.unsubscribe_page.click_to_confirm') }} + +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js index 179fa0193..c2055a16e 100644 --- a/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js +++ b/plugins/talk-plugin-profile-data/client/components/DeleteMyAccount.js @@ -63,17 +63,18 @@ class DeleteMyAccount extends React.Component {

    {t('delete_request.delete_my_account_description')}

    -

    - {scheduledDeletionDate && - t( - 'delete_request.already_submitted_request_description', - moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a') - )} -

    {scheduledDeletionDate ? ( - +
    +

    + {t( + 'delete_request.already_submitted_request_description', + moment(scheduledDeletionDate).format('MMM Do YYYY, h:mm:ss a') + )} +

    + +
    ) : ( - - -
    - - - - diff --git a/plugins/talk-plugin-profile-data/server/views/download.njk b/plugins/talk-plugin-profile-data/server/views/download.njk new file mode 100644 index 000000000..ee9c94272 --- /dev/null +++ b/plugins/talk-plugin-profile-data/server/views/download.njk @@ -0,0 +1,56 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('download_landing.download_your_account') }}{% endblock %} + +{% block html %} +
    +
    +

    {{ t('download_landing.download_your_account') }}

    +

    {{ t('download_landing.download_details') }}

    +

    {{ t('download_landing.all_information_included') }}

    +
      +
    • {{ t('download_landing.information_included.date') }}
    • +
    • {{ t('download_landing.information_included.url') }}
    • +
    • {{ t('download_landing.information_included.body') }}
    • +
    • {{ t('download_landing.information_included.asset_url') }}
    • +
    +
    +
    + +
    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/plugins/talk-plugin-profile-data/translations.yml b/plugins/talk-plugin-profile-data/translations.yml index 51555d305..c3b7e40ca 100644 --- a/plugins/talk-plugin-profile-data/translations.yml +++ b/plugins/talk-plugin-profile-data/translations.yml @@ -35,3 +35,40 @@ en: body: "You have cancelled your account deletion request for {0}. Your account is now reactivated." error: DOWNLOAD_TOKEN_INVALID: "Your download link is not valid." +de: + download_landing: + download_your_account: "Mein Kommentar-Archiv herunterladen" + download_details: "Ihr Kommentar-Archiv wird als ZIP-Datei bereitgestellt. Nach dem Entpacken erhalten Sie eine CSV-Datei, die einfach in ein Tabellenkalkulationsprogramm importiert werden kann." + all_information_included: "Für jeden Ihrer Kommentare sind folgende Informationen enthalten:" + information_included: + date: "Wann Sie den Kommentar geschrieben haben" + url: "Die dauerhafte URL (Internetadresse) des Kommentars" + body: "Der Kommentar-Text" + asset_url: "Die URL (Internetadresse) des Artikels an dem der Kommentar erscheint" + confirm: "Kommentar-Archiv herunterladen" + email: + download: + subject: "Ihre Kommentare sind zum Download bereit: {0}" + download_link_ready: "Hier klicken, um Ihre Kommentare von {0} bis {1} herunterzuladen:" + download_archive: "Archiv herunterladen" + delete: + subject: "Ihr Benutzerkonto bei {0} ist zur Löschung vorgesehen" + body: | + Wir haben eine Anfrage erhalten, Ihr Benutzerkonto zu löschen. Die Löschung ist geplant für den {1}. + + Nach diesem Zeitpunkt werden alle Ihre Kommentare von der Website und aus unserer Datenbank gelöscht. Außerdem werden Ihr Nutzername und Ihre E-Mail-Adresse aus unserem System enfernt. + + Falls Sie es sich noch anders überlegen, können Sie sich bis spätestens zum angegebenen Lösch-Zeitpunkt einloggen und die Lösch-Anfrage abbrechen. + deleted: + subject: "Ihre Benutzerkonto bei {0} wurde gelöscht" + body: | + Ihr Kommentar-Konto bei {0} ist nun gelöscht. Schade, auf Wiedersehen! + + Falls Sie sich in Zukunft erneut an der Diskussion beteiligen möchten, können Sie jederzeit ein neues Benutzerkonto einrichten. + + Wenn Sie Lust haben, schreiben Sie uns doch eine Rückmeldung, Feedback, oder Kritik an {1}, damit wir unsere Community verbessern können. Vielen Dank! + cancelDelete: + subject: "Die Lösch-Anfrage für Ihr Benutzerkonto bei {0} wurde abgebrochen" + body: "Sie haben die Lösch-Anfrage für Ihr Benutzerkonto bei {0} abgebrochen. Das Konto ist nun wieder aktiv." + error: + DOWNLOAD_TOKEN_INVALID: "Der Download-Link ist ungültig." diff --git a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js index f99648cb7..fd5d451a1 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/components/Button.js @@ -7,21 +7,17 @@ class Button extends React.Component { render() { const { className, - title, - onClick, children, active, activeClassName, - disabled, + ...rest } = this.props; return ( @@ -32,11 +28,8 @@ class Button extends React.Component { Button.propTypes = { className: PropTypes.string, activeClassName: PropTypes.string, - title: PropTypes.string, - onClick: PropTypes.func, children: PropTypes.node, active: PropTypes.bool, - disabled: PropTypes.bool, }; export default Button; diff --git a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js index 028757222..59b75fdf2 100644 --- a/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js +++ b/plugins/talk-plugin-rich-text/client/components/rte/factories/createToggle.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '../components/Button'; +import bowser from 'bowser'; /** * createToggle creates a button that can be active, inactive or disabled @@ -32,7 +33,17 @@ const createToggle = ( this.execCommand(); }; + // Detect whether there was focus on the RTE before the click. + hadFocusBeforeClick = false; + handleMouseDown = () => (this.hadFocusBeforeClick = this.props.api.focused); + handleClick = () => { + // Skip IOS when the focus was not there before. + // IOS fails to focus to the RTE correctly and scrolls to nirvana. + // See https://www.pivotaltracker.com/story/show/157607216 + if (!this.hadFocusBeforeClick && bowser.ios) { + return; + } this.props.api.focus(); this.formatToggle(); this.props.api.focus(); @@ -62,6 +73,7 @@ const createToggle = ( - - -
    - - - - diff --git a/views/account/email/confirm.njk b/views/account/email/confirm.njk new file mode 100644 index 000000000..8e2c9ff05 --- /dev/null +++ b/views/account/email/confirm.njk @@ -0,0 +1,63 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('confirm_email.email_confirmation') }}{% endblock %} + +{% block html %} +
    +
    +

    {{ t('confirm_email.email_confirmation') }}

    +

    {{ t('confirm_email.click_to_confirm') }}

    +
    +
    + +
    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/account/password/reset.ejs b/views/account/password/reset.ejs deleted file mode 100644 index c8b106139..000000000 --- a/views/account/password/reset.ejs +++ /dev/null @@ -1,85 +0,0 @@ - - - - <%= t('password_reset.set_new_password') %> - <%- include ../../partials/account %> - - -
    -
    -

    <%= t('password_reset.set_new_password') %>

    -

    <%= t('password_reset.change_password_help') %>

    -
    -
    - - - -
    -
    -
    - - - - diff --git a/views/account/password/reset.njk b/views/account/password/reset.njk new file mode 100644 index 000000000..6eee12dd0 --- /dev/null +++ b/views/account/password/reset.njk @@ -0,0 +1,85 @@ +{% extends "templates/account.njk" %} + +{% block title %}{{ t('password_reset.set_new_password') }}{% endblock %} + +{% block html %} +
    +
    +

    {{ t('password_reset.set_new_password') }}

    +

    {{ t('password_reset.change_password_help') }}

    +
    +
    + + + +
    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/admin.ejs b/views/admin.ejs deleted file mode 100644 index 89cc150f7..000000000 --- a/views/admin.ejs +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Talk - Coral Admin - <%- include partials/head %> - - - - <%- include partials/custom-css %> - - -
    - - - - diff --git a/views/admin.njk b/views/admin.njk new file mode 100644 index 000000000..1b60f6e76 --- /dev/null +++ b/views/admin.njk @@ -0,0 +1,14 @@ +{% extends "templates/base.njk" %} + +{% block title %}Talk Admin{% endblock %} + +{% block css %} + + + +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/api/graphiql.ejs b/views/api/graphiql.ejs deleted file mode 100644 index 8007569b3..000000000 --- a/views/api/graphiql.ejs +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - GraphiQL - - <%- include ../partials/dev %> - - - - - - - - - <%- include ../partials/dev-nav %> -
    - - - diff --git a/views/api/graphiql.njk b/views/api/graphiql.njk new file mode 100644 index 000000000..b726918f3 --- /dev/null +++ b/views/api/graphiql.njk @@ -0,0 +1,126 @@ +{% extends "templates/development.njk" %} + +{% block title %}GraphiQL{% endblock %} + +{% block css %} +{# Include the base development pieces #} +{{ super() }} + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} + +{% block html %} +
    +{% endblock %} diff --git a/views/auth-callback.ejs b/views/auth-callback.ejs deleted file mode 100644 index e4d2387b7..000000000 --- a/views/auth-callback.ejs +++ /dev/null @@ -1,10 +0,0 @@ - - - - <%- include partials/data %> - - - - - - diff --git a/views/auth-callback.njk b/views/auth-callback.njk new file mode 100644 index 000000000..7119a6255 --- /dev/null +++ b/views/auth-callback.njk @@ -0,0 +1,10 @@ + + + + {% include "partials/data.njk" %} + + + + + + diff --git a/views/dev/article.ejs b/views/dev/article.ejs deleted file mode 100644 index 008d3989e..000000000 --- a/views/dev/article.ejs +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - <%= title %> - <%- include ../partials/dev %> - - - <%- include ../partials/dev-nav %> -
    -

    <%= title %>

    -
    - - -
    - - diff --git a/views/dev/article.njk b/views/dev/article.njk new file mode 100644 index 000000000..2fb8b3724 --- /dev/null +++ b/views/dev/article.njk @@ -0,0 +1,55 @@ +{% extends "templates/development.njk" %} + +{% block title %}{{ title }}{% endblock %} + +{% block meta %} + + + + + + + + +{% endblock %} + +{% block html %} +
    +

    {{ title }}

    +
    +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/dev/articles.ejs b/views/dev/articles.ejs deleted file mode 100644 index 1b07ed116..000000000 --- a/views/dev/articles.ejs +++ /dev/null @@ -1,40 +0,0 @@ - - - All Assets - <%- include ../partials/dev %> - - - <%- include ../partials/dev-nav %> -
    -
    -

    All Assets

    - <%= skip + 1 %> - <%= skip + assets.length %> of <%= count %> Assets -
    -
    - <% if (skip === 0) { %> Create a random article<% } %> - <% assets.forEach(function (asset) { %> - -
    -
    <%= asset.title %>
    - Created <%= asset.created_at.toLocaleString('en-US') %> -
    - <%= asset.url %> -
    - <% }) %> -
    - <% if (count !== assets.length) { %> - - <% } %> -
    - - diff --git a/views/dev/articles.njk b/views/dev/articles.njk new file mode 100644 index 000000000..e204c39fc --- /dev/null +++ b/views/dev/articles.njk @@ -0,0 +1,37 @@ +{% extends "templates/development.njk" %} + +{% block title %}All Assets{% endblock %} + +{% block html %} +
    +
    +

    All Assets

    + {{ skip + 1 }} - {{ skip + assets.length }} of {{ count }} Assets +
    + + {% if count !== assets.length %} + + {% endif %} +
    +{% endblock %} diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs deleted file mode 100644 index a1c708b55..000000000 --- a/views/embed/stream.ejs +++ /dev/null @@ -1,14 +0,0 @@ - - - - - <%- include ../partials/head %> - - - <%- include ../partials/custom-css %> - - -
    - - - diff --git a/views/embed/stream.njk b/views/embed/stream.njk new file mode 100644 index 000000000..ddc620f8e --- /dev/null +++ b/views/embed/stream.njk @@ -0,0 +1,16 @@ +{% extends "templates/base.njk" %} + +{% block title %}Talk{% endblock %} + +{% block css %} + + +{% endblock %} + +{% block html %} +
    +{% endblock %} + +{% block js %} + +{% endblock %} diff --git a/views/login.ejs b/views/login.ejs deleted file mode 100644 index 671b25c83..000000000 --- a/views/login.ejs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - <%- include partials/head %> - - <%- include partials/custom-css %> - - -
    - - - - diff --git a/views/login.njk b/views/login.njk new file mode 100644 index 000000000..6a3c1112f --- /dev/null +++ b/views/login.njk @@ -0,0 +1,16 @@ +{% extends "templates/base.njk" %} + +{% block title %}Talk - Login{% endblock %} + +{% block css %} + +{% endblock %} + +{% block html %} +
    +{% endblock %} + +{% block js %} + + +{% endblock %} diff --git a/views/partials/account.ejs b/views/partials/account.ejs deleted file mode 100644 index ff2166816..000000000 --- a/views/partials/account.ejs +++ /dev/null @@ -1,4 +0,0 @@ -<%- include head %> - - -<%- include custom-css %> diff --git a/views/partials/custom-css.ejs b/views/partials/custom-css.ejs deleted file mode 100644 index d453c37e1..000000000 --- a/views/partials/custom-css.ejs +++ /dev/null @@ -1 +0,0 @@ -<% if (locals.customCssUrl) { %><% } %> diff --git a/views/partials/custom-css.njk b/views/partials/custom-css.njk new file mode 100644 index 000000000..4e5a494e6 --- /dev/null +++ b/views/partials/custom-css.njk @@ -0,0 +1,3 @@ +{% if customCssUrl %} + +{% endif %} diff --git a/views/partials/data.ejs b/views/partials/data.ejs deleted file mode 100644 index f0bbf5925..000000000 --- a/views/partials/data.ejs +++ /dev/null @@ -1,3 +0,0 @@ -<%_ if (data != null) { _%> - -<%_ } _%> \ No newline at end of file diff --git a/views/partials/data.njk b/views/partials/data.njk new file mode 100644 index 000000000..4edaf2e2e --- /dev/null +++ b/views/partials/data.njk @@ -0,0 +1,3 @@ +{% if data %} + +{% endif %} diff --git a/views/partials/dev-nav.ejs b/views/partials/dev-nav.ejs deleted file mode 100644 index 088c52c20..000000000 --- a/views/partials/dev-nav.ejs +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/views/partials/favicon.njk b/views/partials/favicon.njk new file mode 100644 index 000000000..5ec2aad8a --- /dev/null +++ b/views/partials/favicon.njk @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/views/partials/head.ejs b/views/partials/head.ejs deleted file mode 100644 index dc3df7103..000000000 --- a/views/partials/head.ejs +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -<%- include data %> - - diff --git a/views/partials/nav.njk b/views/partials/nav.njk new file mode 100644 index 000000000..ecea9018d --- /dev/null +++ b/views/partials/nav.njk @@ -0,0 +1,8 @@ + diff --git a/views/templates/account.njk b/views/templates/account.njk new file mode 100644 index 000000000..7e1b6e6c9 --- /dev/null +++ b/views/templates/account.njk @@ -0,0 +1,6 @@ +{% extends "templates/base.njk" %} + +{% block css %} + + +{% endblock %} diff --git a/views/templates/base.njk b/views/templates/base.njk new file mode 100644 index 000000000..23048151e --- /dev/null +++ b/views/templates/base.njk @@ -0,0 +1,39 @@ + + + + {# Meta tags #} + + + {% block meta %}{% endblock %} + + {# Favicon Configuration #} + {% include "partials/favicon.njk" %} + + {# CSP and security headers #} + {% block security %}{% endblock %} + + {# Title #} + {% block title %}{% endblock %} + + {# CSS #} + + + + {% include "partials/custom-css.njk" %} + {% block css %}{% endblock %} + + {# Static data injection #} + {% include "partials/data.njk" %} + + {# Configuration #} + + + + {% block body %} + {% block html %} +
    + {% endblock %} + {% endblock %} + {% block js %}{% endblock %} + + diff --git a/views/partials/dev.ejs b/views/templates/development.njk similarity index 54% rename from views/partials/dev.ejs rename to views/templates/development.njk index 691b548e5..f7fab6682 100644 --- a/views/partials/dev.ejs +++ b/views/templates/development.njk @@ -1,5 +1,17 @@ - +{% extends "templates/base.njk" %} + +{# Null out the security block, we don't want/need that in development #} +{% block security %}{% endblock %} + +{% block css %} - + +{% endblock %} + +{% block body %} + {% include "partials/nav.njk" %} + + {% block html %}{% endblock %} +{% endblock %} diff --git a/yarn.lock b/yarn.lock index f0da6d006..a65a3524d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,7 +167,7 @@ abbrev@1, abbrev@^1.0.7: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" -accepts@^1.3.4, accepts@~1.3.4: +accepts@~1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" dependencies: @@ -1644,6 +1644,10 @@ bson@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.4.tgz#93c10d39eaa5b58415cbc4052f3e53e562b0b72c" +bson@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.0.6.tgz#444db59ddd4c24f0cb063aabdc5c8c7b0ceca912" + buffer-crc32@^0.2.1, buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1652,10 +1656,6 @@ buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" -buffer-shims@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -2392,6 +2392,12 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +consolidate@0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.14.0.tgz#b03acd566a2565ca96e99f44fd1417486b4df88d" + dependencies: + bluebird "^3.1.1" + constantinople@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-3.1.0.tgz#7569caa8aa3f8d5935d62e1fa96f9f702cd81c79" @@ -3353,10 +3359,6 @@ es6-map@^0.1.3: es6-symbol "~3.1.1" event-emitter "~0.3.5" -es6-promise@3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.2.1.tgz#ec56233868032909207170c39448e24449dd1fc4" - es6-promise@^4.0.5: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" @@ -3979,6 +3981,10 @@ flexbuffer@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" +fluent-langneg@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/fluent-langneg/-/fluent-langneg-0.1.0.tgz#aa12054fbfa4b728daec38331efc12f01faae93a" + flush-write-stream@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" @@ -4890,10 +4896,6 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -hooks-fixed@2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hooks-fixed/-/hooks-fixed-2.0.2.tgz#20076daa07e77d8a6106883ce3f1722e051140b0" - hosted-git-info@^2.1.4: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" @@ -6399,9 +6401,9 @@ jxLoader@*: promised-io "*" walker "1.x" -kareem@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/kareem/-/kareem-1.5.0.tgz#e3e4101d9dcfde299769daf4b4db64d895d17448" +kareem@2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.0.7.tgz#8d260366a4df4236ceccec318fcf10c17c5beb22" keymaster@^1.6.2: version "1.6.2" @@ -6528,9 +6530,9 @@ linkifyjs@^2.1.5: react ">=0.14.0" react-dom ">=0.14.0" -lint-staged@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.0.0.tgz#57926c63201e7bd38ca0576d74391efa699b4a9d" +lint-staged@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.1.0.tgz#1514a5b71b8d9492ca0c3d2a44769cbcbc8bcc79" dependencies: app-root-path "^2.0.1" chalk "^2.3.1" @@ -6541,6 +6543,7 @@ lint-staged@^7.0.0: execa "^0.9.0" find-parent-dir "^0.3.0" is-glob "^4.0.0" + is-windows "^1.0.2" jest-validate "^22.4.0" listr "^0.13.0" lodash "^4.17.5" @@ -6550,8 +6553,9 @@ lint-staged@^7.0.0: p-map "^1.1.1" path-is-inside "^1.0.2" pify "^3.0.0" - please-upgrade-node "^3.0.1" - staged-git-files "1.1.0" + please-upgrade-node "^3.0.2" + staged-git-files "1.1.1" + string-argv "^0.0.2" stringify-object "^3.2.2" listr-silent-renderer@^1.1.1: @@ -7451,36 +7455,36 @@ moment@^2.15.2: version "2.22.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.1.tgz#529a2e9bf973f259c9643d237fda84de3a26e8ad" -mongodb-core@2.1.17: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8" +mongodb-core@3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-3.0.8.tgz#8d401f4eab6056c0d874a3d5844a4844f761d4d7" dependencies: bson "~1.0.4" - require_optional "~1.0.0" + require_optional "^1.0.1" -mongodb@2.2.33: - version "2.2.33" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-2.2.33.tgz#b537c471d34a6651b48f36fdbf29750340e08b50" +mongodb@3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.0.8.tgz#2c1daecac9a0ec2de2f2aea4dc97d76ae70f8951" dependencies: - es6-promise "3.2.1" - mongodb-core "2.1.17" - readable-stream "2.2.7" + mongodb-core "3.0.8" -mongoose@^4.12.3: - version "4.13.7" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-4.13.7.tgz#f760c770e6c8cdf34a6fe8b7443882b5fced1032" +mongoose-legacy-pluralize@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" + +mongoose@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.1.1.tgz#a7e925607e76032e5ef20b3035a357bc8581b45e" dependencies: async "2.1.4" - bson "~1.0.4" - hooks-fixed "2.0.2" - kareem "1.5.0" + bson "~1.0.5" + kareem "2.0.7" lodash.get "4.4.2" - mongodb "2.2.33" - mpath "0.3.0" - mpromise "0.5.5" - mquery "2.3.3" + mongodb "3.0.8" + mongoose-legacy-pluralize "1.0.2" + mpath "0.4.1" + mquery "3.0.0" ms "2.0.0" - muri "1.3.0" regexp-clone "0.0.1" sliced "1.0.1" @@ -7509,17 +7513,13 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" -mpath@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.3.0.tgz#7a58f789e9b5fd3c94520634157960f26bd5ef44" +mpath@0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.4.1.tgz#ed10388430380bf7bbb5be1391e5d6969cb08e89" -mpromise@0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mpromise/-/mpromise-0.5.5.tgz#f5b24259d763acc2257b0a0c8c6d866fd51732e6" - -mquery@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/mquery/-/mquery-2.3.3.tgz#221412e5d4e7290ca5582dd16ea8f190a506b518" +mquery@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.0.0.tgz#e5f387dbabc0b9b69859e550e810faabe0ceabb0" dependencies: bluebird "3.5.0" debug "2.6.9" @@ -7538,10 +7538,6 @@ ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" -muri@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/muri/-/muri-1.3.0.tgz#aeccf3db64c56aa7c5b34e00f95b7878527a4721" - murmurhash-js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" @@ -8556,9 +8552,11 @@ platform@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.4.tgz#6f0fb17edaaa48f21442b3a975c063130f1c3ebd" -please-upgrade-node@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.0.1.tgz#0a681f2c18915e5433a5ca2cd94e0b8206a782db" +please-upgrade-node@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.0.2.tgz#7b9eaeca35aa4a43d6ebdfd10616c042f9a83acc" + dependencies: + semver-compare "^1.0.0" pluralize@^1.2.1: version "1.2.1" @@ -9717,18 +9715,6 @@ readable-stream@2, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stre string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@2.2.7: - version "2.2.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.7.tgz#07057acbe2467b22042d36f98c5ad507054e95b1" - dependencies: - buffer-shims "~1.0.0" - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~1.0.0" - util-deprecate "~1.0.1" - readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -10034,7 +10020,7 @@ require-uncached@^1.0.3: caller-path "^0.1.0" resolve-from "^1.0.0" -require_optional@~1.0.0: +require_optional@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" dependencies: @@ -10285,6 +10271,10 @@ selenium-standalone@^6.11.0: which "^1.2.12" yauzl "^2.5.0" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -10762,9 +10752,9 @@ stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" -staged-git-files@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.0.tgz#1a9bb131c1885601023c7aaddd3d54c22142c526" +staged-git-files@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.1.tgz#37c2218ef0d6d26178b1310719309a16a59f8f7b" static-extend@^0.1.1: version "0.1.2" @@ -10841,6 +10831,10 @@ strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" +string-argv@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736" + string-hash@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" @@ -10875,7 +10869,7 @@ string.prototype.padend@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" -string_decoder@^1.0.0, string_decoder@~1.0.0, string_decoder@~1.0.3: +string_decoder@^1.0.0, string_decoder@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" dependencies: