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:
Diffstat (limited to 'app/assets/javascripts/static_site_editor')
-rw-r--r--app/assets/javascripts/static_site_editor/components/edit_area.vue45
-rw-r--r--app/assets/javascripts/static_site_editor/components/unsaved_changes_confirm_dialog.vue27
-rw-r--r--app/assets/javascripts/static_site_editor/constants.js2
-rw-r--r--app/assets/javascripts/static_site_editor/pages/home.vue6
-rw-r--r--app/assets/javascripts/static_site_editor/services/parse_source_file.js55
-rw-r--r--app/assets/javascripts/static_site_editor/services/submit_content_changes.js13
6 files changed, 140 insertions, 8 deletions
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 dff21d919a9..e9efef40632 100644
--- a/app/assets/javascripts/static_site_editor/components/edit_area.vue
+++ b/app/assets/javascripts/static_site_editor/components/edit_area.vue
@@ -2,12 +2,16 @@
import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue';
import PublishToolbar from './publish_toolbar.vue';
import EditHeader from './edit_header.vue';
+import UnsavedChangesConfirmDialog from './unsaved_changes_confirm_dialog.vue';
+import parseSourceFile from '~/static_site_editor/services/parse_source_file';
+import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/constants';
export default {
components: {
RichContentEditor,
PublishToolbar,
EditHeader,
+ UnsavedChangesConfirmDialog,
},
props: {
title: {
@@ -30,26 +34,57 @@ export default {
},
data() {
return {
- editableContent: this.content,
saveable: false,
+ parsedSource: parseSourceFile(this.content),
+ editorMode: EDITOR_TYPES.wysiwyg,
};
},
computed: {
+ editableContent() {
+ return this.parsedSource.editable;
+ },
+ editableKey() {
+ return this.isWysiwygMode ? 'body' : 'raw';
+ },
+ isWysiwygMode() {
+ return this.editorMode === EDITOR_TYPES.wysiwyg;
+ },
modified() {
- return this.content !== this.editableContent;
+ return this.isWysiwygMode
+ ? this.parsedSource.isModifiedBody()
+ : this.parsedSource.isModifiedRaw();
},
},
methods: {
+ syncSource() {
+ if (this.isWysiwygMode) {
+ this.parsedSource.syncBody();
+ return;
+ }
+
+ this.parsedSource.syncRaw();
+ },
+ onModeChange(mode) {
+ this.editorMode = mode;
+ this.syncSource();
+ },
onSubmit() {
- this.$emit('submit', { content: this.editableContent });
+ this.syncSource();
+ this.$emit('submit', { content: this.editableContent.raw });
},
},
};
</script>
<template>
- <div class="d-flex flex-grow-1 flex-column">
+ <div class="d-flex flex-grow-1 flex-column h-100">
<edit-header class="py-2" :title="title" />
- <rich-content-editor v-model="editableContent" class="mb-9" />
+ <rich-content-editor
+ v-model="editableContent[editableKey]"
+ :initial-edit-type="editorMode"
+ class="mb-9 h-100"
+ @modeChange="onModeChange"
+ />
+ <unsaved-changes-confirm-dialog :modified="modified" />
<publish-toolbar
class="gl-fixed gl-left-0 gl-bottom-0 gl-w-full"
:return-url="returnUrl"
diff --git a/app/assets/javascripts/static_site_editor/components/unsaved_changes_confirm_dialog.vue b/app/assets/javascripts/static_site_editor/components/unsaved_changes_confirm_dialog.vue
new file mode 100644
index 00000000000..255f029bd27
--- /dev/null
+++ b/app/assets/javascripts/static_site_editor/components/unsaved_changes_confirm_dialog.vue
@@ -0,0 +1,27 @@
+<script>
+export default {
+ props: {
+ modified: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ created() {
+ window.addEventListener('beforeunload', this.requestConfirmation);
+ },
+ destroyed() {
+ window.removeEventListener('beforeunload', this.requestConfirmation);
+ },
+ methods: {
+ requestConfirmation(e) {
+ if (this.modified) {
+ e.preventDefault();
+ // eslint-disable-next-line no-param-reassign
+ e.returnValue = '';
+ }
+ },
+ },
+ render: () => null,
+};
+</script>
diff --git a/app/assets/javascripts/static_site_editor/constants.js b/app/assets/javascripts/static_site_editor/constants.js
index 4794cf5eead..947347922f2 100644
--- a/app/assets/javascripts/static_site_editor/constants.js
+++ b/app/assets/javascripts/static_site_editor/constants.js
@@ -17,3 +17,5 @@ export const LOAD_CONTENT_ERROR = __(
export const DEFAULT_HEADING = s__('StaticSiteEditor|Static site editor');
export const TRACKING_ACTION_CREATE_COMMIT = 'create_commit';
+export const TRACKING_ACTION_CREATE_MERGE_REQUEST = 'create_merge_request';
+export const TRACKING_ACTION_INITIALIZE_EDITOR = 'initialize_editor';
diff --git a/app/assets/javascripts/static_site_editor/pages/home.vue b/app/assets/javascripts/static_site_editor/pages/home.vue
index f65b648acd6..a1314c8a478 100644
--- a/app/assets/javascripts/static_site_editor/pages/home.vue
+++ b/app/assets/javascripts/static_site_editor/pages/home.vue
@@ -7,7 +7,8 @@ import appDataQuery from '../graphql/queries/app_data.query.graphql';
import sourceContentQuery from '../graphql/queries/source_content.query.graphql';
import submitContentChangesMutation from '../graphql/mutations/submit_content_changes.mutation.graphql';
import createFlash from '~/flash';
-import { LOAD_CONTENT_ERROR } from '../constants';
+import Tracking from '~/tracking';
+import { LOAD_CONTENT_ERROR, TRACKING_ACTION_INITIALIZE_EDITOR } from '../constants';
import { SUCCESS_ROUTE } from '../router/constants';
export default {
@@ -59,6 +60,9 @@ export default {
return Boolean(this.sourceContent);
},
},
+ mounted() {
+ Tracking.event(document.body.dataset.page, TRACKING_ACTION_INITIALIZE_EDITOR);
+ },
methods: {
onDismissError() {
this.submitChangesError = null;
diff --git a/app/assets/javascripts/static_site_editor/services/parse_source_file.js b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
new file mode 100644
index 00000000000..f32c693411f
--- /dev/null
+++ b/app/assets/javascripts/static_site_editor/services/parse_source_file.js
@@ -0,0 +1,55 @@
+const parseSourceFile = raw => {
+ const frontMatterRegex = /(^---$[\s\S]*?^---$)/m;
+ const preGroupedRegex = /([\s\S]*?)(^---$[\s\S]*?^---$)(\s*)([\s\S]*)/m; // preFrontMatter, frontMatter, spacing, and content
+ let initial;
+ let editable;
+
+ const hasFrontMatter = source => frontMatterRegex.test(source);
+
+ const buildPayload = (source, header, spacing, body) => {
+ return { raw: source, header, spacing, body };
+ };
+
+ const parse = source => {
+ if (hasFrontMatter(source)) {
+ const match = source.match(preGroupedRegex);
+ const [, preFrontMatter, frontMatter, spacing, content] = match;
+ const header = preFrontMatter + frontMatter;
+
+ return buildPayload(source, header, spacing, content);
+ }
+
+ return buildPayload(source, '', '', source);
+ };
+
+ const computedRaw = () => `${editable.header}${editable.spacing}${editable.body}`;
+
+ const syncBody = () => {
+ /*
+ We re-parse as markdown editing could have added non-body changes (preFrontMatter, frontMatter, or spacing).
+ Re-parsing additionally gets us the desired body that was extracted from the mutated editable.raw
+ Additionally we intentionally mutate the existing editable's key values as opposed to reassigning the object itself so consumers of the potentially reactive property stay in sync.
+ */
+ Object.assign(editable, parse(editable.raw));
+ };
+
+ const syncRaw = () => {
+ editable.raw = computedRaw();
+ };
+
+ const isModifiedRaw = () => initial.raw !== editable.raw;
+ const isModifiedBody = () => initial.raw !== computedRaw();
+
+ initial = parse(raw);
+ editable = parse(raw);
+
+ return {
+ editable,
+ isModifiedRaw,
+ isModifiedBody,
+ syncRaw,
+ syncBody,
+ };
+};
+
+export default parseSourceFile;
diff --git a/app/assets/javascripts/static_site_editor/services/submit_content_changes.js b/app/assets/javascripts/static_site_editor/services/submit_content_changes.js
index 49135d2141b..fce7c1f918f 100644
--- a/app/assets/javascripts/static_site_editor/services/submit_content_changes.js
+++ b/app/assets/javascripts/static_site_editor/services/submit_content_changes.js
@@ -10,6 +10,7 @@ import {
SUBMIT_CHANGES_COMMIT_ERROR,
SUBMIT_CHANGES_MERGE_REQUEST_ERROR,
TRACKING_ACTION_CREATE_COMMIT,
+ TRACKING_ACTION_CREATE_MERGE_REQUEST,
} from '../constants';
const createBranch = (projectId, branch) =>
@@ -41,8 +42,15 @@ const commitContent = (projectId, message, branch, sourcePath, content) => {
});
};
-const createMergeRequest = (projectId, title, sourceBranch, targetBranch = DEFAULT_TARGET_BRANCH) =>
- Api.createProjectMergeRequest(
+const createMergeRequest = (
+ projectId,
+ title,
+ sourceBranch,
+ targetBranch = DEFAULT_TARGET_BRANCH,
+) => {
+ Tracking.event(document.body.dataset.page, TRACKING_ACTION_CREATE_MERGE_REQUEST);
+
+ return Api.createProjectMergeRequest(
projectId,
convertObjectPropsToSnakeCase({
title,
@@ -52,6 +60,7 @@ const createMergeRequest = (projectId, title, sourceBranch, targetBranch = DEFAU
).catch(() => {
throw new Error(SUBMIT_CHANGES_MERGE_REQUEST_ERROR);
});
+};
const submitContentChanges = ({ username, projectId, sourcePath, content }) => {
const branch = generateBranchName(username);