diff options
Diffstat (limited to 'app/assets/javascripts/static_site_editor')
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); |