From 10a8aba40bb20c6020eee7339333cbcde7d836be Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Mon, 28 Nov 2022 15:34:05 -0600 Subject: [PATCH 1/4] Allow img size attributes to be specified in markdown using the syntax `{width=50%}` Changelog: added --- .../development/markdown_image_attributes.yml | 8 ++ doc/user/markdown.md | 22 ++++- lib/banzai/filter/attributes_filter.rb | 57 +++++++++++++ lib/banzai/pipeline/gfm_pipeline.rb | 1 + spec/features/markdown/markdown_spec.rb | 7 ++ spec/fixtures/markdown.md.erb | 4 + .../banzai/filter/attributes_filter_spec.rb | 85 +++++++++++++++++++ 7 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 config/feature_flags/development/markdown_image_attributes.yml create mode 100644 lib/banzai/filter/attributes_filter.rb create mode 100644 spec/lib/banzai/filter/attributes_filter_spec.rb diff --git a/config/feature_flags/development/markdown_image_attributes.yml b/config/feature_flags/development/markdown_image_attributes.yml new file mode 100644 index 00000000000000..ddc2ca6ac6308e --- /dev/null +++ b/config/feature_flags/development/markdown_image_attributes.yml @@ -0,0 +1,8 @@ +--- +name: markdown_image_attributes +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104904 +rollout_issue_url: +milestone: '15.7' +type: development +group: group::project management +default_enabled: false diff --git a/doc/user/markdown.md b/doc/user/markdown.md index b6f3ba1cfddd62..ffd4e65af8a727 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1017,8 +1017,26 @@ Do not change to a reference style link. ![alt text](img/markdown_logo.png "Title Text") -In the rare case where you must set a specific height or width for an image, -you can use the `img` HTML tag instead of Markdown and set its `height` and +> Introduced in GitLab 15.7 [with a flag](../administration/feature_flags.md) named `markdown_image_attributes`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available, +ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `markdown_image_attributes`. +On GitLab.com, this feature is available. +The feature is not ready for production use. + +The width and height of an image can be specified by following the image with +an attribute list. For example + +```markdown +![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} + +![alt text](img/markdown_logo.png "Title Text"){ width=75% } +``` + +![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} + +You can also use the `img` HTML tag instead of Markdown and set its `height` and `width` parameters. #### Videos diff --git a/lib/banzai/filter/attributes_filter.rb b/lib/banzai/filter/attributes_filter.rb new file mode 100644 index 00000000000000..b7a06c666f31bf --- /dev/null +++ b/lib/banzai/filter/attributes_filter.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # Looks for attributes that are specified for an element. Follows the basic syntax laid out + # in https://github.com/jgm/commonmark-hs/blob/master/commonmark-extensions/test/attributes.md + # For example, + # ![](http://example.com/image.jpg){width=50%} + # + # However we currently have the following limitations: + # - only support images + # - only support the `width` and `height` attributes + # - attributes can not span multiple lines + # - unsupported attributes are thrown away + class AttributesFilter < HTML::Pipeline::Filter + CSS = 'img' + XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze + + ATTRIBUTES_PATTERN = %r{\A(?\{(?(?:.{1,100}?))\})}.freeze + WIDTH_HEIGHT_REGEX = %r{\A(?height|width)="?(?[\w%]{1,10})"?\z}.freeze + VALID_SIZE_REGEX = %r{\A\d{1,4}(%|px|\z|)\z}.freeze + + def call + return doc unless Feature.enabled?(:markdown_image_attributes, group) + + doc.xpath(XPATH).each do |img| + sibling = img.next + next unless sibling && sibling.text? && sibling.content.first == '{' + + match = sibling.content.match(ATTRIBUTES_PATTERN) + next unless match && match[:attributes] + + match[:attributes].split(' ').each do |attribute| + next unless attribute.match?(WIDTH_HEIGHT_REGEX) + + attribute_match = attribute.match(WIDTH_HEIGHT_REGEX) + img[attribute_match[:name].to_sym] = attribute_match[:size] if valid_size?(attribute_match[:size]) + end + + sibling.content = sibling.content.sub(match[:matched], '') + end + + doc + end + + private + + def valid_size?(size) + size.match?(VALID_SIZE_REGEX) + end + + def group + context[:group] || context[:project]&.group + end + end + end +end diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index 9b73e413d44cfe..8ac4d0aadab126 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -22,6 +22,7 @@ def self.filters Filter::MermaidFilter, Filter::VideoLinkFilter, Filter::AudioLinkFilter, + Filter::AttributesFilter, Filter::ImageLazyLoadFilter, Filter::ImageLinkFilter, *metrics_filters, diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index 08f9b8eda13ff2..73670772402d2f 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -290,6 +290,13 @@ def doc(html = @html) aggregate_failures 'KrokiFilter' do expect(doc).to parse_kroki end + + aggregate_failures 'AttributeFilter' do + img = doc.at_css('img[alt="Sized Image"]') + + expect(img.attr('width')).to eq('75%') + expect(img.attr('height')).to eq('100') + end end end diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 14885813d9365c..38b2a8381bb292 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -399,3 +399,7 @@ Bob -> Sara : Hello [beard]-:>[foul mouth] ] ``` + +### Image Attributes + +![Sized Image](app/assets/images/touch-icon-ipad.png){width=75% height=100} diff --git a/spec/lib/banzai/filter/attributes_filter_spec.rb b/spec/lib/banzai/filter/attributes_filter_spec.rb new file mode 100644 index 00000000000000..86af3364feffc5 --- /dev/null +++ b/spec/lib/banzai/filter/attributes_filter_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Banzai::Filter::AttributesFilter, feature_category: :team_planning do + using RSpec::Parameterized::TableSyntax + include FilterSpecHelper + + def image + %() + end + + it 'does not recognize new syntax when feature flag is off' do + stub_feature_flags(markdown_image_attributes: false) + doc = filter("#{image}{width=100}") + + expect(doc.to_s).to eq "#{image}{width=100}" + end + + describe 'attribute syntax' do + context 'when attribute syntax is valid' do + where(:text, :result) do + "#{image}{width=100}" | '' + "#{image}{ width=100 }" | '' + "#{image}{width=\"100\"}" | '' + "#{image}{width=100 width=200}" | '' + + "#{image}{.test_class width=100 style=\"width:400\"}" | '' + "{width=100}" | '' + end + + with_them do + it 'adds them to the img' do + expect(filter(text).to_html).to eq result + end + end + end + + context 'when attribute syntax is invalid' do + where(:text, :result) do + "#{image} {width=100}" | ' {width=100}' + "#{image}{width=100\nheight=100}" | "{width=100\nheight=100}" + "{width=100 height=100}\n#{image}" | "{width=100 height=100}\n" + '

header

{width=100}' | '

header

{width=100}' + end + + with_them do + it 'does not recognize as attributes' do + expect(filter(text).to_html).to eq result + end + end + end + end + + describe 'height and width' do + context 'when size attributes are valid' do + where(:text, :result) do + "#{image}{width=100 height=200px}" | '' + "#{image}{width=100}" | '' + "#{image}{width=100px}" | '' + "#{image}{height=100%}" | '' + "#{image}{width=\"100%\"}" | '' + end + + with_them do + it 'adds them to the img' do + expect(filter(text).to_html).to eq result + end + end + end + + context 'when size attributes are invalid' do + where(:text, :result) do + "#{image}{width=100cs}" | '' + "#{image}{width=auto height=200}" | '' + end + + with_them do + it 'ignores them' do + expect(filter(text).to_html).to eq result + end + end + end + end +end -- GitLab From 2dd81d867b21bad0b4d36b4940fa4fcc83d17009 Mon Sep 17 00:00:00 2001 From: Marcin Sedlak-Jakubowski Date: Wed, 30 Nov 2022 19:52:04 +0000 Subject: [PATCH 2/4] Apply documentation suggestions --- doc/user/markdown.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index ffd4e65af8a727..e2a5d0a3d32133 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1017,6 +1017,8 @@ Do not change to a reference style link. ![alt text](img/markdown_logo.png "Title Text") +#### Change the image dimensions + > Introduced in GitLab 15.7 [with a flag](../administration/feature_flags.md) named `markdown_image_attributes`. Disabled by default. FLAG: @@ -1025,13 +1027,13 @@ ask an administrator to [enable the feature flag](../administration/feature_flag On GitLab.com, this feature is available. The feature is not ready for production use. -The width and height of an image can be specified by following the image with +You can control the width and height of an image by following the image with an attribute list. For example ```markdown ![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} -![alt text](img/markdown_logo.png "Title Text"){ width=75% } +![alt text](img/markdown_logo.png "Title Text"){width=75%} ``` ![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} -- GitLab From 559d8eedeb2dae71900e2f992d2c5bbea2c0be4e Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 30 Nov 2022 14:22:06 -0600 Subject: [PATCH 3/4] Apply reviewer suggestions --- doc/user/markdown.md | 2 +- lib/banzai/filter/attributes_filter.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index e2a5d0a3d32133..35e11bf6109850 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1039,7 +1039,7 @@ an attribute list. For example ![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} You can also use the `img` HTML tag instead of Markdown and set its `height` and -`width` parameters. +`width` parameters. The value should be an integer with a unit of either `%` or `px` (the default). #### Videos diff --git a/lib/banzai/filter/attributes_filter.rb b/lib/banzai/filter/attributes_filter.rb index b7a06c666f31bf..1cf7155b80e2bd 100644 --- a/lib/banzai/filter/attributes_filter.rb +++ b/lib/banzai/filter/attributes_filter.rb @@ -16,9 +16,9 @@ class AttributesFilter < HTML::Pipeline::Filter CSS = 'img' XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze - ATTRIBUTES_PATTERN = %r{\A(?\{(?(?:.{1,100}?))\})}.freeze + ATTRIBUTES_PATTERN = %r{\A(?\{(?.{1,100})\})}.freeze WIDTH_HEIGHT_REGEX = %r{\A(?height|width)="?(?[\w%]{1,10})"?\z}.freeze - VALID_SIZE_REGEX = %r{\A\d{1,4}(%|px|\z|)\z}.freeze + VALID_SIZE_REGEX = %r{\A\d{1,4}(%|px)?\z}.freeze def call return doc unless Feature.enabled?(:markdown_image_attributes, group) -- GitLab From b7a6245088af9ab7ff5b1bf4856eebeac654c5f9 Mon Sep 17 00:00:00 2001 From: Marcin Sedlak-Jakubowski Date: Thu, 1 Dec 2022 20:10:03 +0000 Subject: [PATCH 4/4] Apply documentation updates --- doc/user/markdown.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 35e11bf6109850..f0dbfb854bbf63 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1027,8 +1027,11 @@ ask an administrator to [enable the feature flag](../administration/feature_flag On GitLab.com, this feature is available. The feature is not ready for production use. -You can control the width and height of an image by following the image with -an attribute list. For example +You can control the width and height of an image by following the image with +an attribute list. +The value must an integer with a unit of either `px` (default) or `%`. + +For example ```markdown ![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} @@ -1039,7 +1042,7 @@ an attribute list. For example ![alt text](img/markdown_logo.png "Title Text"){width=100 height=100px} You can also use the `img` HTML tag instead of Markdown and set its `height` and -`width` parameters. The value should be an integer with a unit of either `%` or `px` (the default). +`width` parameters. #### Videos -- GitLab