Просмотр исходного кода

Fixes: Mobile - Improve new line handling for HTML output in the editor

Vladimir Sheremet 2 лет назад
Родитель
Сommit
2ad3911e0d

+ 2 - 16
app/frontend/apps/mobile/pages/ticket/composable/useTicketEdit.ts

@@ -13,6 +13,7 @@ import type { TicketArticleFormValues } from '@shared/entities/ticket-article/ac
 import type { PartialRequired } from '@shared/types/utils'
 import { convertFilesToAttachmentInput } from '@shared/utils/files'
 import { getNodeByName } from '@shared/components/Form/utils'
+import { populateEditorNewLines } from '@shared/components/Form/fields/FieldEditor/utils'
 import { useTicketUpdateMutation } from '../graphql/mutations/update.api'
 
 type TicketArticleReceivedFormValues = PartialRequired<
@@ -64,22 +65,7 @@ export const useTicketEdit = (
       getNodeByName(formId, 'body')?.context?.contentType || 'text/html'
 
     if (contentType === 'text/html') {
-      const body = document.createElement('div')
-      body.innerHTML = article.body
-      // TODO: https://github.com/zammad/coordination-feature-mobile-view/issues/396
-      // prosemirror always adds a visible linebreak inside an empty paragraph,
-      // but it doesn't return it inside a schema, so we need to add it manually
-      body.querySelectorAll('p').forEach((p) => {
-        p.removeAttribute('data-marker')
-        if (
-          p.childNodes.length === 0 ||
-          p.lastChild?.nodeType !== Node.TEXT_NODE ||
-          p.textContent?.endsWith('\n')
-        ) {
-          p.appendChild(document.createElement('br'))
-        }
-      })
-      article.body = body.innerHTML
+      article.body = populateEditorNewLines(article.body)
     }
 
     return {

+ 2 - 1
app/frontend/apps/mobile/pages/ticket/views/TicketCreate.vue

@@ -27,6 +27,7 @@ import {
 import { ErrorStatusCodes } from '@shared/types/error'
 import type UserError from '@shared/errors/UserError'
 import { defineFormSchema } from '@mobile/form/defineFormSchema'
+import { populateEditorNewLines } from '@shared/components/Form/fields/FieldEditor/utils'
 import CommonStepper from '@mobile/components/CommonStepper/CommonStepper.vue'
 import CommonButton from '@mobile/components/CommonButton/CommonButton.vue'
 import CommonBackButton from '@mobile/components/CommonBackButton/CommonBackButton.vue'
@@ -299,7 +300,7 @@ const createTicket = async (formData: FormData<TicketFormData>) => {
     ...internalObjectAttributeValues,
     article: {
       cc: formData.cc,
-      body: formData.body,
+      body: populateEditorNewLines(formData.body),
       sender: isTicketCustomer.value
         ? 'Customer'
         : ticketCreateArticleType[formData.articleSenderType].sender,

+ 9 - 2
app/frontend/shared/components/Form/fields/FieldEditor/FieldEditorInput.vue

@@ -8,6 +8,7 @@ import { useEditor, EditorContent } from '@tiptap/vue-3'
 import { useEventListener } from '@vueuse/core'
 import { computed, onMounted, onUnmounted, ref, toRef, watch } from 'vue'
 import testFlags from '@shared/utils/testFlags'
+import { htmlCleanup } from '@shared/utils/htmlCleanup'
 import useValue from '../../composables/useValue'
 import {
   getCustomExtensions,
@@ -107,7 +108,10 @@ const editor = useEditor({
     },
   },
   editable: props.context.disabled !== true,
-  content: currentValue.value,
+  content:
+    currentValue.value && contentType.value === 'text/html'
+      ? htmlCleanup(currentValue.value)
+      : currentValue.value,
   onUpdate({ editor }) {
     const content =
       contentType.value === 'text/plain' ? editor.getText() : editor.getHTML()
@@ -159,7 +163,10 @@ const updateValueKey = props.context.node.on('input', ({ payload: value }) => {
       ? editor.value?.getText()
       : editor.value?.getHTML()
   if (editor.value && value !== currentValue) {
-    editor.value.commands.setContent(value, false)
+    editor.value.commands.setContent(
+      value && contentType.value === 'text/html' ? htmlCleanup(value) : value,
+      false,
+    )
   }
 })
 

+ 0 - 5
app/frontend/shared/components/Form/fields/FieldEditor/extensions/HardBreakParagraph.ts

@@ -1,5 +0,0 @@
-// Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
-
-import HardBreak from '@tiptap/extension-hard-break'
-
-export default HardBreak

+ 0 - 0
app/frontend/shared/components/Form/fields/FieldEditor/extensions/HardBreakSimple.ts → app/frontend/shared/components/Form/fields/FieldEditor/extensions/HardBreakPlain.ts


+ 4 - 4
app/frontend/shared/components/Form/fields/FieldEditor/extensions/list.ts

@@ -7,6 +7,7 @@ import Link from '@tiptap/extension-link'
 import Blockquote from '@tiptap/extension-blockquote'
 import StarterKit from '@tiptap/starter-kit'
 import Paragraph from '@tiptap/extension-paragraph'
+import HardBreak from '@tiptap/extension-hard-break'
 import CharacterCount from '@tiptap/extension-character-count'
 
 import type { Extensions } from '@tiptap/core'
@@ -17,8 +18,7 @@ import UserMention, { UserLink } from '../suggestions/UserMention'
 import KnowledgeBaseSuggestion from '../suggestions/KnowledgeBaseSuggestion'
 import TextModuleSuggestion from '../suggestions/TextModuleSuggestion'
 import Image from './Image'
-import HardBreakSimple from './HardBreakSimple'
-import HardBreakParagraph from './HardBreakParagraph'
+import HardBreakPlain from './HardBreakPlain'
 import Signature from './Signature'
 import type { FieldEditorProps } from '../types'
 
@@ -40,7 +40,7 @@ export const getPlainExtensions = (): Extensions => [
     orderedList: false,
     strike: false,
   }),
-  HardBreakSimple,
+  HardBreakPlain,
   CharacterCount,
 ]
 
@@ -66,7 +66,7 @@ export const getHtmlExtensions = (): Extensions => [
   Underline,
   OrderedList,
   ListItem,
-  HardBreakParagraph,
+  HardBreak,
   Blockquote.extend({
     addAttributes() {
       return {

+ 19 - 0
app/frontend/shared/components/Form/fields/FieldEditor/utils.ts

@@ -0,0 +1,19 @@
+// Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
+
+export const populateEditorNewLines = (htmlContent: string): string => {
+  const body = document.createElement('div')
+  body.innerHTML = htmlContent
+  // prosemirror always adds a visible linebreak inside an empty paragraph,
+  // but it doesn't return it inside a schema, so we need to add it manually
+  body.querySelectorAll('p').forEach((p) => {
+    p.removeAttribute('data-marker')
+    if (
+      p.childNodes.length === 0 ||
+      p.lastChild?.nodeType !== Node.TEXT_NODE ||
+      p.textContent?.endsWith('\n')
+    ) {
+      p.appendChild(document.createElement('br'))
+    }
+  })
+  return body.innerHTML
+}

+ 2 - 2
app/frontend/shared/entities/ticket-article/action/plugins/__tests__/action-email-forward.spec.ts

@@ -17,7 +17,7 @@ describe('building header, when "forward" action is called', () => {
     }
 
     expect(buildEmailForwardHeader(article, meta)).toBe(
-      '<p>Subject: Article Subject<br>Date: 2020-02-01 00:00<br>From: Jhon Doe &lt;jhon.doe@email.dcom&gt;<br>To: Agent Smith &lt;smith.a@matrix.com&gt;<br>CC: Agent Rodrigez<br></p>',
+      '<p>Subject: Article Subject<br>Date: 2020-02-01 00:00<br>From: Jhon Doe &lt;jhon.doe@email.dcom&gt;<br>To: Agent Smith &lt;smith.a@matrix.com&gt;<br>CC: Agent Rodrigez<br><br></p>',
     )
   })
 
@@ -34,7 +34,7 @@ describe('building header, when "forward" action is called', () => {
     }
 
     expect(buildEmailForwardHeader(article, meta)).toBe(
-      '<p>Date: 2020-02-01 00:00<br>From: Jhon Doe &lt;jhon.doe@email.dcom&gt;<br>To: Agent Smith &lt;smith.a@matrix.com&gt;<br></p>',
+      '<p>Date: 2020-02-01 00:00<br>From: Jhon Doe &lt;jhon.doe@email.dcom&gt;<br>To: Agent Smith &lt;smith.a@matrix.com&gt;<br><br></p>',
     )
   })
 })

+ 2 - 0
app/frontend/shared/entities/ticket-article/action/plugins/email/forward.ts

@@ -40,6 +40,8 @@ export const buildEmailForwardHeader = (
     return acc
   }, document.createElement('p'))
 
+  output.appendChild(document.createElement('br'))
+
   return output.outerHTML
 }
 

+ 1 - 1
app/frontend/shared/entities/ticket-article/action/plugins/email/reply.ts

@@ -189,7 +189,7 @@ export const replyToEmail = (
   if (selection) {
     const header = getReplyQuoteHeader(config, article)
     // data-full will be removed by the backend, it's used only for siganture handling
-    selection = `<br><blockquote type="cite" ${
+    selection = `${full ? '' : '<br>'}<blockquote type="cite" ${
       full ? 'data-marker="signature-before"' : ''
     }>${header}${selection}</blockquote>`
   }

Некоторые файлы не были показаны из-за большого количества измененных файлов