diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-06 03:09:53 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-06 03:09:53 +0300 |
commit | 631ed6dcca9d41d0dc24dd553065de1a0f8210f0 (patch) | |
tree | e644e0f962e7f6ce664e37d415edf0c584711cdb /app/assets/javascripts | |
parent | ad9eb72915f1be40da3ebe287274fe2bae62e46b (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
6 files changed, 124 insertions, 14 deletions
diff --git a/app/assets/javascripts/lib/chrome_84_icon_fix.js b/app/assets/javascripts/lib/chrome_84_icon_fix.js index 5be77a7284f..60497186c19 100644 --- a/app/assets/javascripts/lib/chrome_84_icon_fix.js +++ b/app/assets/javascripts/lib/chrome_84_icon_fix.js @@ -1,13 +1,78 @@ -import svg4everybody from 'svg4everybody'; +import { debounce } from 'lodash'; /* Chrome and Edge 84 have a bug relating to icon sprite svgs https://bugs.chromium.org/p/chromium/issues/detail?id=1107442 If the SVG is loaded, under certain circumstances the icons are not - shown. As a workaround we use the well-tested svg4everybody and forcefully - include the icon fragments into the DOM and thus circumventing the bug + shown. We load our sprite icons with JS and add them to the body. + Then we iterate over all the `use` elements and replace their reference + to that svg which we added internally. In order to avoid id conflicts, + those are renamed with a unique prefix. + + We do that once the DOMContentLoaded fired and otherwise we use a + mutation observer to re-trigger this logic. + + In order to not have a big impact on performance or to cause flickering + of of content, + + 1. we only do it for each svg once + 2. we debounce the event handler and just do it in a requestIdleCallback + + Before we tried to do it with the library svg4everybody and it had a big + performance impact. See: + https://gitlab.com/gitlab-org/quality/performance/-/issues/312 */ -document.addEventListener('DOMContentLoaded', () => { - svg4everybody({ polyfill: true }); +document.addEventListener('DOMContentLoaded', async () => { + const GITLAB_SVG_PREFIX = 'chrome-issue-230433-gitlab-svgs'; + const FILE_ICON_PREFIX = 'chrome-issue-230433-file-icons'; + const SKIP_ATTRIBUTE = 'data-replaced-by-chrome-issue-230433'; + + const fixSVGs = () => { + requestIdleCallback(() => { + document.querySelectorAll(`use:not([${SKIP_ATTRIBUTE}])`).forEach(use => { + const href = use?.getAttribute('href') ?? use?.getAttribute('xlink:href') ?? ''; + + if (href.includes(window.gon.sprite_icons)) { + use.removeAttribute('xlink:href'); + use.setAttribute('href', `#${GITLAB_SVG_PREFIX}-${href.split('#')[1]}`); + } else if (href.includes(window.gon.sprite_file_icons)) { + use.removeAttribute('xlink:href'); + use.setAttribute('href', `#${FILE_ICON_PREFIX}-${href.split('#')[1]}`); + } + + use.setAttribute(SKIP_ATTRIBUTE, 'true'); + }); + }); + }; + + const watchForNewSVGs = () => { + const observer = new MutationObserver(debounce(fixSVGs, 200)); + observer.observe(document.querySelector('body'), { + childList: true, + attributes: false, + subtree: true, + }); + }; + + const retrieveIconSprites = async (url, prefix) => { + const div = document.createElement('div'); + div.classList.add('hidden'); + const result = await fetch(url); + div.innerHTML = await result.text(); + div.querySelectorAll('[id]').forEach(node => { + node.setAttribute('id', `${prefix}-${node.getAttribute('id')}`); + }); + document.body.append(div); + }; + + if (window.gon && window.gon.sprite_icons) { + await retrieveIconSprites(window.gon.sprite_icons, GITLAB_SVG_PREFIX); + if (window.gon.sprite_file_icons) { + await retrieveIconSprites(window.gon.sprite_file_icons, FILE_ICON_PREFIX); + } + + fixSVGs(); + watchForNewSVGs(); + } }); diff --git a/app/assets/javascripts/static_site_editor/components/edit_area.vue b/app/assets/javascripts/static_site_editor/components/edit_area.vue index 3c09640c78b..53fbb2a330d 100644 --- a/app/assets/javascripts/static_site_editor/components/edit_area.vue +++ b/app/assets/javascripts/static_site_editor/components/edit_area.vue @@ -8,6 +8,7 @@ import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/consta import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants'; import imageRepository from '../image_repository'; import formatter from '../services/formatter'; +import templater from '../services/templater'; export default { components: { @@ -44,7 +45,7 @@ export default { data() { return { saveable: false, - parsedSource: parseSourceFile(this.content), + parsedSource: parseSourceFile(this.preProcess(true, this.content)), editorMode: EDITOR_TYPES.wysiwyg, isModified: false, }; @@ -59,22 +60,30 @@ export default { }, }, methods: { + preProcess(isWrap, value) { + const formattedContent = formatter(value); + const templatedContent = isWrap + ? templater.wrap(formattedContent) + : templater.unwrap(formattedContent); + return templatedContent; + }, onInputChange(newVal) { this.parsedSource.sync(newVal, this.isWysiwygMode); this.isModified = this.parsedSource.isModified(); }, onModeChange(mode) { this.editorMode = mode; - const formattedContent = formatter(this.editableContent); - this.$refs.editor.resetInitialValue(formattedContent); + + const preProcessedContent = this.preProcess(this.isWysiwygMode, this.editableContent); + this.$refs.editor.resetInitialValue(preProcessedContent); }, onUploadImage({ file, imageUrl }) { this.$options.imageRepository.add(file, imageUrl); }, onSubmit() { - const formattedContent = formatter(this.parsedSource.content()); + const preProcessedContent = this.preProcess(false, this.parsedSource.content()); this.$emit('submit', { - content: formattedContent, + content: preProcessedContent, images: this.$options.imageRepository.getAll(), }); }, diff --git a/app/assets/javascripts/static_site_editor/services/templater.js b/app/assets/javascripts/static_site_editor/services/templater.js new file mode 100644 index 00000000000..29a2bcd2e34 --- /dev/null +++ b/app/assets/javascripts/static_site_editor/services/templater.js @@ -0,0 +1,32 @@ +const marker = 'sse'; +const ticks = '```'; +const prefix = `${ticks} ${marker}\n`; // Space intentional due to https://github.com/nhn/tui.editor/blob/6bcec75c69028570d93d973aa7533090257eaae0/libs/to-mark/src/renderer.gfm.js#L26 +const postfix = `\n${ticks}`; +const code = '.| |\\t|\\n(?!\\n)'; +const templatedRegex = new RegExp(`(^${prefix}(${code})+${postfix}$)`, 'gm'); +const embeddedRubyRegex = new RegExp(`(^<%(${code})+%>$)`, 'gm'); + +const unwrap = source => { + let text = source; + const matches = text.match(templatedRegex); + if (matches) { + matches.forEach(match => { + const initial = match.replace(prefix, '').replace(postfix, ''); + text = text.replace(match, initial); + }); + } + return text; +}; + +const wrap = source => { + let text = unwrap(source); + const matches = text.match(embeddedRubyRegex); + if (matches) { + matches.forEach(match => { + text = text.replace(match, `${prefix}${match}${postfix}`); + }); + } + return text; +}; + +export default { wrap, unwrap }; diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue index 7484486d6b4..6190b07962d 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/file_icon.vue @@ -87,7 +87,7 @@ export default { <span> <gl-loading-icon v-if="loading" :inline="true" /> <gl-icon v-else-if="isSymlink" name="symlink" :size="size" /> - <svg v-else-if="!folder" :class="[iconSizeClass, cssClasses]"> + <svg v-else-if="!folder" :key="spriteHref" :class="[iconSizeClass, cssClasses]"> <use v-bind="{ 'xlink:href': spriteHref }" /> </svg> <gl-icon v-else :name="folderIconName" :size="size" class="folder-icon" /> diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue index 80908cbbc9c..68eeadf0f25 100644 --- a/app/assets/javascripts/vue_shared/components/icon.vue +++ b/app/assets/javascripts/vue_shared/components/icon.vue @@ -61,7 +61,12 @@ export default { </script> <template> - <svg :class="[iconSizeClass, iconTestClass]" aria-hidden="true" v-on="$listeners"> + <svg + :key="spriteHref" + :class="[iconSizeClass, iconTestClass]" + aria-hidden="true" + v-on="$listeners" + > <use v-bind="{ 'xlink:href': spriteHref }" /> </svg> </template> diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js index bd374a54d98..b210ada412d 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js @@ -3,7 +3,6 @@ import renderKramdownList from './renderers/render_kramdown_list'; import renderKramdownText from './renderers/render_kramdown_text'; import renderIdentifierInstanceText from './renderers/render_identifier_instance_text'; import renderIdentifierParagraph from './renderers/render_identifier_paragraph'; -import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text'; import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline'; import renderSoftbreak from './renderers/render_softbreak'; @@ -11,7 +10,7 @@ const htmlInlineRenderers = [renderFontAwesomeHtmlInline]; const htmlBlockRenderers = [renderBlockHtml]; const listRenderers = [renderKramdownList]; const paragraphRenderers = [renderIdentifierParagraph]; -const textRenderers = [renderKramdownText, renderEmbeddedRubyText, renderIdentifierInstanceText]; +const textRenderers = [renderKramdownText, renderIdentifierInstanceText]; const softbreakRenderers = [renderSoftbreak]; const executeRenderer = (renderers, node, context) => { |