Commit 1a2a9fe9 authored by Paul Slaughter's avatar Paul Slaughter

Fix Live Markdown Preview in personal and subgroup projects

- Also fixes issue with relative URL's
- https://gitlab.com/gitlab-org/gitlab/-/issues/339117

Changelog: fixed
parent a24b5fa0
......@@ -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();
......
......@@ -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);
......
......@@ -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'));
......
......@@ -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,
}),
);
})
......
......@@ -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() {
......
......@@ -32,4 +32,5 @@ export default () => ({
codesandboxBundlerUrl: null,
environmentsGuidanceAlertDismissed: false,
environmentsGuidanceAlertDetected: false,
previewMarkdownPath: '',
});
......@@ -220,7 +220,8 @@ module BlobHelper
'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
......
......@@ -19,7 +19,8 @@ module IdeHelper
'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
......
......@@ -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,
});
});
});
......
......@@ -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 () => {
......
......@@ -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();
}
},
......
......@@ -18,7 +18,8 @@ RSpec.describe IdeHelper do
'file-path' => nil,
'merge-request' => nil,
'fork-info' => nil,
'project' => nil
'project' => nil,
'preview-markdown-path' => nil
)
end
end
......@@ -41,7 +42,8 @@ RSpec.describe IdeHelper do
'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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment