diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js index c9edcf197..1add0314d 100644 --- a/app/javascript/core/settings.js +++ b/app/javascript/core/settings.js @@ -3,8 +3,6 @@ const { length } = require('stringz'); const { delegate } = require('rails-ujs'); -import { processBio } from 'flavours/glitch/util/bio_metadata'; - delegate(document, '.account_display_name', 'input', ({ target }) => { const nameCounter = document.querySelector('.name-counter'); @@ -17,8 +15,7 @@ delegate(document, '.account_note', 'input', ({ target }) => { const noteCounter = document.querySelector('.note-counter'); if (noteCounter) { - const noteWithoutMetadata = processBio(target.value).text; - noteCounter.textContent = 500 - length(noteWithoutMetadata); + noteCounter.textContent = 500 - length(target.value); } }); diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js index 59d9477d6..174df0cc9 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.js +++ b/app/javascript/flavours/glitch/features/account/components/header.js @@ -7,9 +7,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Avatar from 'flavours/glitch/components/avatar'; import IconButton from 'flavours/glitch/components/icon_button'; -import emojify from 'flavours/glitch/util/emoji'; import { me } from 'flavours/glitch/util/initial_state'; -import { processBio } from 'flavours/glitch/util/bio_metadata'; import classNames from 'classnames'; const messages = defineMessages({ @@ -83,7 +81,7 @@ export default class Header extends ImmutablePureComponent { actionBtn = ''; } - const { text, metadata } = processBio(account.get('note_emojified')); + const content = { __html: account.get('note_emojified') }; return (
tag at the beginning of the bio. It replaces
- the escaped character entities `'` and `"` with single or
- double quotes, respectively, prior to processing. However, no other
- escaped characters are replaced, not even those which might have an
- impact on the syntax otherwise. These minor allowances are provided
- because the Mastodon backend will insert these things automatically
- into a bio before sending it through the API, so it is important we
- account for them. Aside from this, the YAML frontmatter must be the
- very first thing in the bio, leading with three consecutive hyphen-
- minues (`---`), and ending with the same or, alternatively, instead
- with three periods (`...`). No limits have been set with respect to
- the number of characters permitted in the frontmatter, although one
- should note that only limited space is provided for them in the UI.
- ¶ The regular expression used to check the existence of, and then
- process, the YAML frontmatter has been split into a number of small
- components in the code below, in the vain hope that it will be much
- easier to read and to maintain. I leave it to the future readers of
- this code to determine the extent of my successes in this endeavor.
-
- UPDATE 19 Oct 2017: We no longer allow character escapes inside our
- double-quoted strings for ease of processing. We now internally use
- the name "ƔAML" in our code to clarify that this is Not Quite YAML.
-
- Sending love + warmth eternal,
- - kibigo [@kibi@glitch.social]
-
-\*********************************************************************/
-
-/* "u" FLAG COMPATABILITY */
-
-let compat_mode = false;
-try {
- new RegExp('.', 'u');
-} catch (e) {
- compat_mode = true;
-}
-
-/* CONVENIENCE FUNCTIONS */
-
-const unirex = str => compat_mode ? new RegExp(str) : new RegExp(str, 'u');
-const rexstr = exp => '(?:' + exp.source + ')';
-
-/* CHARACTER CLASSES */
-
-const DOCUMENT_START = /^/;
-const DOCUMENT_END = /$/;
-const ALLOWED_CHAR = unirex( // `c-printable` in the YAML 1.2 spec.
- compat_mode ? '[\t\n\r\x20-\x7e\x85\xa0-\ufffd]' : '[\t\n\r\x20-\x7e\x85\xa0-\ud7ff\ue000-\ufffd\u{10000}-\u{10FFFF}]'
-);
-const WHITE_SPACE = /[ \t]/;
-const LINE_BREAK = /\r?\n|\r|
/;
-const INDICATOR = /[-?:,[\]{}*!|>'"%@`]/;
-const FLOW_CHAR = /[,[\]{}]/;
-
-/* NEGATED CHARACTER CLASSES */
-
-const NOT_WHITE_SPACE = unirex('(?!' + rexstr(WHITE_SPACE) + ')[^]');
-const NOT_LINE_BREAK = unirex('(?!' + rexstr(LINE_BREAK) + ')[^]');
-const NOT_INDICATOR = unirex('(?!' + rexstr(INDICATOR) + ')[^]');
-const NOT_FLOW_CHAR = unirex('(?!' + rexstr(FLOW_CHAR) + ')[^]');
-const NOT_ALLOWED_CHAR = unirex(
- '(?!' + rexstr(ALLOWED_CHAR) + ')[^]'
-);
-
-/* BASIC CONSTRUCTS */
-
-const ANY_WHITE_SPACE = unirex(rexstr(WHITE_SPACE) + '*');
-const ANY_ALLOWED_CHARS = unirex(rexstr(ALLOWED_CHAR) + '*');
-const NEW_LINE = unirex(
- rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
-);
-const SOME_NEW_LINES = unirex(
- '(?:' + rexstr(NEW_LINE) + ')+'
-);
-const POSSIBLE_STARTS = unirex(
- rexstr(DOCUMENT_START) + rexstr(/
]*>/) + '?'
-);
-const POSSIBLE_ENDS = unirex(
- rexstr(SOME_NEW_LINES) + '|' +
- rexstr(DOCUMENT_END) + '|' +
- rexstr(/<\/p>/)
-);
-const QUOTE_CHAR = unirex(
- '(?=' + rexstr(NOT_LINE_BREAK) + ')[^"]'
-);
-const ANY_QUOTE_CHAR = unirex(
- rexstr(QUOTE_CHAR) + '*'
-);
-
-const ESCAPED_APOS = unirex(
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/)
-);
-const ANY_ESCAPED_APOS = unirex(
- rexstr(ESCAPED_APOS) + '*'
-);
-const FIRST_KEY_CHAR = unirex(
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
- '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
- rexstr(NOT_INDICATOR) + '|' +
- rexstr(/[?:-]/) +
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
- '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
- '(?=' + rexstr(NOT_FLOW_CHAR) + ')'
-);
-const FIRST_VALUE_CHAR = unirex(
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
- '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
- rexstr(NOT_INDICATOR) + '|' +
- rexstr(/[?:-]/) +
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
- '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
- // Flow indicators are allowed in values.
-);
-const LATER_KEY_CHAR = unirex(
- rexstr(WHITE_SPACE) + '|' +
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
- '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
- '(?=' + rexstr(NOT_FLOW_CHAR) + ')' +
- rexstr(/[^:#]#?/) + '|' +
- rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
-);
-const LATER_VALUE_CHAR = unirex(
- rexstr(WHITE_SPACE) + '|' +
- '(?=' + rexstr(NOT_LINE_BREAK) + ')' +
- '(?=' + rexstr(NOT_WHITE_SPACE) + ')' +
- // Flow indicators are allowed in values.
- rexstr(/[^:#]#?/) + '|' +
- rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')'
-);
-
-/* YAML CONSTRUCTS */
-
-const ƔAML_START = unirex(
- rexstr(ANY_WHITE_SPACE) + '---'
-);
-const ƔAML_END = unirex(
- rexstr(ANY_WHITE_SPACE) + '(?:---|\.\.\.)'
-);
-const ƔAML_LOOKAHEAD = unirex(
- '(?=' +
- rexstr(ƔAML_START) +
- rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) +
- rexstr(ƔAML_END) + rexstr(POSSIBLE_ENDS) +
- ')'
-);
-const ƔAML_DOUBLE_QUOTE = unirex(
- '"' + rexstr(ANY_QUOTE_CHAR) + '"'
-);
-const ƔAML_SINGLE_QUOTE = unirex(
- '\'' + rexstr(ANY_ESCAPED_APOS) + '\''
-);
-const ƔAML_SIMPLE_KEY = unirex(
- rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*'
-);
-const ƔAML_SIMPLE_VALUE = unirex(
- rexstr(FIRST_VALUE_CHAR) + rexstr(LATER_VALUE_CHAR) + '*'
-);
-const ƔAML_KEY = unirex(
- rexstr(ƔAML_DOUBLE_QUOTE) + '|' +
- rexstr(ƔAML_SINGLE_QUOTE) + '|' +
- rexstr(ƔAML_SIMPLE_KEY)
-);
-const ƔAML_VALUE = unirex(
- rexstr(ƔAML_DOUBLE_QUOTE) + '|' +
- rexstr(ƔAML_SINGLE_QUOTE) + '|' +
- rexstr(ƔAML_SIMPLE_VALUE)
-);
-const ƔAML_SEPARATOR = unirex(
- rexstr(ANY_WHITE_SPACE) +
- ':' + rexstr(WHITE_SPACE) +
- rexstr(ANY_WHITE_SPACE)
-);
-const ƔAML_LINE = unirex(
- '(' + rexstr(ƔAML_KEY) + ')' +
- rexstr(ƔAML_SEPARATOR) +
- '(' + rexstr(ƔAML_VALUE) + ')'
-);
-
-/* FRONTMATTER REGEX */
-
-const ƔAML_FRONTMATTER = unirex(
- rexstr(POSSIBLE_STARTS) +
- rexstr(ƔAML_LOOKAHEAD) +
- rexstr(ƔAML_START) + rexstr(SOME_NEW_LINES) +
- '(?:' +
- rexstr(ANY_WHITE_SPACE) + rexstr(ƔAML_LINE) + rexstr(SOME_NEW_LINES) +
- '){0,5}' +
- rexstr(ƔAML_END) + rexstr(POSSIBLE_ENDS)
-);
-
-/* SEARCHES */
-
-const FIND_ƔAML_LINE = unirex(
- rexstr(NEW_LINE) + rexstr(ANY_WHITE_SPACE) + rexstr(ƔAML_LINE)
-);
-
-/* STRING PROCESSING */
-
-function processString (str) {
- switch (str.charAt(0)) {
- case '"':
- return str.substring(1, str.length - 1);
- case '\'':
- return str
- .substring(1, str.length - 1)
- .replace(/''/g, '\'');
- default:
- return str;
- }
-}
-
-/* BIO PROCESSING */
-
-export function processBio(content) {
- content = content.replace(/"/g, '"').replace(/'/g, '\'');
- let result = {
- text: content,
- metadata: [],
- };
- let ɣaml = content.match(ƔAML_FRONTMATTER);
- if (!ɣaml) {
- return result;
- } else {
- ɣaml = ɣaml[0];
- }
- const start = content.search(ƔAML_START);
- const end = start + ɣaml.length - ɣaml.search(ƔAML_START);
- result.text = content.substr(end);
- let metadata = null;
- let query = new RegExp(rexstr(FIND_ƔAML_LINE), 'g'); // Some browsers don't allow flags unless both args are strings
- while ((metadata = query.exec(ɣaml))) {
- result.metadata.push([
- processString(metadata[1]),
- processString(metadata[2]),
- ]);
- }
- return result;
-}
-
-/* BIO CREATION */
-
-export function createBio(note, data) {
- if (!note) note = '';
- let frontmatter = '';
- if ((data && data.length) || note.match(/^\s*---\s+/)) {
- if (!data) frontmatter = '---\n...\n';
- else {
- frontmatter += '---\n';
- for (let i = 0; i < data.length; i++) {
- let key = '' + data[i][0];
- let val = '' + data[i][1];
-
- // Key processing
- if (key === (key.match(ƔAML_SIMPLE_KEY) || [])[0]) /* do nothing */;
- else if (key === (key.match(ANY_QUOTE_CHAR) || [])[0]) key = '"' + key + '"';
- else {
- key = key
- .replace(/'/g, '\'\'')
- .replace(new RegExp(rexstr(NOT_ALLOWED_CHAR), compat_mode ? 'g' : 'gu'), '�');
- key = '\'' + key + '\'';
- }
-
- // Value processing
- if (val === (val.match(ƔAML_SIMPLE_VALUE) || [])[0]) /* do nothing */;
- else if (val === (val.match(ANY_QUOTE_CHAR) || [])[0]) val = '"' + val + '"';
- else {
- key = key
- .replace(/'/g, '\'\'')
- .replace(new RegExp(rexstr(NOT_ALLOWED_CHAR), compat_mode ? 'g' : 'gu'), '�');
- key = '\'' + key + '\'';
- }
-
- frontmatter += key + ': ' + val + '\n';
- }
- frontmatter += '...\n';
- }
- }
- return frontmatter + note;
-}
diff --git a/app/lib/frontmatter_handler.rb b/app/lib/frontmatter_handler.rb
deleted file mode 100644
index fa787630d..000000000
--- a/app/lib/frontmatter_handler.rb
+++ /dev/null
@@ -1,223 +0,0 @@
-# frozen_string_literal: true
-
-require 'singleton'
-
-# See also `app/javascript/features/account/util/bio_metadata.js`.
-
-class FrontmatterHandler
- include Singleton
-
- # CONVENIENCE FUNCTIONS #
-
- def self.unirex(str)
- Regexp.new str, Regexp::MULTILINE
- end
- def self.rexstr(exp)
- '(?:' + exp.source + ')'
- end
-
- # CHARACTER CLASSES #
-
- DOCUMENT_START = /^/
- DOCUMENT_END = /$/
- ALLOWED_CHAR = # c-printable` in the YAML 1.2 spec.
- /[\t\n\r\u{20}-\u{7e}\u{85}\u{a0}-\u{d7ff}\u{e000}-\u{fffd}\u{10000}-\u{10ffff}]/u
- WHITE_SPACE = /[ \t]/
- INDENTATION = / */
- LINE_BREAK = /\r?\n|\r|
/
- ESCAPE_CHAR = /[0abt\tnvfre "\/\\N_LP]/
- HEXADECIMAL_CHARS = /[0-9a-fA-F]/
- INDICATOR = /[-?:,\[\]{}*!|>'"%@`]/
- FLOW_CHAR = /[,\[\]{}]/
-
- # NEGATED CHARACTER CLASSES #
-
- NOT_WHITE_SPACE = unirex '(?!' + rexstr(WHITE_SPACE) + ').'
- NOT_LINE_BREAK = unirex '(?!' + rexstr(LINE_BREAK) + ').'
- NOT_INDICATOR = unirex '(?!' + rexstr(INDICATOR) + ').'
- NOT_FLOW_CHAR = unirex '(?!' + rexstr(FLOW_CHAR) + ').'
- NOT_ALLOWED_CHAR = unirex '(?!' + rexstr(ALLOWED_CHAR) + ').'
-
- # BASIC CONSTRUCTS #
-
- ANY_WHITE_SPACE = unirex rexstr(WHITE_SPACE) + '*'
- ANY_ALLOWED_CHARS = unirex rexstr(ALLOWED_CHAR) + '*'
- NEW_LINE = unirex(
- rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
- )
- SOME_NEW_LINES = unirex(
- '(?:' + rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK) + ')+'
- )
- POSSIBLE_STARTS = unirex(
- rexstr(DOCUMENT_START) + rexstr(/
]*>/) + '?' - ) - POSSIBLE_ENDS = unirex( - rexstr(SOME_NEW_LINES) + '|' + - rexstr(DOCUMENT_END) + '|' + - rexstr(/<\/p>/) - ) - CHARACTER_ESCAPE = unirex( - rexstr(/\\/) + - '(?:' + - rexstr(ESCAPE_CHAR) + '|' + - rexstr(/x/) + rexstr(HEXADECIMAL_CHARS) + '{2}' + '|' + - rexstr(/u/) + rexstr(HEXADECIMAL_CHARS) + '{4}' + '|' + - rexstr(/U/) + rexstr(HEXADECIMAL_CHARS) + '{8}' + - ')' - ) - ESCAPED_CHAR = unirex( - rexstr(/(?!["\\])/) + rexstr(NOT_LINE_BREAK) + '|' + - rexstr(CHARACTER_ESCAPE) - ) - ANY_ESCAPED_CHARS = unirex( - rexstr(ESCAPED_CHAR) + '*' - ) - ESCAPED_APOS = unirex( - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/) - ) - ANY_ESCAPED_APOS = unirex( - rexstr(ESCAPED_APOS) + '*' - ) - FIRST_KEY_CHAR = unirex( - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + - '(?=' + rexstr(NOT_WHITE_SPACE) + ')' + - rexstr(NOT_INDICATOR) + '|' + - rexstr(/[?:-]/) + - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + - '(?=' + rexstr(NOT_WHITE_SPACE) + ')' + - '(?=' + rexstr(NOT_FLOW_CHAR) + ')' - ) - FIRST_VALUE_CHAR = unirex( - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + - '(?=' + rexstr(NOT_WHITE_SPACE) + ')' + - rexstr(NOT_INDICATOR) + '|' + - rexstr(/[?:-]/) + - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + - '(?=' + rexstr(NOT_WHITE_SPACE) + ')' - # Flow indicators are allowed in values. - ) - LATER_KEY_CHAR = unirex( - rexstr(WHITE_SPACE) + '|' + - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + - '(?=' + rexstr(NOT_WHITE_SPACE) + ')' + - '(?=' + rexstr(NOT_FLOW_CHAR) + ')' + - rexstr(/[^:#]#?/) + '|' + - rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')' - ) - LATER_VALUE_CHAR = unirex( - rexstr(WHITE_SPACE) + '|' + - '(?=' + rexstr(NOT_LINE_BREAK) + ')' + - '(?=' + rexstr(NOT_WHITE_SPACE) + ')' + - # Flow indicators are allowed in values. - rexstr(/[^:#]#?/) + '|' + - rexstr(/:/) + '(?=' + rexstr(NOT_WHITE_SPACE) + ')' - ) - - # YAML CONSTRUCTS # - - YAML_START = unirex( - rexstr(ANY_WHITE_SPACE) + rexstr(/---/) - ) - YAML_END = unirex( - rexstr(ANY_WHITE_SPACE) + rexstr(/(?:---|\.\.\.)/) - ) - YAML_LOOKAHEAD = unirex( - '(?=' + - rexstr(YAML_START) + - rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) + - rexstr(YAML_END) + rexstr(POSSIBLE_ENDS) + - ')' - ) - YAML_DOUBLE_QUOTE = unirex( - rexstr(/"/) + rexstr(ANY_ESCAPED_CHARS) + rexstr(/"/) - ) - YAML_SINGLE_QUOTE = unirex( - rexstr(/'/) + rexstr(ANY_ESCAPED_APOS) + rexstr(/'/) - ) - YAML_SIMPLE_KEY = unirex( - rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*' - ) - YAML_SIMPLE_VALUE = unirex( - rexstr(FIRST_VALUE_CHAR) + rexstr(LATER_VALUE_CHAR) + '*' - ) - YAML_KEY = unirex( - rexstr(YAML_DOUBLE_QUOTE) + '|' + - rexstr(YAML_SINGLE_QUOTE) + '|' + - rexstr(YAML_SIMPLE_KEY) - ) - YAML_VALUE = unirex( - rexstr(YAML_DOUBLE_QUOTE) + '|' + - rexstr(YAML_SINGLE_QUOTE) + '|' + - rexstr(YAML_SIMPLE_VALUE) - ) - YAML_SEPARATOR = unirex( - rexstr(ANY_WHITE_SPACE) + - ':' + rexstr(WHITE_SPACE) + - rexstr(ANY_WHITE_SPACE) - ) - YAML_LINE = unirex( - '(' + rexstr(YAML_KEY) + ')' + - rexstr(YAML_SEPARATOR) + - '(' + rexstr(YAML_VALUE) + ')' - ) - - # FRONTMATTER REGEX # - - YAML_FRONTMATTER = unirex( - rexstr(POSSIBLE_STARTS) + - rexstr(YAML_LOOKAHEAD) + - rexstr(YAML_START) + rexstr(SOME_NEW_LINES) + - '(?:' + - '(' + rexstr(INDENTATION) + ')' + - rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) + - '(?:' + - '\\1' + rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) + - '){0,4}' + - ')?' + - rexstr(YAML_END) + rexstr(POSSIBLE_ENDS) - ) - - # SEARCHES # - - FIND_YAML_LINES = unirex( - rexstr(NEW_LINE) + rexstr(INDENTATION) + rexstr(YAML_LINE) - ) - - # STRING PROCESSING # - - def process_string(str) - case str[0] - when '"' - str[1..-2] - when "'" - str[1..-2].gsub(/''/, "'") - else - str - end - end - - # BIO PROCESSING # - - def process_bio content - result = { - text: content.gsub(/"/, '"').gsub(/'/, "'"), - metadata: [] - } - yaml = YAML_FRONTMATTER.match(result[:text]) - return result unless yaml - yaml = yaml[0] - start = YAML_START =~ result[:text] - ending = start + yaml.length - (YAML_START =~ yaml) - result[:text][start..ending - 1] = '' - metadata = nil - index = 0 - while metadata = FIND_YAML_LINES.match(yaml, index) do - index = metadata.end(0) - result[:metadata].push [ - process_string(metadata[1]), process_string(metadata[2]) - ] - end - return result - end - -end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml index b5653f161..4098d6778 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,4 +1,3 @@ -- processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format(account, custom_emojify: true) .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } .card__illustration = render 'accounts/follow_button', account: account @@ -24,21 +23,16 @@ .roles .account-role.moderator = t 'accounts.roles.moderator' + .bio - .account__header__content.p-note.emojify!=processed_bio[:text] + .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) - - if !account.fields.empty? + - unless account.fields.empty? .account__header__fields - account.fields.each do |field| %dl %dt.emojify{ title: field.name }= field.name %dd.emojify{ title: field.value }= Formatter.instance.format_field(account, field.value, custom_emojify: true) - - elsif processed_bio[:metadata].length > 0 - .account__header__fields - - processed_bio[:metadata].each do |i| - %dl - %dt.emojify{ title: i[0] }!= i[0] - %dd.emojify{ title: i[1] }!= i[1] .details-counters .counter{ class: active_nav_class(short_account_url(account)) }