Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-06 03:09:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-06 03:09:53 +0300
commit631ed6dcca9d41d0dc24dd553065de1a0f8210f0 (patch)
treee644e0f962e7f6ce664e37d415edf0c584711cdb /app/assets/javascripts
parentad9eb72915f1be40da3ebe287274fe2bae62e46b (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/lib/chrome_84_icon_fix.js75
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_area.vue19
-rw-r--r--app/assets/javascripts/static_site_editor/services/templater.js32
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js3
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) => {