├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── cat.png ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | - '5' 5 | cache: 6 | directories: 7 | - node_modules 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # rich-message change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | This project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [1.0.2](https://github.com/moose-team/rich-message/compare/v1.0.1...v1.0.2) 8 | 9 | * fix channel name parsing bug ([#8](https://github.com/moose-team/rich-message/issues/8)) 10 | * add tests ([#9](https://github.com/moose-team/rich-message/pull/9)) 11 | * simplify internal directory structure 12 | * move `cat.png` into `rich-message` module 13 | * update `.travis.yml` to target node 4 & 5, remove outdated optimizations 14 | * add usage example 15 | 16 | ## [1.0.1](https://github.com/moose-team/rich-message/compare/v1.0.0...v1.0.1) 17 | 18 | * fix channel link formatting ([#6](https://github.com/moose-team/rich-message/pull/6)) 19 | * use MOOSE Team MIT License ([#7](https://github.com/moose-team/rich-message/pull/7)) 20 | 21 | ## 1.0.0 22 | * initial release 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Contributions welcome! Please check past issues and pull requests before you open your own issue or pull request to avoid duplicating a frequently asked question. 4 | 5 | In addition to improving the project, refactoring code, and implementing features, this project welcomes the following types of contributions: 6 | 7 | - **Ideas**: participate in an issue thread or start your own to have your voice heard. 8 | - **Writing**: contribute your expertise in an area by helping expand the included content. 9 | - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content. 10 | - **Formatting**: help keep content easy to read with consistent formatting. 11 | 12 | ## Install 13 | 14 | Fork and clone the repo, then `npm install` to install all dependencies. 15 | 16 | ## Testing 17 | 18 | Tests are run with `npm test`. Please ensure all tests are passing before submitting a pull request (unless you're creating a failing test to increase test coverage or show a problem). 19 | 20 | ## Code Style 21 | 22 | [![standard][standard-image]][standard-url] 23 | 24 | This repository uses [`standard`][standard-url] to maintain code style and consistency and avoid style arguments. `npm test` runs `standard` so you don't have to! 25 | 26 | [standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg 27 | [standard-url]: https://github.com/feross/standard 28 | [semistandard-image]: https://cdn.rawgit.com/flet/semistandard/master/badge.svg 29 | [semistandard-url]: https://github.com/Flet/semistandard 30 | 31 | --- 32 | 33 | # Collaborating Guidelines 34 | 35 | **This is an OPEN Open Source Project.** 36 | 37 | ## What? 38 | 39 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 40 | 41 | ## Rules 42 | 43 | There are a few basic ground-rules for contributors: 44 | 45 | 1. **No `--force` pushes** or modifying the Git history in any way. 46 | 1. **Non-master branches** ought to be used for ongoing work. 47 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 48 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 49 | 1. Contributors should attempt to adhere to the prevailing code style. 50 | 51 | ## Releases 52 | 53 | Declaring formal releases remains the prerogative of the project maintainer. 54 | 55 | ## Changes to this arrangement 56 | 57 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 58 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # [MIT License](https://spdx.org/licenses/MIT) 2 | 3 | Copyright (c) 2015-2016 [MOOSE Team](http://moose-team.github.io) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rich-message 2 | 3 | > Turn a plain message into a rich HTML message 4 | 5 | [![npm][npm-image]][npm-url] 6 | [![travis][travis-image]][travis-url] 7 | 8 | [npm-image]: https://img.shields.io/npm/v/rich-message.svg?style=flat-square 9 | [npm-url]: https://www.npmjs.com/package/rich-message 10 | [travis-image]: https://img.shields.io/travis/moose-team/rich-message.svg?style=flat-square 11 | [travis-url]: https://travis-ci.org/moose-team/rich-message 12 | 13 | ## Install 14 | 15 | ``` 16 | npm install rich-message 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```js 22 | var richMessage = require('rich-message') 23 | var basicMessage = { 24 | text: 'hi maxogden', // text entered by a user 25 | username: 'mafintosh', // github username 26 | timestamp: Date.now() 27 | } 28 | var username = 'maxogden' // current user's github username (used for highlighting) 29 | 30 | var output = richMessage(basicMessage, username) 31 | // { text: 'hi maxogden', 32 | // username: 'mafintosh', 33 | // timestamp: 1458939703123, 34 | // anon: false, 35 | // avatar: 'https://github.com/mafintosh.png', 36 | // timeago: '2:01 PM', 37 | // html: '

hi maxogden

