Merge branch 'develop' into fix/CW-6944
This commit is contained in:
@@ -49,7 +49,11 @@ const rules = computed(() => ({
|
||||
props.attribute.regexPattern && {
|
||||
regexValidation: value => {
|
||||
if (!value) return true;
|
||||
return getRegexp(props.attribute.regexPattern).test(value);
|
||||
try {
|
||||
return getRegexp(props.attribute.regexPattern).test(value);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -102,13 +102,14 @@ export default {
|
||||
return this.v$.editedValue.$error;
|
||||
},
|
||||
errorMessage() {
|
||||
if (this.v$.editedValue.url) {
|
||||
if (this.v$.editedValue.url?.$invalid) {
|
||||
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_URL');
|
||||
}
|
||||
if (!this.v$.editedValue.regexValidation) {
|
||||
return this.regexCue
|
||||
? this.regexCue
|
||||
: this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_INPUT');
|
||||
if (this.v$.editedValue.regexValidation?.$invalid) {
|
||||
return (
|
||||
this.regexCue ||
|
||||
this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.INVALID_INPUT')
|
||||
);
|
||||
}
|
||||
return this.$t('CUSTOM_ATTRIBUTES.VALIDATIONS.REQUIRED');
|
||||
},
|
||||
@@ -134,9 +135,12 @@ export default {
|
||||
editedValue: {
|
||||
required,
|
||||
regexValidation: value => {
|
||||
return !(
|
||||
this.attributeRegex && !getRegexp(this.attributeRegex).test(value)
|
||||
);
|
||||
if (!this.attributeRegex || !value) return true;
|
||||
try {
|
||||
return getRegexp(this.attributeRegex).test(value);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
triggerCharacters,
|
||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||
import imagePastePlugin from '@chatwoot/prosemirror-schema/src/plugins/image';
|
||||
import embedPreviewPlugin from '@chatwoot/prosemirror-schema/src/plugins/embedPreview';
|
||||
import trailingParagraphPlugin from '@chatwoot/prosemirror-schema/src/plugins/trailingParagraph';
|
||||
import { embeds as markdownEmbeds } from 'dashboard/helper/markdownEmbeds';
|
||||
import { toggleMark } from 'prosemirror-commands';
|
||||
import { wrapInList } from 'prosemirror-schema-list';
|
||||
import { toggleBlockType } from '@chatwoot/prosemirror-schema/src/menu/common';
|
||||
@@ -77,6 +80,8 @@ export default {
|
||||
plugins: [
|
||||
imagePastePlugin(this.handleImageUpload),
|
||||
this.createSlashPlugin(),
|
||||
embedPreviewPlugin(markdownEmbeds),
|
||||
trailingParagraphPlugin(),
|
||||
],
|
||||
isTextSelected: false, // Tracks text selection and prevents unnecessary re-renders on mouse selection
|
||||
showSlashMenu: false,
|
||||
@@ -113,6 +118,12 @@ export default {
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (editorView) {
|
||||
editorView.destroy();
|
||||
editorView = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createSlashPlugin() {
|
||||
return suggestionsPlugin({
|
||||
@@ -488,4 +499,9 @@ export default {
|
||||
max-height: 7.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ProseMirror .cw-embed-preview {
|
||||
max-width: 36rem;
|
||||
margin: 0.5rem 0 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -123,6 +123,13 @@ export function cleanSignature(signature) {
|
||||
}
|
||||
}
|
||||
|
||||
// Strip `\<newline>` hardbreak markers trailing `--` after a signature slice
|
||||
const stripDelimiterHardbreaks = body =>
|
||||
body.replace(/(--)\s*(?:\\\s*)+$/, '$1');
|
||||
|
||||
// Strip standalone blank-paragraph markers (`\` on their own lines).
|
||||
const stripTrailingBlankLine = body => body.replace(/\n(?:\s*\\\n)+$/, '');
|
||||
|
||||
/**
|
||||
* Adds the signature delimiter to the beginning of the signature.
|
||||
*
|
||||
@@ -227,13 +234,19 @@ export function removeSignature(body, signature, channelType) {
|
||||
// trimming will ensure any spaces or new lines before the signature are removed
|
||||
// This means we will have the delimiter at the end
|
||||
if (signatureIndex > -1) {
|
||||
newBody = newBody.substring(0, signatureIndex).trimEnd();
|
||||
newBody = stripDelimiterHardbreaks(
|
||||
newBody.substring(0, signatureIndex)
|
||||
).trimEnd();
|
||||
}
|
||||
|
||||
// Remove delimiter if it's at the end
|
||||
if (newBody.endsWith(SIGNATURE_DELIMITER)) {
|
||||
// if the delimiter is at the end, remove it
|
||||
newBody = newBody.slice(0, -SIGNATURE_DELIMITER.length);
|
||||
// strip any trailing blank-line markers
|
||||
if (signatureIndex > -1) {
|
||||
newBody = stripTrailingBlankLine(newBody);
|
||||
}
|
||||
}
|
||||
|
||||
return newBody;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import config from '../../../../config/markdown_embeds.yml';
|
||||
|
||||
// Gists rely on document.write() and can't render inline in the editor.
|
||||
const NON_PREVIEWABLE_EMBEDS = new Set(['github_gist']);
|
||||
|
||||
export const embeds = Object.entries(config)
|
||||
.filter(([key]) => !NON_PREVIEWABLE_EMBEDS.has(key))
|
||||
.map(([, { regex, template }]) => ({
|
||||
regex: new RegExp(regex),
|
||||
template,
|
||||
}));
|
||||
@@ -336,6 +336,38 @@ describe('removeSignature', () => {
|
||||
'This is a test\n\n'
|
||||
);
|
||||
});
|
||||
it('strips blank-paragraph marker before the delimiter', () => {
|
||||
expect(removeSignature('hey\n\n\\\n--\n\nHello there', 'Hello there')).toBe(
|
||||
'hey'
|
||||
);
|
||||
});
|
||||
it('strips multiple consecutive blank-paragraph markers before the delimiter', () => {
|
||||
expect(
|
||||
removeSignature('wewe\n\n\\\n\\\n\\\n--\n\nHello there', 'Hello there')
|
||||
).toBe('wewe');
|
||||
});
|
||||
it('strips dangling hardbreak when signature shared a paragraph with "--"', () => {
|
||||
expect(removeSignature('hey\n\n--\\\nHello there', 'Hello there')).toBe(
|
||||
'hey\n\n'
|
||||
);
|
||||
});
|
||||
it('preserves trailing backslash in user text when appending', () => {
|
||||
expect(appendSignature('The path is C:\\', 'Best\nAgent')).toContain(
|
||||
'C:\\'
|
||||
);
|
||||
expect(appendSignature('C:\\\n', 'Best\nAgent')).toContain('C:\\');
|
||||
expect(appendSignature('C:\\\n\n', 'Best\nAgent')).toContain('C:\\');
|
||||
});
|
||||
it('preserves trailing backslash in user text when removing', () => {
|
||||
expect(removeSignature('C:\\\n--\n\nBest\nAgent', 'Best\nAgent')).toContain(
|
||||
'C:\\'
|
||||
);
|
||||
expect(removeSignature('C:\\\n--', 'no matching sig')).toContain('C:\\');
|
||||
expect(removeSignature('C:\\\nBest\\\nAgent', 'Best\nAgent')).toContain(
|
||||
'C:\\'
|
||||
);
|
||||
expect(removeSignature('notes\n\\\n--', 'no matching sig')).toContain('\\');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSignature with stripped signature', () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { required, minLength } from '@vuelidate/validators';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { convertToAttributeSlug } from 'dashboard/helper/commons.js';
|
||||
import { normalizeRegexPattern } from 'shared/helpers/Validators';
|
||||
import { ATTRIBUTE_MODELS, ATTRIBUTE_TYPES } from './constants';
|
||||
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
@@ -99,19 +100,10 @@ export default {
|
||||
},
|
||||
|
||||
validations: {
|
||||
displayName: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
},
|
||||
description: {
|
||||
required,
|
||||
},
|
||||
attributeModel: {
|
||||
required,
|
||||
},
|
||||
attributeType: {
|
||||
required,
|
||||
},
|
||||
displayName: { required, minLength: minLength(1) },
|
||||
description: { required },
|
||||
attributeModel: { required },
|
||||
attributeType: { required },
|
||||
attributeKey: {
|
||||
required,
|
||||
isKey(value) {
|
||||
@@ -151,9 +143,7 @@ export default {
|
||||
attribute_display_type: this.attributeType,
|
||||
attribute_key: this.attributeKey,
|
||||
attribute_values: this.attributeListValues,
|
||||
regex_pattern: this.regexPattern
|
||||
? new RegExp(this.regexPattern).toString()
|
||||
: null,
|
||||
regex_pattern: normalizeRegexPattern(this.regexPattern),
|
||||
regex_cue: this.regexCue,
|
||||
});
|
||||
this.alertMessage = this.$t('ATTRIBUTES_MGMT.ADD.API.SUCCESS_MESSAGE');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { getRegexp } from 'shared/helpers/Validators';
|
||||
import { getRegexp, normalizeRegexPattern } from 'shared/helpers/Validators';
|
||||
import { ATTRIBUTE_TYPES } from './constants';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import TagInput from 'dashboard/components-next/taginput/TagInput.vue';
|
||||
@@ -41,16 +41,9 @@ export default {
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
displayName: {
|
||||
required,
|
||||
},
|
||||
attributeType: {
|
||||
required,
|
||||
},
|
||||
description: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
},
|
||||
displayName: { required },
|
||||
attributeType: { required },
|
||||
description: { required, minLength: minLength(1) },
|
||||
attributeKey: {
|
||||
required,
|
||||
isKey(value) {
|
||||
@@ -118,7 +111,7 @@ export default {
|
||||
},
|
||||
setFormValues() {
|
||||
const regexPattern = this.selectedAttribute.regex_pattern
|
||||
? getRegexp(this.selectedAttribute.regex_pattern).source
|
||||
? getRegexp(this.selectedAttribute.regex_pattern).toString()
|
||||
: null;
|
||||
this.displayName = this.selectedAttribute.attribute_display_name;
|
||||
this.description = this.selectedAttribute.attribute_description;
|
||||
@@ -144,9 +137,7 @@ export default {
|
||||
attribute_description: this.description,
|
||||
attribute_display_name: this.displayName,
|
||||
attribute_values: this.updatedAttributeListValues,
|
||||
regex_pattern: this.regexPattern
|
||||
? new RegExp(this.regexPattern).toString()
|
||||
: null,
|
||||
regex_pattern: normalizeRegexPattern(this.regexPattern),
|
||||
regex_cue: this.regexCue,
|
||||
});
|
||||
this.alertMessage = this.$t('ATTRIBUTES_MGMT.EDIT.API.SUCCESS_MESSAGE');
|
||||
|
||||
Reference in New Issue
Block a user