mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	# 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>
		
			
				
	
	
		
			162 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			162 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<script setup>
 | 
						|
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: '' },
 | 
						|
  messageType: {
 | 
						|
    type: String,
 | 
						|
    default: 'info',
 | 
						|
    validator: value => ['info', 'error', 'success'].includes(value),
 | 
						|
  },
 | 
						|
  enableVariables: { type: Boolean, default: false },
 | 
						|
  enableCannedResponses: { type: Boolean, default: true },
 | 
						|
  enabledMenuOptions: { type: Array, default: () => [] },
 | 
						|
});
 | 
						|
 | 
						|
const emit = defineEmits(['update:modelValue']);
 | 
						|
 | 
						|
const slots = useSlots();
 | 
						|
 | 
						|
const isFocused = ref(false);
 | 
						|
 | 
						|
const characterCount = computed(() => props.modelValue.length);
 | 
						|
 | 
						|
const messageClass = computed(() => {
 | 
						|
  switch (props.messageType) {
 | 
						|
    case 'error':
 | 
						|
      return 'text-n-ruby-9 dark:text-n-ruby-9';
 | 
						|
    case 'success':
 | 
						|
      return 'text-green-500 dark:text-green-400';
 | 
						|
    default:
 | 
						|
      return 'text-n-slate-11 dark:text-n-slate-11';
 | 
						|
  }
 | 
						|
});
 | 
						|
 | 
						|
const handleInput = value => {
 | 
						|
  if (!props.disabled) {
 | 
						|
    emit('update:modelValue', value);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const handleFocus = () => {
 | 
						|
  if (!props.disabled) {
 | 
						|
    isFocused.value = true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
const handleBlur = () => {
 | 
						|
  if (!props.disabled) {
 | 
						|
    isFocused.value = false;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
watch(
 | 
						|
  () => props.modelValue,
 | 
						|
  newValue => {
 | 
						|
    if (props.maxLength && props.showCharacterCount && !slots.actions) {
 | 
						|
      if (characterCount.value >= props.maxLength) {
 | 
						|
        emit('update:modelValue', newValue.slice(0, props.maxLength));
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
);
 | 
						|
</script>
 | 
						|
 | 
						|
<template>
 | 
						|
  <div class="flex flex-col min-w-0 gap-1">
 | 
						|
    <label v-if="label" class="mb-0.5 text-sm font-medium text-n-slate-12">
 | 
						|
      {{ label }}
 | 
						|
    </label>
 | 
						|
    <div
 | 
						|
      class="flex flex-col w-full gap-2 px-3 py-3 transition-all duration-500 ease-in-out border rounded-lg editor-wrapper bg-n-alpha-black2"
 | 
						|
      :class="[
 | 
						|
        {
 | 
						|
          'cursor-not-allowed opacity-50 pointer-events-none !bg-n-alpha-black2 disabled:border-n-weak dark:disabled:border-n-weak':
 | 
						|
            disabled,
 | 
						|
          'border-n-brand dark:border-n-brand': isFocused,
 | 
						|
          'hover:border-n-slate-6 dark:hover:border-n-slate-6 border-n-weak dark:border-n-weak':
 | 
						|
            !isFocused && messageType !== 'error',
 | 
						|
          'border-n-ruby-8 dark:border-n-ruby-8 hover:border-n-ruby-9 dark:hover:border-n-ruby-9':
 | 
						|
            messageType === 'error' && !isFocused,
 | 
						|
        },
 | 
						|
      ]"
 | 
						|
    >
 | 
						|
      <WootEditor
 | 
						|
        :model-value="modelValue"
 | 
						|
        :placeholder="placeholder"
 | 
						|
        :focus-on-mount="focusOnMount"
 | 
						|
        :disabled="disabled"
 | 
						|
        :enable-variables="enableVariables"
 | 
						|
        :enable-canned-responses="enableCannedResponses"
 | 
						|
        :enabled-menu-options="enabledMenuOptions"
 | 
						|
        @input="handleInput"
 | 
						|
        @focus="handleFocus"
 | 
						|
        @blur="handleBlur"
 | 
						|
      />
 | 
						|
      <div
 | 
						|
        v-if="showCharacterCount || slots.actions"
 | 
						|
        class="flex items-center justify-end h-4 ltr:right-3 rtl:left-3"
 | 
						|
      >
 | 
						|
        <span
 | 
						|
          v-if="showCharacterCount && !slots.actions"
 | 
						|
          class="text-xs tabular-nums text-n-slate-10"
 | 
						|
        >
 | 
						|
          {{ characterCount }} / {{ maxLength }}
 | 
						|
        </span>
 | 
						|
        <slot v-else name="actions" />
 | 
						|
      </div>
 | 
						|
    </div>
 | 
						|
    <p
 | 
						|
      v-if="message"
 | 
						|
      class="min-w-0 mt-1 mb-0 text-xs truncate transition-all duration-500 ease-in-out"
 | 
						|
      :class="messageClass"
 | 
						|
    >
 | 
						|
      {{ message }}
 | 
						|
    </p>
 | 
						|
  </div>
 | 
						|
</template>
 | 
						|
 | 
						|
<style lang="scss" scoped>
 | 
						|
.editor-wrapper {
 | 
						|
  ::v-deep {
 | 
						|
    .ProseMirror-menubar-wrapper {
 | 
						|
      @apply gap-2 !important;
 | 
						|
 | 
						|
      .ProseMirror-menubar {
 | 
						|
        @apply bg-transparent dark:bg-transparent w-fit left-1 pt-0 h-5 !top-0 !relative !important;
 | 
						|
 | 
						|
        .ProseMirror-menuitem {
 | 
						|
          @apply h-5 !important;
 | 
						|
        }
 | 
						|
 | 
						|
        .ProseMirror-icon {
 | 
						|
          @apply p-1 w-3 h-3 text-n-slate-12 dark:text-n-slate-12 !important;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      .ProseMirror.ProseMirror-woot-style {
 | 
						|
        p {
 | 
						|
          @apply first:mt-0 !important;
 | 
						|
        }
 | 
						|
 | 
						|
        .empty-node {
 | 
						|
          @apply m-0 !important;
 | 
						|
 | 
						|
          &::before {
 | 
						|
            @apply text-n-slate-11 dark:text-n-slate-11;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
</style>
 |