From 925af115bb1adb6d7f3da39236c41fbfd3b3bdb1 Mon Sep 17 00:00:00 2001 From: Ludovic Dubost Date: Sun, 12 Apr 2020 15:51:59 +0200 Subject: [PATCH] Customize directory for alpha meet instance --- customize/application_config.js | 37 +++ customize/index.html | 16 ++ customize/messages.js | 197 ++++++++++++++++ customize/pages/index.js | 172 ++++++++++++++ customize/src/less2/pages/page-index.less | 265 ++++++++++++++++++++++ customize/translations/messages.js | 19 ++ 6 files changed, 706 insertions(+) create mode 100644 customize/application_config.js create mode 100644 customize/index.html create mode 100755 customize/messages.js create mode 100644 customize/pages/index.js create mode 100644 customize/src/less2/pages/page-index.less create mode 100644 customize/translations/messages.js diff --git a/customize/application_config.js b/customize/application_config.js new file mode 100644 index 000000000..967e6d169 --- /dev/null +++ b/customize/application_config.js @@ -0,0 +1,37 @@ +/* + * You can override the configurable values from this file. + * The recommended method is to make a copy of this file (/customize.dist/application_config.js) + in a 'customize' directory (/customize/application_config.js). + * If you want to check all the configurable values, you can open the internal configuration file + but you should not change it directly (/common/application_config_internal.js) +*/ +define(['/common/application_config_internal.js'], function (AppConfig) { + // Example: If you want to remove the survey link in the menu: + // AppConfig.surveyURL = ""; + /* Select the buttons displayed on the main page to create new collaborative sessions. + * Removing apps from the list will prevent users from accessing them. They will instead be + * redirected to the drive. + * You should never remove the drive from this list. + */ + AppConfig.availablePadTypes = ['drive', 'teams', 'pad', 'sheet', 'code', 'slide', 'poll', 'kanban', 'whiteboard', + 'meet', 'oodoc', 'ooslide', 'file', 'todo', 'contacts']; + /* The registered only types are apps restricted to registered users. + * You should never remove apps from this list unless you know what you're doing. The apps + * listed here by default can't work without a user account. + * You can however add apps to this list. The new apps won't be visible for unregistered + * users and these users will be redirected to the login page if they still try to access + * the app + */ + AppConfig.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'notifications']; + + /* CryptPad is available is multiple languages, but only English and French are maintained + * by the developers. The other languages may be outdated, and any missing string for a langauge + * will use the english version instead. You can customize the langauges you want to be available + * on your instance by removing them from the following list. + * An empty list will load all available languages for CryptPad. The list of available languages + * can be found at the top of the file `/customize.dist/messages.js`. The list should only + * contain languages code ('en', 'fr', 'de', 'pt-br', etc.), not their full name. + */ + AppConfig.availableLanguages = ['en']; + return AppConfig; +}); diff --git a/customize/index.html b/customize/index.html new file mode 100644 index 000000000..10e1545de --- /dev/null +++ b/customize/index.html @@ -0,0 +1,16 @@ + + + + + CryptPad Meet Alpha: Experimental Zero Knowledge Conferencing + + + + + + + + diff --git a/customize/messages.js b/customize/messages.js new file mode 100755 index 000000000..d42d8fa46 --- /dev/null +++ b/customize/messages.js @@ -0,0 +1,197 @@ +(function () { +// add your module to this map so it gets used +var map = { + 'ca': 'Català', + 'de': 'Deutsch', + 'el': 'Ελληνικά', + 'es': 'Español', + 'fi': 'Suomalainen', + 'fr': 'Français', + //'hi': 'हिन्दी', + 'it': 'Italiano', + 'nb': 'Norwegian Bokmål', + //'pl': 'Polski', + 'pt-br': 'Português do Brasil', + 'ro': 'Română', + 'ru': 'Русский', + //'sv': 'Svenska', + //'te': 'తెలుగు', + 'zh': '繁體中文', + //'nl': 'Nederlands' +}; + +var messages = {}; +var LS_LANG = "CRYPTPAD_LANG"; +var getStoredLanguage = function () { return localStorage && localStorage.getItem(LS_LANG); }; +var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; }; +var getLanguage = messages._getLanguage = function () { + if (window.cryptpadLanguage) { return window.cryptpadLanguage; } + if (getStoredLanguage()) { return getStoredLanguage(); } + var l = getBrowserLanguage(); + // Edge returns 'fr-FR' --> transform it to 'fr' and check again + return map[l] ? l : + (map[l.split('-')[0]] ? l.split('-')[0] : + (map[l.split('_')[0]] ? l.split('_')[0] : 'en')); +}; +var language = getLanguage(); + +// Translations files were migrated from requirejs modules to json. +// To avoid asking every administrator to update their customized translation files, +// we use a requirejs map to redirect the old path to the new one and to use the +// requirejs json plugin +var reqPaths = { + "/common/translations/messages.js":"json!/common/translations/messages.json" +}; +Object.keys(map).forEach(function (k) { + reqPaths["/common/translations/messages."+k+".js"] = "json!/common/translations/messages."+k+".json"; +}); +require.config({ + map: { + "*": reqPaths + } +}); + +var req = [ + '/common/common-util.js', + '/customize/application_config.js', + '/customize/translations/messages.js' +]; +if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); } + +define(req, function(Util, AppConfig, Default, Language) { + map.en = 'English'; + var defaultLanguage = 'en'; + + if (AppConfig.availableLanguages) { + if (AppConfig.availableLanguages.indexOf(language) === -1) { + language = defaultLanguage; + Language = Default; + localStorage.setItem(LS_LANG, language); + } + Object.keys(map).forEach(function (l) { + if (l === defaultLanguage) { return; } + if (AppConfig.availableLanguages.indexOf(l) === -1) { + delete map[l]; + } + }); + } + + var extend = function (a, b) { + for (var k in b) { + if (Util.isObject(b[k])) { + a[k] = Util.isObject(a[k]) ? a[k] : {}; + extend(a[k], b[k]); + continue; + } + if (Array.isArray(b[k])) { + a[k] = b[k].slice(); + continue; + } + a[k] = b[k] || a[k]; + } + }; + + extend(messages, Default); + if (Language && language !== defaultLanguage) { + // Add the translated keys to the returned object + extend(messages, Language); + } + + messages._languages = map; + messages._languageUsed = language; + + messages._checkTranslationState = function (cb) { + if (typeof(cb) !== "function") { return; } + var allMissing = []; + var reqs = []; + Object.keys(map).forEach(function (code) { + if (code === defaultLanguage) { return; } + reqs.push('/customize/translations/messages.' + code + '.js'); + }); + require(reqs, function () { + var langs = arguments; + Object.keys(map).forEach(function (code, i) { + if (code === defaultLanguage) { return; } + var translation = langs[i]; + var missing = []; + var checkInObject = function (ref, translated, path) { + var updated = {}; + Object.keys(ref).forEach(function (k) { + if (/^updated_[0-9]+_/.test(k) && !translated[k]) { + var key = k.split('_').slice(2).join('_'); + // Make sure we don't already have an update for that key. It should not happen + // but if it does, keep the latest version + if (updated[key]) { + var ek = updated[key]; + if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; } + } + updated[key] = k; + } + }); + Object.keys(ref).forEach(function (k) { + if (/^_/.test(k) || k === 'driveReadme') { return; } + var nPath = path.slice(); + nPath.push(k); + if (!translated[k] || updated[k]) { + if (updated[k]) { + var uPath = path.slice(); + uPath.unshift('out'); + missing.push([code, nPath, 2, uPath.join('.') + '.' + updated[k]]); + return; + } + return void missing.push([code, nPath, 1]); + } + if (typeof ref[k] !== typeof translated[k]) { + return void missing.push([code, nPath, 3]); + } + if (typeof ref[k] === "object" && !Array.isArray(ref[k])) { + checkInObject(ref[k], translated[k], nPath); + } + }); + Object.keys(translated).forEach(function (k) { + if (/^_/.test(k) || k === 'driveReadme') { return; } + var nPath = path.slice(); + nPath.push(k); + if (typeof ref[k] === "undefined") { + missing.push([code, nPath, 0]); + } + }); + }; + checkInObject(Default, translation, []); + // Push the removals at the end + missing.sort(function (a, b) { + if (a[2] === 0 && b[2] !== 0) { return 1; } + if (a[2] !== 0 && b[2] === 0) { return -1; } + return 0; + }); + Array.prototype.push.apply(allMissing, missing); // Destructive concat + }); + cb(allMissing); + }); + }; + + // Get keys with parameters + messages._getKey = function (key, argArray) { + if (!messages[key]) { return '?'; } + var text = messages[key]; + if (typeof(text) === 'string') { + return text.replace(/\{(\d+)\}/g, function (str, p1) { + if (typeof(argArray[p1]) === 'string' || typeof(argArray[p1]) === "number") { + return argArray[p1]; + } + console.error("Only strings and numbers can be used in _getKey params!"); + return ''; + }); + } else { + return text; + } + }; + + messages.driveReadme = '["BODY",{"class":"cke_editable cke_editable_themed cke_contents_ltr cke_show_borders","contenteditable":"true","spellcheck":"false","style":"color: rgb(51, 51, 51);"},' + + '[["H1",{},["'+messages.readme_welcome+'"]],["P",{},["'+messages.readme_p1+'"]],["P",{},["'+messages.readme_p2+'"]],["HR",{},[]],["H2",{},["'+messages.readme_cat1+'",["BR",{},[]]]],["UL",{},[["LI",{},["'+messages._getKey("readme_cat1_l1", ['",["STRONG",{},["'+messages.newButton+'"]],"', '",["STRONG",{},["'+messages.type.pad+'"]],"'])+'"]],["LI",{},["'+messages.readme_cat1_l2+'"]],["LI",{},["'+messages._getKey("readme_cat1_l3", ['",["STRONG",{},["'+messages.fm_unsortedName+'"]],"'])+'",["UL",{},[["LI",{},["'+messages._getKey("readme_cat1_l3_l1", ['",["STRONG",{},["'+messages.fm_rootName+'"]],"'])+'"]],["LI",{},["'+messages.readme_cat1_l3_l2+'"]]]]]],["LI",{},["'+messages._getKey("readme_cat1_l4", ['",["STRONG",{},["'+messages.fm_trashName+'"]],"'])+'",["BR",{},[]]]]]],["P",{},[["BR",{},[]]]],["H2",{},["'+messages.readme_cat2+'",["BR",{},[]]]],["UL",{},[["LI",{},["'+messages._getKey("readme_cat2_l1", ['",["STRONG",{},["'+messages.shareButton+'"]],"', '",["STRONG",{},["'+messages.edit+'"]],"', '",["STRONG",{},["'+messages.view+'"]],"'])+'"]],["LI",{},["'+messages.readme_cat2_l2+'"]]]],["P",{},[["BR",{},[]]]],["H2",{},["'+messages.readme_cat3+'"]],["UL",{},[["LI",{},["'+messages.readme_cat3_l1+'"]],["LI",{},["'+messages.readme_cat3_l2+'"]],["LI",{},["'+messages.readme_cat3_l3+'",["BR",{},[]]]]]]],' + + '{"metadata":{"defaultTitle":"' + messages.driveReadmeTitle + '","title":"' + messages.driveReadmeTitle + '"}}]'; + + return messages; + +}); +}()); diff --git a/customize/pages/index.js b/customize/pages/index.js new file mode 100644 index 000000000..a431715f6 --- /dev/null +++ b/customize/pages/index.js @@ -0,0 +1,172 @@ +define([ + 'jquery', + '/api/config', + '/common/hyperscript.js', + '/common/common-feedback.js', + '/common/common-interface.js', + '/common/textFit.min.js', + '/customize/messages.js', + '/customize/application_config.js', + '/common/outer/local-store.js', + '/customize/pages.js' +], function ($, Config, h, Feedback, UI, TextFit, Msg, AppConfig, LocalStore, Pages) { + var urlArgs = Config.requireConf.urlArgs; + + var isAvailableType = function (x) { + if (!Array.isArray(AppConfig.availablePadTypes)) { return true; } + return AppConfig.availablePadTypes.indexOf(x) !== -1; + }; + + var checkRegisteredType = function (x) { + // Return true if we're registered or if the app is not registeredOnly + if (LocalStore.isLoggedIn()) { return true; } + if (!Array.isArray(AppConfig.registeredOnlyTypes)) { return true; } + return AppConfig.registeredOnlyTypes.indexOf(x) === -1; + }; + + return function () { + var icons = [ + [ 'meet', Msg.type.meet], + ].filter(function (x) { + return isAvailableType(x[0]); + }) + .map(function (x) { + var s = 'div.bs-callout.cp-callout-' + x[0]; + var isEnabled = checkRegisteredType(x[0]); + //if (i > 2) { s += '.cp-more.cp-hidden'; } + var icon = AppConfig.applicationsIcon[x[0]]; + var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa'; + var href = '/'+ x[0] +'/'; + var attr = isEnabled ? { href: href } : { + onclick: function () { + sessionStorage.redirectTo = href; + window.location.href = '/login/'; + } + }; + if (!isEnabled) { + s += '.cp-app-disabled'; + attr.title = Msg.mustLogin; + } + return h('a', [ + attr, + h(s, [ + h('i.' + font + '.' + icon), + h('div.pad-button-text', { + style: 'width:120px;height:30px;' + }, [ x[1] ]) + ]) + ]); + }); + + icons.forEach(function (a) { + setTimeout(function () { + TextFit($(a).find('.pad-button-text')[0], {minFontSize: 13, maxFontSize: 18}); + }); + }); + UI.addTooltips(); + + /* + var more = icons.length < 4? undefined: h('div.bs-callout.cp-callout-more', [ + h('div.cp-callout-more-lessmsg.cp-hidden', [ + "see less ", + h('i.fa.fa-caret-up') + ]), + h('div.cp-callout-more-moremsg', [ + "see more ", + h('i.fa.fa-caret-down') + ]), + { + onclick: function () { + if (showingMore) { + $('.cp-more, .cp-callout-more-lessmsg').addClass('cp-hidden'); + $('.cp-callout-more-moremsg').removeClass('cp-hidden'); + } else { + $('.cp-more, .cp-callout-more-lessmsg').removeClass('cp-hidden'); + $('.cp-callout-more-moremsg').addClass('cp-hidden'); + } + showingMore = !showingMore; + } + } + ]);*/ + + var _link = h('a', { + href: "https://opencollective.com/cryptpad/", + target: '_blank', + rel: 'noopener', + }); + + var crowdFunding = h('button', [ + Msg.crowdfunding_button + ]); + + $(crowdFunding).click(function () { + _link.click(); + Feedback.send('HOME_SUPPORT_CRYPTPAD'); + }); + + var blocks = h('div.container',[ + h('div.row.justify-content-sm-center',[ + h('div.col-12.col-sm-4.cp-index-block.cp-index-block-host', h('div', [ + Pages.setHTML(h('span'), Msg.home_host), + h('div.cp-img-container', [ + h('img.agpl', { + src: "/customize/images/AGPL.png", + title: Msg.home_host_agpl + }), + h('a.img', { + href: 'https://blog.cryptpad.fr/2018/11/13/CryptPad-receives-NGI-Startup-Award/', + target: '_blank' + }, h('img.ngi', { + src: "/customize/images/ngi.png", + title: Msg.home_ngi + })) + ]) + ])), + h('div.col-12.col-sm-4.cp-index-block.cp-index-block-product', h('div', [ + Msg.home_product + ])), + AppConfig.disableCrowdfundingMessages ? undefined : h('div.col-12.col-sm-4.cp-index-block.cp-index-block-help', h('div', [ + Msg.crowdfunding_home1, + h('br'), + Msg.crowdfunding_home2, + h('br'), + crowdFunding, + _link + ])), + ]) + ]); + + return [ + h('div#cp-main', [ + Pages.infopageTopbar(), + h('div.container.cp-container', [ + h('div.row', [ + h('div.col-12.col-sm-12',[ + h('h2', Msg.meet_home), + h('h4', Msg.meet_warning), + h('br'), + h('br'), + ]), + h('div.cp-title.col-12.col-sm-6', [ + h('img', { src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs }), + h('h1', 'CryptPad'), + h('p', Msg.main_catch_phrase) + ]), + h('div.col-12.col-sm-6.cp-app-grid', [ + icons, + //more + ]), + ]), + blocks, + /*h('div.row', [ + h('div.cp-crowdfunding', [ + crowdFunding + ]) + ])*/ + ]), + ]), + Pages.infopageFooter(), + ]; + }; +}); + diff --git a/customize/src/less2/pages/page-index.less b/customize/src/less2/pages/page-index.less new file mode 100644 index 000000000..dcbf15ed7 --- /dev/null +++ b/customize/src/less2/pages/page-index.less @@ -0,0 +1,265 @@ +@import (reference) "../include/infopages.less"; +@import (reference) "../include/colortheme-all.less"; + +&.cp-page-index { + .infopages_main(); + + @background_lighter: rgba(0,0,0,0.1); + @background_darker: rgba(0,0,0,0.4); + #cp-main { + color: #FFF; + background: linear-gradient( @background_darker, @background_lighter ), url('/customize/bg14.jpg'); + background-size: cover; + background-position: center; + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + .container { + @media only screen and (max-device-width : 576px) { + margin-top: 6em; + } + } + & > .cp-container { + flex: 1; + display: flex; + flex-flow: column; + justify-content: space-around; + justify-content: space-evenly; + } + } + body { + font-family: "Open Sans", Helvetica; + } + .cp-right { + .cp-register-btn { + padding: 0.5em 1em 0.7em 1em; + border: 2px solid #fff; + &:hover { + transform: scale(1.05); + } + } + .cp-login-btn { + color: #fff; + padding: 0.5em 1em 0.7em 1em; + &:hover { + transform: scale(1.05); + } + } + } + .cp-title { + display: flex; + align-items: center; + flex-direction: column; + margin-top: 1.5em; + img { + height: 20vh; + margin-bottom: 1.5em; + } + margin-left: 0; + margin-bottom: 20px; + h1 { + font-family: "Neuropolitical"; + //font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", Times, serif; + //font-family: "Raleway"; + font-size: 45px; + } + p { + //font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + font-size: 20px; + //font-style: italic; + } + } + .navbar { + background: transparent; + width: 100%; + @media only screen and (max-device-width: 991px) { + margin-top: 0; + } + .navbar-brand { + background-image: url(/customize/CryptPad-white-logo.svg); + } + a { + color: #fff; + &:visited { + color: rgba(255,255,255,.9); + }; + } + .nav-link { + &:hover { + color: inherit; + transform: scale(1.05); + }; + } + .cp-register-btn { + border: 2px solid #fff; + } + .navbar-toggler { + margin-top: 10px; + color: #fff; + } + } + @callout-padding: 15px; + a:hover { + text-decoration: none; + } + + .cp-app-grid { + display: flex; + flex-wrap: wrap; + & > a { + margin: 20px; + } + align-items: center; + } + @icons-size: 120px; + @icons-text-size: 30px; + .bs-callout { + display: flex; + align-items: stretch; + margin: 0; + background: rgba(255,255,255,0.6); + color: black; + transition: all .1s ease-in-out; + box-sizing: border-box; + position: relative; + flex-flow: column; + height: @icons-size; + width: @icons-size; + a { + color: black; + &:hover { text-decoration-line: none; } + } + div h4 { + @media only screen and (min-device-width: 576px) and (max-device-width: 767px) { + font-size: 1.3em; + } + } + div { + flex: 1; + min-height: 0; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 16px; + } + .fa, .cptools { + display: flex; + align-items: center; + font-size: @icons-size - 60px; + justify-content: center; + width: @icons-size; + height: @icons-size - @icons-text-size; + transition: width 0.1s; + color: #fff; + } + &.cp-app-disabled { + cursor: not-allowed !important; + opacity: 0.5; + } + } + h4 { + margin: 0; + } + .cp-callout-more-moremsg,.cp-callout-more-lessmsg { + transform: none !important; + } + .bs-callout+.bs-callout { + margin-top: -5px; + } + + .bs-callout:hover { + //color: white; + transform: scale(1.05); + cursor: pointer; + } + .bs-callout:hover.cp-callout-more { + transform: none !important; + } + .cp-callout-pad .cptools { background-color: @colortheme_pad-bg; } + .cp-callout-code .cptools { background-color: @colortheme_code-bg; } + .cp-callout-slide .cptools { background-color: @colortheme_slide-bg; } + .cp-callout-poll .cptools { background-color: @colortheme_poll-bg; } + .cp-callout-kanban .cptools { background-color: @colortheme_kanban-bg; } + .cp-callout-whiteboard .cptools { background-color: @colortheme_whiteboard-bg; } + .cp-callout-drive .fa { background-color: @colortheme_drive-bg; } + .cp-callout-sheet .fa { background-color: @colortheme_oocell-bg; } + .cp-hidden { display: none !important; } + .cp-callout-more { + display: inline-block; + align-content: center; + height: 2em; + border-radius: 1em; + margin-left: auto; + margin-right: auto; + margin-top: 0; + background: none; + width: 100%; + div { + .infopages_link(); + color: #fff; + .fa, .cptools { + font-size: inherit; + padding: 0; + width: 1em; + padding-left: 5px; + } + } + } + + .cp-index-block-help { + button { + outline: none; + background-color: @colortheme_logo-2; + color: @colortheme_base; + border: none; + padding: 10px 20px; + border-radius: 44px; + cursor: pointer; + &:hover { + background-color: lighten(@colortheme_logo-2, 3%); + } + } + } + + .cp-index-block { + min-height: 100%; + & > div { + background: rgba(0,0,0,0.5); + margin: 0 5px; + padding: 15px 10px; + height: 100%; + display: flex; + flex-flow: column; + justify-content: space-evenly; + } + .cp-img-container { + display: flex; + } + img, a.img { + margin: auto; + background: white; + &.agpl { + max-height: 50px; + } + &.ngi { + max-height: 100px; + } + } + } + + @media (min-width: 576px) and (max-width: 767px) { + .container { + padding-left: 0; + padding-right: 0; + } + div#cp-main.cp-page-index .cp-topbar .navbar-toggler-left { + left: 5px; + } + } +} + diff --git a/customize/translations/messages.js b/customize/translations/messages.js new file mode 100644 index 000000000..6cd1aea7d --- /dev/null +++ b/customize/translations/messages.js @@ -0,0 +1,19 @@ +/* + * You can override the translation text using this file. + * The recommended method is to make a copy of this file (/customize.dist/translations/messages.{LANG}.js) + in a 'customize' directory (/customize/translations/messages.{LANG}.js). + * If you want to check all the existing translation keys, you can open the internal language file + but you should not change it directly (/common/translations/messages.{LANG}.js) +*/ +define(['/common/translations/messages.js'], function (Messages) { + // Replace the existing keys in your copied file here: + // Messages.button_newpad = "New Rich Text Document"; + Messages.home_host = "This instance is an alpha instance of CryptPad Meet, a version of CryptPad featuring a Zero Knowledge audio and video conferencing pad."; + Messages.meet_home = "This service features an experimental end-to-end encrypted video and audio conferencing service"; + Messages.meet_warning = "WARNING ! While the plan is to have the same security features as for pads, the experimental conferencing service is not yet secure. The current transport is end-to-end encrypted but uses the same key for all conferences. All accounts and data on this server can be deleted at any time"; + Messages.main_catch_phrase = "CryptPad Meet - Alpha"; + Messages.meet = "Meet"; + Messages.button_newmeet = "New Meeting"; + return Messages; +}); +