' } 38 | ``` 39 | 40 | This module is currently very tightly coupled with [friends](https://github.com/moose-team/friends) (sorry). If you'd like to help make it more usable for other projects, please fork and contribute! 41 | 42 | ## Contributing 43 | 44 | Contributions welcome! Please read the [contributing guidelines](CONTRIBUTING.md) first. 45 | 46 | ## License 47 | 48 | [MIT](LICENSE.md) 49 | -------------------------------------------------------------------------------- /cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moose-team/rich-message/870200384027b50e0e8b3a1bad1831e9f0287b2e/cat.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = makeRichMessage 2 | module.exports.mergeMessages = mergeMessages 3 | module.exports.timeago = timeago 4 | 5 | var emoji = require('markdown-it-emoji') 6 | var ghlink = require('ghlink') 7 | var higlight = require('highlight.js') 8 | var MarkdownIt = require('markdown-it') 9 | var twemoji = require('twemoji') 10 | var moment = require('moment') 11 | 12 | var CAT_URL = 'https://cdn.rawgit.com/moose-team/rich-message/master/cat.png' 13 | 14 | var md = new MarkdownIt({ 15 | linkify: true, 16 | highlight: function (str, lang) { 17 | if (lang && higlight.getLanguage(lang)) { 18 | try { 19 | return higlight.highlight(lang, str).value 20 | } catch (err) {} 21 | } 22 | 23 | try { 24 | return higlight.highlightAuto(str).value 25 | } catch (err) {} 26 | 27 | return '' // use external default escaping 28 | } 29 | }).use(emoji) 30 | 31 | md.renderer.rules.emoji = function (token, index) { 32 | return twemoji.parse(token[index].content) 33 | } 34 | 35 | function makeRichMessage (message, username) { 36 | message.anon = /Anonymous/i.test(message.username) 37 | message.avatar = message.anon 38 | ? CAT_URL 39 | : `https://github.com/${message.username}.png` 40 | message.timeago = timeago(message.timestamp) 41 | 42 | message.text = message.text.replace(/(^|\s)(#[a-zA-Z0-9]+)(?=$|\s)/g, '$1[$2]($2)') 43 | message.html = md.render(message.text) 44 | message.html = message.html.replace(/\n/g, '

') 45 | message.html = ghlink(message.html, { format: 'html' }) 46 | 47 | var highlight = (message.text.indexOf(username) !== -1) 48 | var classStr = highlight ? ' class="highlight"' : '' 49 | message.html = `${message.html}` 50 | 51 | return message 52 | } 53 | 54 | function mergeMessages (message1, message2) { 55 | message1.text += '\n' + message2.text 56 | message1.html += '

' + message2.html 57 | return message1 58 | } 59 | 60 | function timeago (milliseconds) { 61 | var timeago = moment(milliseconds).calendar() 62 | if (timeago.indexOf('Today at ') === 0) return timeago.substring(9) 63 | return timeago 64 | } 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rich-message", 3 | "description": "Turn a plain message into a rich HTML message.", 4 | "version": "1.0.2", 5 | "author": "moose-team", 6 | "bugs": { 7 | "url": "https://github.com/moose-team/rich-message/issues" 8 | }, 9 | "devDependencies": { 10 | "standard": "^6.0.8", 11 | "tap-spec": "^4.1.1", 12 | "tape": "^4.5.1" 13 | }, 14 | "homepage": "https://github.com/moose-team/rich-message", 15 | "keywords": [ 16 | "chat", 17 | "embed", 18 | "emoji", 19 | "friends", 20 | "html", 21 | "markdown", 22 | "message", 23 | "moose", 24 | "rich" 25 | ], 26 | "license": "ISC", 27 | "main": "index.js", 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/moose-team/rich-message.git" 31 | }, 32 | "scripts": { 33 | "test": "standard && tape test.js | tap-spec" 34 | }, 35 | "dependencies": { 36 | "ghlink": "^0.1.2", 37 | "highlight.js": "^8.5.0", 38 | "markdown-it": "^4.2.0", 39 | "markdown-it-emoji": "^1.0.0", 40 | "moment": "^2.10.2", 41 | "twemoji": "^1.4.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var richMessage = require('./') 3 | var mergeMessages = richMessage.mergeMessages 4 | var CAT_URL = 'https://cdn.rawgit.com/moose-team/rich-message/master/cat.png' 5 | 6 | test('usage example', function (t) { 7 | var message = { 8 | text: 'hi maxogden', // text entered by a user 9 | username: 'mafintosh', // user's github name 10 | timestamp: Date.now() 11 | } 12 | var username = 'maxogden' // current user's github name (used for highlighting) 13 | 14 | var output = richMessage(message, username) 15 | var expected = { 16 | text: 'hi maxogden', 17 | username: 'mafintosh', 18 | anon: false, 19 | avatar: 'https://github.com/mafintosh.png', 20 | timeago: richMessage.timeago(message.timestamp), 21 | html: '

