mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	feat: Add rich text support for widget welcome tagline (#11666)
# Pull Request Template ## Description This PR adds rich text support for the widget welcome tagline, enabling users to format the message using basic text styling (bold, italics, links, etc.) https://github.com/chatwoot/chatwoot/pull/11629#issuecomment-2932448975 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screenshots <img width="1100" alt="image" src="https://github.com/user-attachments/assets/eef2bfd3-7bc9-4aea-b21d-078f6b29f334" /> <img width="448" alt="image" src="https://github.com/user-attachments/assets/713c6b07-d0dc-4c8f-bfba-cd119a858375" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
		@@ -4,38 +4,14 @@ import { computed, ref, watch, useSlots } from 'vue';
 | 
			
		||||
import WootEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelValue: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  label: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  placeholder: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  focusOnMount: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  maxLength: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 200,
 | 
			
		||||
  },
 | 
			
		||||
  showCharacterCount: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: true,
 | 
			
		||||
  },
 | 
			
		||||
  disabled: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  message: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
  },
 | 
			
		||||
  modelValue: { type: String, default: '' },
 | 
			
		||||
  label: { type: String, default: '' },
 | 
			
		||||
  placeholder: { type: String, default: '' },
 | 
			
		||||
  focusOnMount: { type: Boolean, default: false },
 | 
			
		||||
  maxLength: { type: Number, default: 200 },
 | 
			
		||||
  showCharacterCount: { type: Boolean, default: true },
 | 
			
		||||
  disabled: { type: Boolean, default: false },
 | 
			
		||||
  message: { type: String, default: '' },
 | 
			
		||||
  messageType: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'info',
 | 
			
		||||
@@ -43,6 +19,7 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
  enableVariables: { type: Boolean, default: false },
 | 
			
		||||
  enableCannedResponses: { type: Boolean, default: true },
 | 
			
		||||
  enabledMenuOptions: { type: Array, default: () => [] },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits(['update:modelValue']);
 | 
			
		||||
@@ -120,6 +97,7 @@ watch(
 | 
			
		||||
        :disabled="disabled"
 | 
			
		||||
        :enable-variables="enableVariables"
 | 
			
		||||
        :enable-canned-responses="enableCannedResponses"
 | 
			
		||||
        :enabled-menu-options="enabledMenuOptions"
 | 
			
		||||
        @input="handleInput"
 | 
			
		||||
        @focus="handleFocus"
 | 
			
		||||
        @blur="handleBlur"
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,14 @@ export const ARTICLE_EDITOR_MENU_OPTIONS = [
 | 
			
		||||
  'code',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const WIDGET_BUILDER_EDITOR_MENU_OPTIONS = [
 | 
			
		||||
  'strong',
 | 
			
		||||
  'em',
 | 
			
		||||
  'link',
 | 
			
		||||
  'undo',
 | 
			
		||||
  'redo',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const MESSAGE_EDITOR_IMAGE_RESIZES = [
 | 
			
		||||
  {
 | 
			
		||||
    name: 'Small',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  config: {
 | 
			
		||||
@@ -8,6 +9,8 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { formatMessage } = useMessageFormatter();
 | 
			
		||||
 | 
			
		||||
const isDefaultScreen = computed(() => {
 | 
			
		||||
  return (
 | 
			
		||||
    props.config.isDefaultScreen &&
 | 
			
		||||
@@ -53,12 +56,13 @@ const isDefaultScreen = computed(() => {
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div v-if="isDefaultScreen" class="overflow-auto max-h-60">
 | 
			
		||||
        <h2 class="mb-2 text-2xl break-words text-slate-900 dark:text-white">
 | 
			
		||||
        <h2 class="mb-2 text-2xl break-words text-n-slate-12">
 | 
			
		||||
          {{ config.welcomeHeading }}
 | 
			
		||||
        </h2>
 | 
			
		||||
        <p class="text-sm break-words text-slate-600 dark:text-slate-100">
 | 
			
		||||
          {{ config.welcomeTagline }}
 | 
			
		||||
        </p>
 | 
			
		||||
        <p
 | 
			
		||||
          v-dompurify-html="formatMessage(config.welcomeTagline)"
 | 
			
		||||
          class="text-sm break-words text-n-slate-11 [&_a]:!text-n-slate-11 [&_a]:underline"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@ import { FEATURE_FLAGS } from '../../../../featureFlags';
 | 
			
		||||
import SenderNameExamplePreview from './components/SenderNameExamplePreview.vue';
 | 
			
		||||
import NextButton from 'dashboard/components-next/button/Button.vue';
 | 
			
		||||
import { INBOX_TYPES } from 'dashboard/helper/inbox';
 | 
			
		||||
import { WIDGET_BUILDER_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
 | 
			
		||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
@@ -43,6 +45,7 @@ export default {
 | 
			
		||||
    NextButton,
 | 
			
		||||
    InstagramReauthorize,
 | 
			
		||||
    DuplicateInboxBanner,
 | 
			
		||||
    Editor,
 | 
			
		||||
  },
 | 
			
		||||
  mixins: [inboxMixin],
 | 
			
		||||
  setup() {
 | 
			
		||||
@@ -70,6 +73,7 @@ export default {
 | 
			
		||||
      selectedTabIndex: 0,
 | 
			
		||||
      selectedPortalSlug: '',
 | 
			
		||||
      showBusinessNameInput: false,
 | 
			
		||||
      welcomeTaglineEditorMenuOptions: WIDGET_BUILDER_EDITOR_MENU_OPTIONS,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@@ -480,10 +484,10 @@ export default {
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <woot-input
 | 
			
		||||
          <Editor
 | 
			
		||||
            v-if="isAWebWidgetInbox"
 | 
			
		||||
            v-model="channelWelcomeTagline"
 | 
			
		||||
            class="pb-4"
 | 
			
		||||
            class="mb-4"
 | 
			
		||||
            :label="
 | 
			
		||||
              $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WELCOME_TAGLINE.LABEL')
 | 
			
		||||
            "
 | 
			
		||||
@@ -492,6 +496,8 @@ export default {
 | 
			
		||||
                'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WELCOME_TAGLINE.PLACEHOLDER'
 | 
			
		||||
              )
 | 
			
		||||
            "
 | 
			
		||||
            :max-length="255"
 | 
			
		||||
            :enabled-menu-options="welcomeTaglineEditorMenuOptions"
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <label v-if="isAWebWidgetInbox" class="pb-4">
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,16 @@ import { useVuelidate } from '@vuelidate/core';
 | 
			
		||||
import { required } from '@vuelidate/validators';
 | 
			
		||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
 | 
			
		||||
import { LocalStorage } from 'shared/helpers/localStorage';
 | 
			
		||||
import { WIDGET_BUILDER_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
 | 
			
		||||
import NextButton from 'dashboard/components-next/button/Button.vue';
 | 
			
		||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    Widget,
 | 
			
		||||
    InputRadioGroup,
 | 
			
		||||
    NextButton,
 | 
			
		||||
    Editor,
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    inbox: {
 | 
			
		||||
@@ -71,6 +74,7 @@ export default {
 | 
			
		||||
          checked: false,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      welcomeTaglineEditorMenuOptions: WIDGET_BUILDER_EDITOR_MENU_OPTIONS,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@@ -310,7 +314,7 @@ export default {
 | 
			
		||||
                )
 | 
			
		||||
              "
 | 
			
		||||
            />
 | 
			
		||||
            <woot-input
 | 
			
		||||
            <Editor
 | 
			
		||||
              v-model="welcomeTagline"
 | 
			
		||||
              :label="
 | 
			
		||||
                $t(
 | 
			
		||||
@@ -322,6 +326,9 @@ export default {
 | 
			
		||||
                  'INBOX_MGMT.WIDGET_BUILDER.WIDGET_OPTIONS.WELCOME_TAGLINE.PLACE_HOLDER'
 | 
			
		||||
                )
 | 
			
		||||
              "
 | 
			
		||||
              :max-length="255"
 | 
			
		||||
              :enabled-menu-options="welcomeTaglineEditorMenuOptions"
 | 
			
		||||
              class="mb-4"
 | 
			
		||||
            />
 | 
			
		||||
            <label>
 | 
			
		||||
              {{
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,15 @@ import router from '../../../../index';
 | 
			
		||||
import NextButton from 'dashboard/components-next/button/Button.vue';
 | 
			
		||||
import PageHeader from '../../SettingsSubPageHeader.vue';
 | 
			
		||||
import GreetingsEditor from 'shared/components/GreetingsEditor.vue';
 | 
			
		||||
import { WIDGET_BUILDER_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
 | 
			
		||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    PageHeader,
 | 
			
		||||
    GreetingsEditor,
 | 
			
		||||
    NextButton,
 | 
			
		||||
    Editor,
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
@@ -21,6 +24,7 @@ export default {
 | 
			
		||||
      channelWelcomeTagline: '',
 | 
			
		||||
      greetingEnabled: false,
 | 
			
		||||
      greetingMessage: '',
 | 
			
		||||
      welcomeTaglineEditorMenuOptions: WIDGET_BUILDER_EDITOR_MENU_OPTIONS,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@@ -134,22 +138,21 @@ export default {
 | 
			
		||||
          />
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="w-full">
 | 
			
		||||
        <label>
 | 
			
		||||
          {{
 | 
			
		||||
            $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WELCOME_TAGLINE.LABEL')
 | 
			
		||||
          }}
 | 
			
		||||
          <input
 | 
			
		||||
      <Editor
 | 
			
		||||
        v-model="channelWelcomeTagline"
 | 
			
		||||
            type="text"
 | 
			
		||||
        :label="
 | 
			
		||||
          $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WELCOME_TAGLINE.LABEL')
 | 
			
		||||
        "
 | 
			
		||||
        :placeholder="
 | 
			
		||||
          $t(
 | 
			
		||||
            'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WELCOME_TAGLINE.PLACEHOLDER'
 | 
			
		||||
          )
 | 
			
		||||
        "
 | 
			
		||||
        :max-length="255"
 | 
			
		||||
        :enabled-menu-options="welcomeTaglineEditorMenuOptions"
 | 
			
		||||
        class="mb-4"
 | 
			
		||||
      />
 | 
			
		||||
        </label>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <label class="w-full">
 | 
			
		||||
        {{ $t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_GREETING_TOGGLE.LABEL') }}
 | 
			
		||||
        <select v-model="greetingEnabled">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
import HeaderActions from './HeaderActions.vue';
 | 
			
		||||
import { computed } from 'vue';
 | 
			
		||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  avatarUrl: {
 | 
			
		||||
@@ -21,6 +22,8 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { formatMessage } = useMessageFormatter();
 | 
			
		||||
 | 
			
		||||
const containerClasses = computed(() => [
 | 
			
		||||
  props.avatarUrl ? 'justify-between' : 'justify-end',
 | 
			
		||||
]);
 | 
			
		||||
@@ -47,8 +50,8 @@ const containerClasses = computed(() => [
 | 
			
		||||
      class="mt-4 text-2xl mb-1.5 font-medium text-n-slate-12"
 | 
			
		||||
    />
 | 
			
		||||
    <p
 | 
			
		||||
      v-dompurify-html="introBody"
 | 
			
		||||
      class="text-lg leading-normal text-n-slate-11"
 | 
			
		||||
      v-dompurify-html="formatMessage(introBody)"
 | 
			
		||||
      class="text-lg leading-normal text-n-slate-11 [&_a]:underline"
 | 
			
		||||
    />
 | 
			
		||||
  </header>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
        locale: '<%= @web_widget.account.locale %>',
 | 
			
		||||
        websiteName: '<%= @web_widget.inbox.name %>',
 | 
			
		||||
        websiteToken: '<%= @web_widget.website_token %>',
 | 
			
		||||
        welcomeTagline: '<%= @web_widget.welcome_tagline %>',
 | 
			
		||||
        welcomeTagline: <%= @web_widget.welcome_tagline.to_json.html_safe %>,
 | 
			
		||||
        welcomeTitle: '<%= @web_widget.welcome_title %>',
 | 
			
		||||
        widgetColor: '<%= @web_widget.widget_color %>',
 | 
			
		||||
        portal: <%= @web_widget.inbox.portal.to_json.html_safe %>,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user