diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 76d9b18b777d96b0d0c784221d0de6f038949c93..2d9ffda06d040f996b53e5d8afa9cac0ba40a1b5 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -69,6 +69,7 @@ export default () => { const currentAction = $('.js-file-title').data('currentAction'); const projectId = editBlobForm.data('project-id'); const isMarkdown = editBlobForm.data('is-markdown'); + const previewMarkdownPath = editBlobForm.data('previewMarkdownPath'); const commitButton = $('.js-commit-button'); const cancelLink = $('.btn.btn-cancel'); @@ -80,6 +81,7 @@ export default () => { currentAction, projectId, isMarkdown, + previewMarkdownPath, }); initPopovers(); initCodeQualityWalkthroughStep(); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index e068910c62687829e92e90295abf4e45126b380e..118cef59d5a01b114af25d705485da129fd04cbb 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -11,7 +11,7 @@ import { BLOB_EDITOR_ERROR, BLOB_PREVIEW_ERROR } from './constants'; export default class EditBlob { // The options object has: - // assetsPath, filePath, currentAction, projectId, isMarkdown + // assetsPath, filePath, currentAction, projectId, isMarkdown, previewMarkdownPath constructor(options) { this.options = options; this.configureMonacoEditor(); @@ -30,7 +30,10 @@ export default class EditBlob { import('~/editor/extensions/source_editor_markdown_ext') .then(({ EditorMarkdownExtension: MarkdownExtension } = {}) => { this.editor.use( - new MarkdownExtension({ instance: this.editor, projectPath: this.options.projectPath }), + new MarkdownExtension({ + instance: this.editor, + previewMarkdownPath: this.options.previewMarkdownPath, + }), ); this.hasMarkdownExtension = true; addEditorMarkdownListeners(this.editor); diff --git a/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js b/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js index 76e009164f70f27eeb3ad7d7a168827848e71d2b..57de21c933ef66b1ab3a8800e43dd21d0d5cf40a 100644 --- a/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js +++ b/app/assets/javascripts/editor/extensions/source_editor_markdown_ext.js @@ -14,17 +14,9 @@ import { } from '../constants'; import { SourceEditorExtension } from './source_editor_extension_base'; -const getPreview = (text, projectPath = '') => { - let url; - - if (projectPath) { - url = `/${projectPath}/preview_markdown`; - } else { - const { group, project } = document.body.dataset; - url = `/${group}/${project}/preview_markdown`; - } +const getPreview = (text, previewMarkdownPath) => { return axios - .post(url, { + .post(previewMarkdownPath, { text, }) .then(({ data }) => { @@ -43,10 +35,10 @@ const setupDomElement = ({ injectToEl = null } = {}) => { }; export class EditorMarkdownExtension extends SourceEditorExtension { - constructor({ instance, projectPath, ...args } = {}) { + constructor({ instance, previewMarkdownPath, ...args } = {}) { super({ instance, ...args }); Object.assign(instance, { - projectPath, + previewMarkdownPath, preview: { el: undefined, action: undefined, @@ -112,7 +104,7 @@ export class EditorMarkdownExtension extends SourceEditorExtension { fetchPreview() { const { el: previewEl } = this.preview; - getPreview(this.getValue(), this.projectPath) + getPreview(this.getValue(), this.previewMarkdownPath) .then((data) => { previewEl.innerHTML = sanitize(data); syntaxHighlight(previewEl.querySelectorAll('.js-syntax-highlight')); diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 2f990280367f8c662b33ffb68ea6126f95b5319d..2bf99550bf22908f534fdc0c0861c7acfd437833 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -79,6 +79,7 @@ export default { 'editorTheme', 'entries', 'currentProjectId', + 'previewMarkdownPath', ]), ...mapGetters([ 'getAlert', @@ -314,14 +315,15 @@ export default { if ( this.fileType === MARKDOWN_FILE_TYPE && - this.editor?.getEditorType() === EDITOR_TYPE_CODE + this.editor?.getEditorType() === EDITOR_TYPE_CODE && + this.previewMarkdownPath ) { import('~/editor/extensions/source_editor_markdown_ext') .then(({ EditorMarkdownExtension: MarkdownExtension } = {}) => { this.editor.use( new MarkdownExtension({ instance: this.editor, - projectPath: this.currentProjectId, + previewMarkdownPath: this.previewMarkdownPath, }), ); }) diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index e8c726d618424b4a3a741a49a1cddbb6451bad6c..bdffed7088298579c7c07d83cfece24747fb4850 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -63,6 +63,7 @@ export function initIde(el, options = {}) { editorTheme: window.gon?.user_color_scheme || DEFAULT_THEME, codesandboxBundlerUrl: el.dataset.codesandboxBundlerUrl, environmentsGuidanceAlertDismissed: !parseBoolean(el.dataset.enableEnvironmentsGuidance), + previewMarkdownPath: el.dataset.previewMarkdownPath, }); }, beforeDestroy() { diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 83551e87f099322b5e7c5d5adc874a449ea88741..526987c750a565a30430bf3708136521ccbcce9f 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -32,4 +32,5 @@ export default () => ({ codesandboxBundlerUrl: null, environmentsGuidanceAlertDismissed: false, environmentsGuidanceAlertDetected: false, + previewMarkdownPath: '', }); diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index eccd0e7a34cabcab3322e757a339549aef73accb..c1a33794b509fc4497188162c30883112be7678d 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -220,7 +220,8 @@ def blob_editor_paths(project) 'assets-prefix' => Gitlab::Application.config.assets.prefix, 'blob-filename' => @blob && @blob.path, 'project-id' => project.id, - 'is-markdown' => @blob && @blob.path && Gitlab::MarkupHelper.gitlab_markdown?(@blob.path) + 'is-markdown' => @blob && @blob.path && Gitlab::MarkupHelper.gitlab_markdown?(@blob.path), + 'preview-markdown-path' => preview_markdown_path(project) } end diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 41c7abbbabdf33cf1128b15aaad1202fb9771c2e..09ff57e2bafddf2937fc4a4024459645f2c1bc2b 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -19,7 +19,8 @@ def ide_data 'merge-request' => @merge_request, 'fork-info' => @fork_info&.to_json, 'project' => convert_to_project_entity_json(@project), - 'enable-environments-guidance' => enable_environments_guidance?.to_s + 'enable-environments-guidance' => enable_environments_guidance?.to_s, + 'preview-markdown-path' => @project && preview_markdown_path(@project) } end diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js index 2be72ded0a2769178e0dca406fda57fa21b54ea8..ebef065675007c7675880b220203baf644b157c8 100644 --- a/spec/frontend/blob_edit/edit_blob_spec.js +++ b/spec/frontend/blob_edit/edit_blob_spec.js @@ -8,6 +8,8 @@ jest.mock('~/editor/source_editor'); jest.mock('~/editor/extensions/source_editor_markdown_ext'); jest.mock('~/editor/extensions/source_editor_file_template_ext'); +const PREVIEW_MARKDOWN_PATH = '/foo/bar/preview_markdown'; + describe('Blob Editing', () => { const useMock = jest.fn(); const mockInstance = { @@ -34,6 +36,7 @@ describe('Blob Editing', () => { const editorInst = (isMarkdown) => { return new EditBlob({ isMarkdown, + previewMarkdownPath: PREVIEW_MARKDOWN_PATH, }); }; @@ -44,6 +47,7 @@ describe('Blob Editing', () => { it('loads FileTemplateExtension by default', async () => { await initEditor(); + expect(useMock).toHaveBeenCalledWith(expect.any(FileTemplateExtension)); expect(FileTemplateExtension).toHaveBeenCalledTimes(1); }); @@ -55,9 +59,12 @@ describe('Blob Editing', () => { it('loads MarkdownExtension only for the markdown files', async () => { await initEditor(true); - expect(useMock).toHaveBeenCalledTimes(2); - expect(FileTemplateExtension).toHaveBeenCalledTimes(1); + expect(useMock).toHaveBeenCalledWith(expect.any(EditorMarkdownExtension)); expect(EditorMarkdownExtension).toHaveBeenCalledTimes(1); + expect(EditorMarkdownExtension).toHaveBeenCalledWith({ + instance: mockInstance, + previewMarkdownPath: PREVIEW_MARKDOWN_PATH, + }); }); }); diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js index 48ccc10e4868a47478b1abac73b2bd5b550df58e..245c6c28d3149023bc19a38476d836dcb2fd1c53 100644 --- a/spec/frontend/editor/source_editor_markdown_ext_spec.js +++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js @@ -23,7 +23,7 @@ describe('Markdown Extension for Source Editor', () => { let editorEl; let panelSpy; let mockAxios; - const projectPath = 'fooGroup/barProj'; + const previewMarkdownPath = '/gitlab/fooGroup/barProj/preview_markdown'; const firstLine = 'This is a'; const secondLine = 'multiline'; const thirdLine = 'string with some **markup**'; @@ -57,7 +57,7 @@ describe('Markdown Extension for Source Editor', () => { blobPath: markdownPath, blobContent: text, }); - editor.use(new EditorMarkdownExtension({ instance, projectPath })); + editor.use(new EditorMarkdownExtension({ instance, previewMarkdownPath })); panelSpy = jest.spyOn(EditorMarkdownExtension, 'togglePreviewPanel'); }); @@ -74,7 +74,7 @@ describe('Markdown Extension for Source Editor', () => { shown: false, modelChangeListener: undefined, }); - expect(instance.projectPath).toBe(projectPath); + expect(instance.previewMarkdownPath).toBe(previewMarkdownPath); }); describe('model language changes listener', () => { @@ -223,34 +223,24 @@ describe('Markdown Extension for Source Editor', () => { }); describe('fetchPreview', () => { - const group = 'foo'; - const project = 'bar'; - const setData = (path, g, p) => { - instance.projectPath = path; - document.body.setAttribute('data-group', g); - document.body.setAttribute('data-project', p); - }; const fetchPreview = async () => { instance.fetchPreview(); await waitForPromises(); }; + let previewMarkdownSpy; + beforeEach(() => { - mockAxios.onPost().reply(200, { body: responseData }); + previewMarkdownSpy = jest.fn().mockImplementation(() => [200, { body: responseData }]); + mockAxios.onPost(previewMarkdownPath).replyOnce((req) => previewMarkdownSpy(req)); }); - it('correctly fetches preview based on projectPath', async () => { - setData(projectPath, group, project); + it('correctly fetches preview based on previewMarkdownPath', async () => { await fetchPreview(); - expect(mockAxios.history.post[0].url).toBe(`/${projectPath}/preview_markdown`); - expect(mockAxios.history.post[0].data).toEqual(JSON.stringify({ text })); - }); - it('correctly fetches preview based on group and project data attributes', async () => { - setData(undefined, group, project); - await fetchPreview(); - expect(mockAxios.history.post[0].url).toBe(`/${group}/${project}/preview_markdown`); - expect(mockAxios.history.post[0].data).toEqual(JSON.stringify({ text })); + expect(previewMarkdownSpy).toHaveBeenCalledWith( + expect.objectContaining({ data: JSON.stringify({ text }) }), + ); }); it('puts the fetched content into the preview DOM element', async () => { diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js index b2254de706c13f57ab28fc155ea7d0304ac20a90..47bcfb59a5f7f28060f6d35224afed37f362da6c 100644 --- a/spec/frontend/ide/components/repo_editor_spec.js +++ b/spec/frontend/ide/components/repo_editor_spec.js @@ -24,6 +24,8 @@ import axios from '~/lib/utils/axios_utils'; import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue'; import { file } from '../helpers'; +const PREVIEW_MARKDOWN_PATH = '/foo/bar/preview_markdown'; + const defaultFileProps = { ...file('file.txt'), content: 'hello world', @@ -77,6 +79,7 @@ const prepareStore = (state, activeFile) => { entries: { [activeFile.path]: activeFile, }, + previewMarkdownPath: PREVIEW_MARKDOWN_PATH, }; const storeOptions = createStoreOptions(); return new Vuex.Store({ @@ -278,10 +281,10 @@ describe('RepoEditor', () => { async ({ activeFile, viewer, shouldHaveMarkdownExtension } = {}) => { await createComponent({ state: { viewer }, activeFile }); if (shouldHaveMarkdownExtension) { - expect(vm.editor.projectPath).toBe(vm.currentProjectId); + expect(vm.editor.previewMarkdownPath).toBe(PREVIEW_MARKDOWN_PATH); expect(vm.editor.togglePreview).toBeDefined(); } else { - expect(vm.editor.projectPath).toBeUndefined(); + expect(vm.editor.previewMarkdownPath).toBeUndefined(); expect(vm.editor.togglePreview).toBeUndefined(); } }, diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb index d34358e49c0b3d985d4a6b560bf5fa0b3df9b819..503ad3ad66d69a0d331f9903427a4580f61702c5 100644 --- a/spec/helpers/ide_helper_spec.rb +++ b/spec/helpers/ide_helper_spec.rb @@ -18,7 +18,8 @@ 'file-path' => nil, 'merge-request' => nil, 'fork-info' => nil, - 'project' => nil + 'project' => nil, + 'preview-markdown-path' => nil ) end end @@ -41,7 +42,8 @@ 'file-path' => 'foo/bar', 'merge-request' => '1', 'fork-info' => fork_info.to_json, - 'project' => serialized_project + 'project' => serialized_project, + 'preview-markdown-path' => Gitlab::Routing.url_helpers.preview_markdown_project_path(project) ) end end