diff --git a/app/javascript/styles/accounts.scss b/app/javascript/styles/accounts.scss
index 10f8bd2b9c2..d346a6bb2c3 100644
--- a/app/javascript/styles/accounts.scss
+++ b/app/javascript/styles/accounts.scss
@@ -1,29 +1,34 @@
.card {
+ display: flex;
background: $ui-base-color;
- background-size: cover;
- background-position: center;
- padding: 60px 0;
- padding-bottom: 0;
border-radius: 4px 4px 0 0;
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
overflow: hidden;
- position: relative;
@media screen and (max-width: 700px) {
border-radius: 0;
box-shadow: none;
}
- &::after {
- background: linear-gradient(rgba($base-shadow-color, 0.5), rgba($base-shadow-color, 0.8));
- display: block;
- content: "";
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- z-index: 1;
+ .details {
+ position: relative;
+ padding: 60px 0 0;
+ background-size: cover;
+ background-position: center;
+ text-align: center;
+ flex: auto;
+
+ &::after {
+ background: linear-gradient(rgba($base-shadow-color, 0.5), rgba($base-shadow-color, 0.8));
+ display: block;
+ content: "";
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ }
}
.name {
@@ -66,59 +71,38 @@
z-index: 2;
}
- .details {
- display: flex;
- margin-top: 30px;
- position: relative;
- z-index: 2;
- flex-direction: row;
- }
-
.details-counters {
- display: flex;
+ position: relative;
+ display: inline-flex;
flex-direction: row;
- order: 0;
+ margin: 15px 0;
+ z-index: 2;
}
.counter {
width: 80px;
color: $ui-primary-color;
padding: 5px 10px 0;
- margin-bottom: 10px;
- border-right: 1px solid $ui-primary-color;
cursor: default;
position: relative;
+ & + .counter {
+ border-left: 1px solid $ui-primary-color;
+ }
+
+ & > * {
+ opacity: .7;
+ transition: opacity .3s ease;
+ }
+
+ &.active > *, &:hover > * {
+ opacity: 1;
+ }
+
a {
display: block;
}
- &::after {
- display: block;
- content: "";
- position: absolute;
- bottom: -10px;
- left: 0;
- width: 100%;
- border-bottom: 4px solid $ui-primary-color;
- opacity: 0.5;
- transition: all 0.8s ease;
- }
-
- &.active {
- &::after {
- border-bottom: 4px solid $ui-highlight-color;
- opacity: 1;
- }
- }
-
- &:hover {
- &::after {
- opacity: 1;
- transition-duration: 0.2s;
- }
- }
-
a {
text-decoration: none;
color: inherit;
@@ -140,30 +124,51 @@
}
.bio {
- flex: 1;
+ position: relative;
font-size: 14px;
line-height: 18px;
+ margin: 15px 0;
padding: 5px 10px;
color: $ui-secondary-color;
- order: 1;
+ z-index: 2;
}
- @media screen and (max-width: 480px) {
- .details {
- display: block;
- }
+ .metadata {
+ max-width: 40%;
+ background: $ui-base-color;
+ color: $primary-text-color;
+ text-align: left;
+ overflow-y: auto;
+ white-space: pre-wrap;
- .bio {
- text-align: center;
- margin-bottom: 20px;
- }
+ .metadata-item {
+ border-bottom: 1px $ui-primary-color solid;
+ padding: 15px 10px;
+ font-size: 18px;
+ line-height: 24px;
+ overflow: hidden;
+ text-overflow: ellipsis;
- .counter {
- flex: 1 1 auto;
- }
+ a {
+ color: $ui-highlight-color;
+ text-decoration: none;
- .counter:last-child {
- border-right: none;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ b {
+ display: block;
+ font-size: 12px;
+ line-height: 16px;
+ text-transform: uppercase;
+ color: $ui-primary-color;
+
+ a {
+ color: $ui-primary-color;
+ }
+ }
}
}
}
diff --git a/app/lib/frontmatter_handler.rb b/app/lib/frontmatter_handler.rb
new file mode 100644
index 00000000000..19007cf4e4c
--- /dev/null
+++ b/app/lib/frontmatter_handler.rb
@@ -0,0 +1,242 @@
+# 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, false, 'um'
+ 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) + ').'
+
+ # 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] + .gsub(/\\0/, "\u{00}") + .gsub(/\\a/, "\u{07}") + .gsub(/\\b/, "\u{08}") + .gsub(/\\t/, "\u{09}") + .gsub(/\\n/, "\u{0a}") + .gsub(/\\v/, "\u{0b}") + .gsub(/\\f/, "\u{0c}") + .gsub(/\\r/, "\u{0d}") + .gsub(/\\e/, "\u{1b}") + .gsub(/\\ /, "\u{20}") + .gsub(/\\"/, "\u{22}") + .gsub(/\\\//, "\u{2f}") + .gsub(/\\\\/, "\u{5c}") + .gsub(/\\N/, "\u{85}") + .gsub(/\\_/, "\u{a0}") + .gsub(/\\L/, "\u{2028}") + .gsub(/\\P/, "\u{2029}") + .gsub(/\\x([0-9a-fA-F]{2})/mu) {|s| $1.to_i.chr Encoding::UTF_8} + .gsub(/\\u([0-9a-fA-F]{4})/mu) {|s| $1.to_i.chr Encoding::UTF_8} + .gsub(/\\U([0-9a-fA-F]{8})/mu) {|s| $1.to_i.chr Encoding::UTF_8} + 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 6451a5573d4..ef6a2bcbea4 100644 --- a/app/views/accounts/_header.html.haml +++ b/app/views/accounts/_header.html.haml @@ -1,23 +1,24 @@ -.card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" } - - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) - .controls - - if current_account.following?(account) - = link_to t('accounts.unfollow'), account_unfollow_path(account), data: { method: :post }, class: 'button' - - else - = link_to t('accounts.follow'), account_follow_path(account), data: { method: :post }, class: 'button' - - elsif !user_signed_in? - .controls - .remote-follow - = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button' - .avatar= image_tag account.avatar.url(:original), class: 'u-photo' - %h1.name - %span.p-name.emojify= display_name(account) - %small - %span @#{account.username} - = fa_icon('lock') if account.locked? - .details +- processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account +.card.h-card.p-author + .details{ style: "background-image: url(#{account.header.url(:original)})" } + - if user_signed_in? && current_account.id != account.id && !current_account.requested?(account) + .controls + - if current_account.following?(account) + = link_to t('accounts.unfollow'), account_unfollow_path(account), data: { method: :post }, class: 'button' + - else + = link_to t('accounts.follow'), account_follow_path(account), data: { method: :post }, class: 'button' + - elsif !user_signed_in? + .controls + .remote-follow + = link_to t('accounts.remote_follow'), account_remote_follow_path(account), class: 'button' + .avatar= image_tag account.avatar.url(:original), class: 'u-photo' + %h1.name + %span.p-name.emojify= display_name(account) + %small + %span @#{account.username} + = fa_icon('lock') if account.locked? .bio - .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account) + .account__header__content.p-note.emojify!=processed_bio[:text] .details-counters .counter{ class: active_nav_class(short_account_url(account)) } @@ -32,3 +33,9 @@ = link_to account_followers_url(account) do %span.counter-label= t('accounts.followers') %span.counter-number= number_with_delimiter account.followers_count + - if processed_bio[:metadata].length > 0 + .metadata< + - processed_bio[:metadata].each do |i| + .metadata-item>< + %b.emojify>!=i[0] + %span.emojify>!=i[1]