hi maxogden

' 22 | } 23 | 24 | t.equal(output.html, expected.html, 'html is correctly formatted') 25 | t.false(output.anon, 'user is not anon') 26 | t.equal(output.timeago, expected.timeago, 'timeago works as expected') 27 | t.end() 28 | }) 29 | 30 | test('link replacement', function (t) { 31 | var message = { 32 | text: '#cats #cats #cats not#cat #cat!%$', 33 | username: 'cat', 34 | timestamp: Date.now() 35 | } 36 | 37 | var output = richMessage(message, 'other_cat') 38 | var expected = '

' + 39 | '#cats #cats ' + 40 | '#cats not#cat #cat!%$' + 41 | '

' 42 | 43 | t.equal(output.html, expected, 'links are replaced in output html') 44 | t.end() 45 | }) 46 | 47 | test('github avatars', function (t) { 48 | var message = { 49 | text: 'i like cats', 50 | username: 'cat', 51 | timestamp: Date.now() 52 | } 53 | 54 | var output = richMessage(message, 'other_cat') 55 | t.equal(output.avatar, 'https://github.com/cat.png') 56 | t.end() 57 | }) 58 | 59 | test('anon avatars', function (t) { 60 | var message = { 61 | text: 'i like cats', 62 | username: 'Anonymous cat', 63 | timestamp: Date.now() 64 | } 65 | 66 | var output = richMessage(message, 'other_cat') 67 | t.equal(output.avatar, CAT_URL) 68 | t.end() 69 | }) 70 | 71 | test('github links', function (t) { 72 | var message = { 73 | text: 'cats isaacs/npm#1234', 74 | username: 'cat', 75 | timestamp: Date.now() 76 | } 77 | 78 | var output = richMessage(message, 'other_cat') 79 | var expected = '

' + 80 | 'cats ' + 81 | 'isaacs/npm#1234' + 82 | '

' 83 | 84 | t.equal(output.html, expected) 85 | t.end() 86 | }) 87 | 88 | test('newlines -> paragraphs', function (t) { 89 | var message = { 90 | text: 'cat\ncat', 91 | username: 'cat', 92 | timestamp: Date.now() 93 | } 94 | 95 | var output = richMessage(message, 'other_cat') 96 | var expected = '

' + 97 | 'cat

cat' + 98 | '

' 99 | 100 | t.equal(output.html, expected) 101 | t.end() 102 | }) 103 | 104 | test('username highlight', function (t) { 105 | var message = { 106 | text: 'cat', 107 | username: 'cat', 108 | timestamp: Date.now() 109 | } 110 | 111 | var output = richMessage(message, 'cat') 112 | var expected = '

' + 113 | 'cat' + 114 | '

' 115 | 116 | t.equal(output.html, expected) 117 | t.end() 118 | }) 119 | 120 | // bug: moment does not calculate time reliably in different environments 121 | // on travis, 0 returns 1970, on mac os x, 1969 122 | test.skip('timeago', function (t) { 123 | var message = { 124 | text: 'cat', 125 | username: 'cat', 126 | timestamp: 0 127 | } 128 | 129 | var output = richMessage(message, 'cat') 130 | t.equal(output.timeago, '01/01/1970') 131 | t.end() 132 | }) 133 | 134 | test('markdown render', function (t) { 135 | var message = { 136 | text: '`cat` **cat**', 137 | username: 'cat', 138 | timestamp: Date.now() 139 | } 140 | 141 | var output = richMessage(message, 'other_cat') 142 | var expected = '

' + 143 | 'cat cat' + 144 | '

' 145 | 146 | t.equal(output.html, expected) 147 | t.end() 148 | }) 149 | 150 | test('merge messages', function (t) { 151 | var message1 = { 152 | text: 'cat1', 153 | html: '

cat1

' 154 | } 155 | 156 | var message2 = { 157 | text: 'cat2', 158 | html: '

cat2

' 159 | } 160 | 161 | var output = mergeMessages(message1, message2) 162 | var expected = { 163 | text: 'cat1\ncat2', 164 | html: '

cat1

cat2

' 165 | } 166 | 167 | t.deepEqual(output, expected) 168 | t.end() 169 | }) 170 | --------------------------------------------------------------------------------