diff --git a/app/assets/javascripts/discourse/app/components/backup-codes.js b/app/assets/javascripts/discourse/app/components/backup-codes.js index 1109c4cde68..279a659a52a 100644 --- a/app/assets/javascripts/discourse/app/components/backup-codes.js +++ b/app/assets/javascripts/discourse/app/components/backup-codes.js @@ -1,5 +1,6 @@ import discourseComputed from "discourse-common/utils/decorators"; import Component from "@ember/component"; +import { toAsciiPrintable, slugify } from "discourse/lib/utilities"; // https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding function b64EncodeUnicode(str) { @@ -42,6 +43,14 @@ export default Component.extend({ return backupCodes.join("\n").trim(); }, + @discourseComputed() + siteTitleSlug() { + const title = this.siteSettings.title; + const convertedTitle = toAsciiPrintable(title, "discourse"); + + return slugify(convertedTitle); + }, + actions: { copyToClipboard() { this._selectAllBackupCodes(); diff --git a/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js b/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js index aa3e0c11436..3ef9658cc4e 100644 --- a/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js +++ b/app/assets/javascripts/discourse/app/controllers/second-factor-backup-edit.js @@ -1,7 +1,6 @@ import { alias } from "@ember/object/computed"; import { later } from "@ember/runloop"; import Controller from "@ember/controller"; -import discourseComputed from "discourse-common/utils/decorators"; import { SECOND_FACTOR_METHODS } from "discourse/models/user"; import ModalFunctionality from "discourse/mixins/modal-functionality"; @@ -14,13 +13,6 @@ export default Controller.extend(ModalFunctionality, { backupCodes: null, secondFactorMethod: SECOND_FACTOR_METHODS.TOTP, - @discourseComputed("backupEnabled") - generateBackupCodeBtnLabel(backupEnabled) { - return backupEnabled - ? "user.second_factor_backup.regenerate" - : "user.second_factor_backup.enable"; - }, - onShow() { this.setProperties({ loading: false, diff --git a/app/assets/javascripts/discourse/app/lib/utilities.js b/app/assets/javascripts/discourse/app/lib/utilities.js index b848ca7f862..f104c300023 100644 --- a/app/assets/javascripts/discourse/app/lib/utilities.js +++ b/app/assets/javascripts/discourse/app/lib/utilities.js @@ -328,6 +328,28 @@ export function clipboardData(e, canUpload) { return { clipboard, types, canUpload, canPasteHtml }; } +// Replace any accented characters with their ASCII equivalent +// Return the string if it only contains ASCII printable characters, +// otherwise use the fallback +export function toAsciiPrintable(string, fallback) { + if (typeof string.normalize === "function") { + string = string.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + } + + return /^[/\040-\176/]*$/.test(string) ? string : fallback; +} + +export function slugify(string) { + return string + .trim() + .toLowerCase() + .replace(/\s|_+/g, "-") // Replace spaces and underscores with dashes + .replace(/[^\w\-]+/g, "") // Remove non-word characters except for dashes + .replace(/\-\-+/g, "-") // Replace multiple dashes with a single dash + .replace(/^-+/, "") // Remove leading dashes + .replace(/-+$/, ""); // Remove trailing dashes +} + export function toNumber(input) { return typeof input === "number" ? input : parseFloat(input); } diff --git a/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs b/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs index 73b7ad43aab..95ae6664711 100644 --- a/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs +++ b/app/assets/javascripts/discourse/app/templates/components/backup-codes.hbs @@ -4,10 +4,14 @@ {{d-button action=(action "copyToClipboard") class="backup-codes-copy-btn" - icon="copy"}} + icon="copy" + aria-label="user.second_factor_backup.copy_to_clipboard" + title="user.second_factor_backup.copy_to_clipboard"}} - diff --git a/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs b/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs index 8a9e433672f..1d3e96142bb 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/second-factor-backup-edit.hbs @@ -18,18 +18,27 @@ {{/if}}
- {{d-button - class="btn-primary" - action=(action "generateSecondFactorCodes") - type="submit" - disabled=loading - label=generateBackupCodeBtnLabel}} {{#if backupEnabled}} + {{d-button + class="btn-primary" + icon="redo" + action=(action "generateSecondFactorCodes") + type="submit" + isLoading=loading + label="user.second_factor_backup.regenerate"}} {{d-button class="btn-danger" + icon="ban" action=(action "disableSecondFactorBackup") disabled=loading label="user.second_factor_backup.disable"}} + {{else}} + {{d-button + class="btn-primary" + action=(action "generateSecondFactorCodes") + type="submit" + disabled=loading + label="user.second_factor_backup.enable"}} {{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs index bd0d5a26364..d0d1207d751 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit-security-key.hbs @@ -8,11 +8,13 @@ {{d-button action=(action "editSecurityKey") class="btn-primary" - label="user.second_factor.security_key.edit" + label="user.second_factor.security_key.save" }} {{d-button action=(action "disableSecurityKey") class="btn-danger" - label="user.second_factor.security_key.delete" + icon="trash-alt" + aria-label="user.second_factor.disable" + title="user.second_factor.disable" }} {{/d-modal-body}} diff --git a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs index 2a77fceab6d..a7a4f60a294 100644 --- a/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs +++ b/app/assets/javascripts/discourse/app/templates/modal/second-factor-edit.hbs @@ -8,11 +8,13 @@ {{d-button action=(action "editSecondFactor") class="btn-primary" - label="user.second_factor.edit" + label="user.second_factor.save" }} {{d-button action=(action "disableSecondFactor") class="btn-danger" - label="user.second_factor.disable" + icon="trash-alt" + aria-label="user.second_factor.disable" + title="user.second_factor.disable" }} {{/d-modal-body}} diff --git a/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs b/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs index d8b8d95ac02..62cadd25959 100644 --- a/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs +++ b/app/assets/javascripts/discourse/app/templates/preferences-second-factor.hbs @@ -31,6 +31,7 @@

{{i18n "user.second_factor.totp.title"}}

{{d-button action=(action "createTotp") class="btn-primary new-totp" + icon="plus" disabled=loading label="user.second_factor.totp.add"}} {{#each totps as |totp|}} @@ -46,6 +47,8 @@ class="btn-default btn-small btn-icon pad-left no-text edit" disabled=loading icon="pencil-alt" + aria-label="user.second_factor.edit" + title="user.second_factor.edit" }} {{/if}} @@ -58,6 +61,7 @@

{{i18n "user.second_factor.security_key.title"}}

{{d-button action=(action "createSecurityKey") class="btn-primary new-security-key" + icon="plus" disabled=loading label="user.second_factor.security_key.add"}} {{#each security_keys as |security_key|}} @@ -73,6 +77,8 @@ class="btn-default btn-small btn-icon pad-left no-text edit" disabled=loading icon="pencil-alt" + aria-label="user.second_factor.edit" + title="user.second_factor.edit" }} {{/if}} @@ -95,6 +101,8 @@ class="btn-default btn-small btn-icon pad-left no-text edit edit-2fa-backup" disabled=loading icon="pencil-alt" + aria-label="user.second_factor.edit" + title="user.second_factor.edit" }} {{/if}} {{else}} @@ -107,9 +115,9 @@ {{#unless showEnforcedNotice}}
-

{{i18n "user.second_factor.disable_title"}}

{{d-button class="btn-danger" + icon="ban" action=(action "disableAllSecondFactors") disabled=loading label="disable"}} diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss index 869635a250c..587af3c3539 100644 --- a/app/assets/stylesheets/common/base/user.scss +++ b/app/assets/stylesheets/common/base/user.scss @@ -590,8 +590,13 @@ margin-right: 10px; } - .form-horizontal .instructions { - margin-left: 0; + .form-horizontal { + .instructions { + margin-left: 0; + } + .actions { + margin-top: 5px; + } } .backup-codes { diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 336a4cf6c15..7dacfab23b0 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1010,8 +1010,10 @@ en: enable: "Enable" enable_long: "Enable backup codes" manage: "Manage backup codes. You have {{count}} backup codes remaining." - copied_to_clipboard: "Copied to Clipboard" + copy_to_clipboard: "Copy to Clipboard" copy_to_clipboard_error: "Error copying data to Clipboard" + copied_to_clipboard: "Copied to Clipboard" + download_backup_codes: "Download backup codes" remaining_codes: "You have {{count}} backup codes remaining." use: "Use a backup code" enable_prerequisites: "You must enable a primary second factor before generating backup codes." @@ -1038,28 +1040,29 @@ en: oauth_enabled_warning: "Please note that social logins will be disabled once two factor authentication has been enabled on your account." use: "Use Authenticator app" enforced_notice: "You are required to enable two factor authentication before accessing this site." - disable: "disable" - disable_title: "Disable Second Factor" + disable: "Disable" disable_confirm: "Are you sure you want to disable all second factors?" + save: "Save" edit: "Edit" edit_title: "Edit Second Factor" edit_description: "Second Factor Name" - enable_security_key_description: "When you have your physical security key prepared press the Register button below." + enable_security_key_description: | + When you have your
hardware security key prepared, press the Register button below. totp: title: "Token-Based Authenticators" - add: "New Authenticator" + add: "Add Authenticator" default_name: "My Authenticator" name_and_code_required_error: "You must provide a name and the code from your authenticator app." security_key: register: "Register" title: "Security Keys" - add: "Register Security Key" + add: "Add Security Key" default_name: "Main Security Key" not_allowed_error: "The security key registration process either timed out or was cancelled." already_added_error: "You have already registered this security key. You don’t have to register it again." edit: "Edit Security Key" + save: "Save" edit_description: "Security Key Name" - delete: "Delete" name_required_error: "You must provide a name for your security key." change_about: diff --git a/plugins/discourse-presence/assets/javascripts/discourse/templates/components/composer-presence-display.hbs b/plugins/discourse-presence/assets/javascripts/discourse/templates/components/composer-presence-display.hbs index 453b13d07a7..1f4b4ec7272 100644 --- a/plugins/discourse-presence/assets/javascripts/discourse/templates/components/composer-presence-display.hbs +++ b/plugins/discourse-presence/assets/javascripts/discourse/templates/components/composer-presence-display.hbs @@ -8,12 +8,14 @@ {{#if isReply ~}} - {{i18n 'presence.replying'}} + {{i18n "presence.replying"}} {{~else~}} - {{i18n 'presence.editing'}} - {{~/if}}{{!-- (using comment to stop whitespace) - --}}{{!-- - --}}... + {{i18n "presence.editing"}} + {{~/if}} + {{!-- (using comment to stop whitespace) +--}}{{!-- +--}} + ...
{{/if}} diff --git a/plugins/lazy-yt/assets/javascripts/lazyYT.js b/plugins/lazy-yt/assets/javascripts/lazyYT.js index 897c15497d2..651ea9df48c 100644 --- a/plugins/lazy-yt/assets/javascripts/lazyYT.js +++ b/plugins/lazy-yt/assets/javascripts/lazyYT.js @@ -1,40 +1,40 @@ /*! -* lazyYT (lazy load YouTube videos) -* v1.0.1 - 2014-12-30 -* (CC) This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -* http://creativecommons.org/licenses/by-sa/4.0/ -* Contributors: https://github.com/tylerpearson/lazyYT/graphs/contributors || https://github.com/daugilas/lazyYT/graphs/contributors -* -* Usage:
loading...
-* -* Note: Discourse has forked this from the original, beware when updating the file. -* -*/ + * lazyYT (lazy load YouTube videos) + * v1.0.1 - 2014-12-30 + * (CC) This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. + * http://creativecommons.org/licenses/by-sa/4.0/ + * Contributors: https://github.com/tylerpearson/lazyYT/graphs/contributors || https://github.com/daugilas/lazyYT/graphs/contributors + * + * Usage:
loading...
+ * + * Note: Discourse has forked this from the original, beware when updating the file. + * + */ -(function ($) { - 'use strict'; +(function($) { + "use strict"; function setUp($el, settings) { - var width = $el.data('width'), - height = $el.data('height'), - ratio = ($el.data('ratio')) ? $el.data('ratio') : settings.default_ratio, - id = $el.data('youtube-id'), - title = $el.data('youtube-title'), - padding_bottom, - innerHtml = [], - $thumb, - thumb_img, - youtube_parameters = $el.data('parameters') || ''; + var width = $el.data("width"), + height = $el.data("height"), + ratio = $el.data("ratio") ? $el.data("ratio") : settings.default_ratio, + id = $el.data("youtube-id"), + title = $el.data("youtube-title"), + padding_bottom, + innerHtml = [], + $thumb, + thumb_img, + youtube_parameters = $el.data("parameters") || ""; ratio = ratio.split(":"); // width and height might override default_ratio value - if (typeof width === 'number' && typeof height === 'number') { + if (typeof width === "number" && typeof height === "number") { $el.width(width); - padding_bottom = height + 'px'; - } else if (typeof width === 'number') { + padding_bottom = height + "px"; + } else if (typeof width === "number") { $el.width(width); - padding_bottom = (width * ratio[1] / ratio[0]) + 'px'; + padding_bottom = (width * ratio[1]) / ratio[0] + "px"; } else { width = $el.width(); @@ -43,7 +43,7 @@ width = $el.parent().width(); } - padding_bottom = (ratio[1] / ratio[0] * 100) + '%'; + padding_bottom = (ratio[1] / ratio[0]) * 100 + "%"; } // @@ -53,81 +53,108 @@ // Play button from YouTube (exactly as it is in YouTube) innerHtml.push('
'); - innerHtml.push(''); - innerHtml.push(''); - innerHtml.push(''); - innerHtml.push(''); - innerHtml.push('
'); // end of .ytp-large-play-button + if (width <= 640) + innerHtml.push(' style="transform: scale(0.563888888888889);"'); + innerHtml.push(">"); + innerHtml.push(""); + innerHtml.push( + '' + ); + innerHtml.push( + '' + ); + innerHtml.push(""); + innerHtml.push("
"); // end of .ytp-large-play-button - innerHtml.push(''); // end of .ytp-thumbnail + innerHtml.push(""); // end of .ytp-thumbnail // Video title (info bar) innerHtml.push('
'); innerHtml.push('
'); innerHtml.push(''); // .html5-title - innerHtml.push('
'); // .html5-title-text-wrapper - innerHtml.push('
'); // end of Video title .html5-info-bar + innerHtml.push(""); + innerHtml.push(""); // .html5-title + innerHtml.push(""); // .html5-title-text-wrapper + innerHtml.push(""); // end of Video title .html5-info-bar - $el.css({ - 'padding-bottom': padding_bottom - }) - .html(innerHtml.join('')); + $el + .css({ + "padding-bottom": padding_bottom + }) + .html(innerHtml.join("")); if (width > 640) { - thumb_img = 'maxresdefault.jpg'; + thumb_img = "maxresdefault.jpg"; } else if (width > 480) { - thumb_img = 'sddefault.jpg'; + thumb_img = "sddefault.jpg"; } else if (width > 320) { - thumb_img = 'hqdefault.jpg'; + thumb_img = "hqdefault.jpg"; } else if (width > 120) { - thumb_img = 'mqdefault.jpg'; - } else if (width === 0) { // sometimes it fails on fluid layout - thumb_img = 'hqdefault.jpg'; + thumb_img = "mqdefault.jpg"; + } else if (width === 0) { + // sometimes it fails on fluid layout + thumb_img = "hqdefault.jpg"; } else { - thumb_img = 'default.jpg'; + thumb_img = "default.jpg"; } - $thumb = $el.find('.ytp-thumbnail').css({ - 'background-image': ['url(https://img.youtube.com/vi/', id, '/', thumb_img, ')'].join('') - }) - .addClass('lazyYT-image-loaded') - .on('click', function (e) { - e.preventDefault(); + $thumb = $el + .find(".ytp-thumbnail") + .css({ + "background-image": [ + "url(https://img.youtube.com/vi/", + id, + "/", + thumb_img, + ")" + ].join("") + }) + .addClass("lazyYT-image-loaded") + .on("click", function(e) { + e.preventDefault(); - if (!$el.hasClass('lazyYT-video-loaded') && $thumb.hasClass('lazyYT-image-loaded')) { - $el.html('') - .addClass('lazyYT-video-loaded'); - } - - if (settings.onPlay) { - settings.onPlay(e, $el); - } - }); + if ( + !$el.hasClass("lazyYT-video-loaded") && + $thumb.hasClass("lazyYT-image-loaded") + ) { + $el + .html( + '' + ) + .addClass("lazyYT-video-loaded"); + } + if (settings.onPlay) { + settings.onPlay(e, $el); + } + }); } - $.fn.lazyYT = function (newSettings) { + $.fn.lazyYT = function(newSettings) { var defaultSettings = { - default_ratio: '16:9', + default_ratio: "16:9", callback: null, // ToDO execute callback if given - container_class: 'lazyYT-container' + container_class: "lazyYT-container" }; var settings = $.extend(defaultSettings, newSettings); - return this.each(function () { + return this.each(function() { var $el = $(this).addClass(settings.container_class); setUp($el, settings); }); }; - })(jQuery); diff --git a/test/javascripts/lib/utilities-test.js b/test/javascripts/lib/utilities-test.js index 46609895a2e..f3d14a2657e 100644 --- a/test/javascripts/lib/utilities-test.js +++ b/test/javascripts/lib/utilities-test.js @@ -9,6 +9,8 @@ import { setDefaultHomepage, caretRowCol, setCaretPosition, + toAsciiPrintable, + slugify, fillMissingDates, inCodeBlock } from "discourse/lib/utilities"; @@ -175,6 +177,49 @@ QUnit.test("caretRowCol", assert => { document.body.removeChild(textarea); }); +QUnit.test("toAsciiPrintable", assert => { + const accentedString = "Créme_Brûlée!"; + const unicodeString = "談話"; + + assert.equal( + toAsciiPrintable(accentedString, "discourse"), + "Creme_Brulee!", + "it replaces accented characters with the appropriate ASCII equivalent" + ); + + assert.equal( + toAsciiPrintable(unicodeString, "discourse"), + "discourse", + "it uses the fallback string when unable to convert" + ); + + assert.strictEqual( + typeof toAsciiPrintable(unicodeString), + "undefined", + "it returns undefined when unable to convert and no fallback is provided" + ); +}); + +QUnit.test("slugify", assert => { + const asciiString = "--- 0__( Some-cool Discourse Site! )__0 --- "; + const accentedString = "Créme_Brûlée!"; + const unicodeString = "談話"; + + assert.equal( + slugify(asciiString), + "0-some-cool-discourse-site-0", + "it properly slugifies an ASCII string" + ); + + assert.equal( + slugify(accentedString), + "crme-brle", + "it removes accented characters" + ); + + assert.equal(slugify(unicodeString), "", "it removes unicode characters"); +}); + QUnit.test("fillMissingDates", assert => { const startDate = "2017-11-12"; // YYYY-MM-DD const endDate = "2017-12-12"; // YYYY-MM-DD