mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 13:07:55 +00:00 
			
		
		
		
	# Pull Request Template
## Description
**Issue**
This PR fixes template variables in messages (e.g., {{customer.name}})
that were being incorrectly converted to clickable links by the
`MessageFormatter's linkify` functionality. This caused formatting
issues and broken links.
**Solution**
Added a `linkify` parameter to `MessageFormatter` to optionally disable
link conversion
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
**Screenshots**
**Before**
<img width="1012" alt="image"
src="https://github.com/user-attachments/assets/70abb238-b4d9-439d-9e51-c7513cf482fb"
/>
**After**
<img width="1012" alt="image"
src="https://github.com/user-attachments/assets/387acb74-674e-4b26-85cc-2d7190d256b1"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] 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
- [x] 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
		
	
		
			
				
	
	
		
			104 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			104 lines
		
	
	
		
			2.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import mila from 'markdown-it-link-attributes';
 | 
						|
import mentionPlugin from './markdownIt/link';
 | 
						|
import MarkdownIt from 'markdown-it';
 | 
						|
 | 
						|
const setImageHeight = inlineToken => {
 | 
						|
  const imgSrc = inlineToken.attrGet('src');
 | 
						|
  if (!imgSrc) return;
 | 
						|
  const url = new URL(imgSrc);
 | 
						|
  const height = url.searchParams.get('cw_image_height');
 | 
						|
  if (!height) return;
 | 
						|
  inlineToken.attrSet('style', `height: ${height};`);
 | 
						|
};
 | 
						|
 | 
						|
const processInlineToken = blockToken => {
 | 
						|
  blockToken.children.forEach(inlineToken => {
 | 
						|
    if (inlineToken.type === 'image') {
 | 
						|
      setImageHeight(inlineToken);
 | 
						|
    }
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
const imgResizeManager = md => {
 | 
						|
  // Custom rule for image resize in markdown
 | 
						|
  // If the image url has a query param cw_image_height, then add a style attribute to the image
 | 
						|
  md.core.ruler.after('inline', 'add-image-height', state => {
 | 
						|
    state.tokens.forEach(blockToken => {
 | 
						|
      if (blockToken.type === 'inline') {
 | 
						|
        processInlineToken(blockToken);
 | 
						|
      }
 | 
						|
    });
 | 
						|
  });
 | 
						|
};
 | 
						|
 | 
						|
const createMarkdownInstance = (linkify = true) => {
 | 
						|
  return MarkdownIt({
 | 
						|
    html: false,
 | 
						|
    xhtmlOut: true,
 | 
						|
    breaks: true,
 | 
						|
    langPrefix: 'language-',
 | 
						|
    linkify,
 | 
						|
    typographer: true,
 | 
						|
    quotes: '\u201c\u201d\u2018\u2019',
 | 
						|
    maxNesting: 20,
 | 
						|
  })
 | 
						|
    .use(mentionPlugin)
 | 
						|
    .use(imgResizeManager)
 | 
						|
    .use(mila, {
 | 
						|
      attrs: {
 | 
						|
        class: 'link',
 | 
						|
        rel: 'noreferrer noopener nofollow',
 | 
						|
        target: '_blank',
 | 
						|
      },
 | 
						|
    });
 | 
						|
};
 | 
						|
 | 
						|
const TWITTER_USERNAME_REGEX = /(^|[^@\w])@(\w{1,15})\b/g;
 | 
						|
const TWITTER_USERNAME_REPLACEMENT = '$1[@$2](http://twitter.com/$2)';
 | 
						|
const TWITTER_HASH_REGEX = /(^|\s)#(\w+)/g;
 | 
						|
const TWITTER_HASH_REPLACEMENT = '$1[#$2](https://twitter.com/hashtag/$2)';
 | 
						|
 | 
						|
class MessageFormatter {
 | 
						|
  constructor(
 | 
						|
    message,
 | 
						|
    isATweet = false,
 | 
						|
    isAPrivateNote = false,
 | 
						|
    linkify = true
 | 
						|
  ) {
 | 
						|
    this.message = message || '';
 | 
						|
    this.isAPrivateNote = isAPrivateNote;
 | 
						|
    this.isATweet = isATweet;
 | 
						|
    this.linkify = linkify;
 | 
						|
    this.md = createMarkdownInstance(linkify);
 | 
						|
  }
 | 
						|
 | 
						|
  formatMessage() {
 | 
						|
    let updatedMessage = this.message;
 | 
						|
    if (this.isATweet && !this.isAPrivateNote) {
 | 
						|
      updatedMessage = updatedMessage.replace(
 | 
						|
        TWITTER_USERNAME_REGEX,
 | 
						|
        TWITTER_USERNAME_REPLACEMENT
 | 
						|
      );
 | 
						|
      updatedMessage = updatedMessage.replace(
 | 
						|
        TWITTER_HASH_REGEX,
 | 
						|
        TWITTER_HASH_REPLACEMENT
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return this.md.render(updatedMessage);
 | 
						|
  }
 | 
						|
 | 
						|
  get formattedMessage() {
 | 
						|
    return this.formatMessage();
 | 
						|
  }
 | 
						|
 | 
						|
  get plainText() {
 | 
						|
    const strippedOutHtml = new DOMParser().parseFromString(
 | 
						|
      this.formattedMessage,
 | 
						|
      'text/html'
 | 
						|
    );
 | 
						|
    return strippedOutHtml.body.textContent || '';
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export default MessageFormatter;
 |