@happeouikit/content-renderer 1.0.48 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,12 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.1
4
+
5
+ - [Fixed] ContentRenderer should now deal with all kinds of xss
6
+
7
+ ## 3.0.0
8
+
9
+ - [Fixed] the xss converter now accepts a whiteList for CSS properties that includes vertical-align
10
+ - [Updated] new dependencies updates: React 17 and and Styled Components 5
11
+
12
+ ## 2.0.1
13
+
14
+ - [Added] Changed type to uppercase and to handle both upper and lowercase. Add warning if lowecase is used.
15
+ - [Added] Handle widgetType=`page` by removing forced styles from component.
16
+
17
+ ## 2.0.0
18
+
19
+ - [Breaking change] Links rendered from markdown content now always open in a new tab (not configurable).
20
+
3
21
  ## 1.0.48
4
22
 
5
23
  - [Fixed] Decode HTML entites causing crashes
6
24
 
7
25
  ## 1.0.46
8
26
 
9
- - [Fixed] Use correct font-family for all the tags inside <p>, <li> etc.
27
+ - [Fixed] Use correct font-family for all the tags inside paragraphs and list elements etc.
10
28
 
11
29
  ## 1.0.45
12
30
 
@@ -59,7 +59,7 @@ function _templateObject() {
59
59
  * ContentRenderer
60
60
  *
61
61
  */
62
- import React, { useRef, useEffect, useState, useCallback } from "react";
62
+ import React, { useRef, useEffect, useState, useCallback, useMemo } from "react";
63
63
  import ReactDOM from "react-dom";
64
64
  import PropTypes from "prop-types";
65
65
  import styled, { css } from "styled-components";
@@ -68,8 +68,11 @@ import { CopyToClipboardButton } from "@happeouikit/copy-to-clipboard-button";
68
68
  import { BodyUI, sansFamily } from "@happeouikit/typography";
69
69
  import { margin200, media, padding200 } from "@happeouikit/layout";
70
70
  import { active, gray04, gray07, gray09, lighten, warn } from "@happeouikit/colors";
71
- import { toHtml, toSafeText, loadCodeFont, decodeHtmlEntities, removeSpecialCharacters } from "./utils";
71
+ import { toHtml, loadCodeFont, decodeHtmlEntities, removeSpecialCharacters, toSafeText } from "./utils";
72
72
  import { ELEMENT_TYPE_PRE, ELEMENT_TYPE_CODE, HIGHLIGHJS_CSS, HIGHLIGHJS_JS, CODE_BLOCK_AVAILABLE_LANGUAGES } from "./constants";
73
+ import { jsx as _jsx } from "react/jsx-runtime";
74
+ import { Fragment as _Fragment } from "react/jsx-runtime";
75
+ import { jsxs as _jsxs } from "react/jsx-runtime";
73
76
 
74
77
  var ContentRenderer = function ContentRenderer(_ref) {
75
78
  var content = _ref.content,
@@ -172,45 +175,47 @@ var ContentRenderer = function ContentRenderer(_ref) {
172
175
  scrollAndHighlightHash();
173
176
  }
174
177
  }, [element, createAnchors, useHashAutoscroll, scrollAndHighlightHash]);
175
- var transformedContent = toHtml(content, type);
178
+ var safeText = useMemo(function () {
179
+ var lowercaseType = type && typeof type === "string" ? type.toLowerCase() : "html";
180
+ var transformedContent = lowercaseType === "text" ? toSafeText("<p>".concat(content, "</p>")) : toHtml(content, lowercaseType);
176
181
 
177
- if (type === "text") {
178
- transformedContent = "<p>".concat(transformedContent, "</p>");
179
- }
180
-
181
- if (decodeHtmlEntitiesOnRender) {
182
- transformedContent = decodeHtmlEntities(transformedContent);
183
- }
182
+ if (decodeHtmlEntitiesOnRender) {
183
+ transformedContent = decodeHtmlEntities(transformedContent);
184
+ }
184
185
 
185
- var safeText = toSafeText(transformedContent); // Use dangerouslySetInnerHTML as there might be nested <p>'s header etc
186
+ return transformedContent;
187
+ }, [content, type, decodeHtmlEntitiesOnRender]); // Use dangerouslySetInnerHTML as there might be nested <p>'s header etc
186
188
  // This requires that all content is run through the toSafeText filter
187
189
  // before displaying. This will run it through xss filter.
188
190
 
189
- return React.createElement(React.Fragment, null, React.createElement(Wrapper, {
190
- className: "content-renderer",
191
- ref: element,
192
- widgetType: widgetType,
193
- headerFont: headerFont,
194
- bodyFont: bodyFont,
195
- createAnchors: createAnchors,
196
- dangerouslySetInnerHTML: {
197
- __html: safeText
198
- }
199
- }), copyBtns.map(function (el) {
200
- var clipboardContent = el.textContent || "";
201
-
202
- if (el.tagName !== ELEMENT_TYPE_PRE) {
203
- clipboardContent = "".concat(window.location.href.split("#")[0], "#").concat(el.id);
204
- } // eslint-disable-next-line no-control-regex
205
-
206
-
207
- clipboardContent = removeSpecialCharacters(clipboardContent);
208
- return ReactDOM.createPortal(React.createElement(BtnContainer, {
209
- className: "content-copy-btn"
210
- }, React.createElement(CopyToClipboardButton, {
211
- clipboardContent: clipboardContent
212
- })), el);
213
- }));
191
+ return /*#__PURE__*/_jsxs(_Fragment, {
192
+ children: [/*#__PURE__*/_jsx(Wrapper, {
193
+ className: "content-renderer",
194
+ ref: element,
195
+ widgetType: widgetType,
196
+ headerFont: headerFont,
197
+ bodyFont: bodyFont,
198
+ createAnchors: createAnchors,
199
+ dangerouslySetInnerHTML: {
200
+ __html: safeText
201
+ }
202
+ }), copyBtns.map(function (el) {
203
+ var clipboardContent = el.textContent || "";
204
+
205
+ if (el.tagName !== ELEMENT_TYPE_PRE) {
206
+ clipboardContent = "".concat(window.location.href.split("#")[0], "#").concat(el.id);
207
+ } // eslint-disable-next-line no-control-regex
208
+
209
+
210
+ clipboardContent = removeSpecialCharacters(clipboardContent);
211
+ return /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/_jsx(BtnContainer, {
212
+ className: "content-copy-btn",
213
+ children: /*#__PURE__*/_jsx(CopyToClipboardButton, {
214
+ clipboardContent: clipboardContent
215
+ })
216
+ }), el);
217
+ })]
218
+ });
214
219
  };
215
220
 
216
221
  var BtnContainer = styled.div.withConfig({
@@ -222,18 +227,30 @@ var Wrapper = styled(BodyUI).attrs({
222
227
  }).withConfig({
223
228
  displayName: "ContentRenderer__Wrapper",
224
229
  componentId: "sc-1mleu9k-1"
225
- })(["*{font-family:", " !important;}", " h1.highlight,h2.highlight,h3.highlight,h4.highlight,h5.highlight,h6.highlight{border-radius:6px;position:relative;:after{content:\" \";position:absolute;top:-8px;bottom:-8px;left:-8px;right:-8px;border-radius:6px;border:5px solid ", ";}}h1{font-weight:500;font-size:24px;letter-spacing:-0.5px;line-height:32px;+ h1,+ h2,+ h3,+ h4,+ p{margin-top:16px;}}h2{font-weight:500;font-size:20px;line-height:28px;+ h1,+ h2,+ h3,+ h4,+ p{margin-top:14px;}}h3{font-weight:500;font-size:16px;line-height:24px;+ h1,+ h2,+ h3,+ h4,+ p{margin-top:12px;}}h4{font-size:16px;line-height:24px;+ h1,+ h2,+ h3,+ h4,+ p{margin-top:12px;}}p{font-size:16px;line-height:24px;letter-spacing:0px;+ h1,+ h2,+ h3,+ h4,+ p{margin-top:12px;}}a{color:", ";text-decoration:none;:hover{text-decoration:underline;}}ul{list-style-type:disc;}ol{list-style-type:decimal;counter-reset:item;li{padding-left:12px;text-indent:-33px;list-style-type:none;counter-increment:item;&:before{display:inline-block;width:13px;padding-right:15px;padding-left:5px;font-weight:bold;text-align:right;content:counter(item) \".\";}}}ol,ul{font-size:16px;line-height:24px;margin:18px 16px;display:block;-webkit-margin-before:0.5em;-webkit-margin-after:0.5em;-webkit-padding-start:0.5em;li,li{margin-bottom:5px;padding-left:10px;> ul,> ol{-webkit-margin-before:8px !important;-webkit-margin-after:8px !important;}}&:first-child,&:first-child{-webkit-margin-before:0;}&:last-child,&:last-child{-webkit-margin-after:0;}}hr{-webkit-margin-before:16px;-webkit-margin-after:13px;border-color:@border-color;border-style:solid;height:0px;border-width:1px 0 0 0;}table{width:100%;margin:15px 0;text-align:left;thead th{background:@white;}th,td{border-width:1px;border-style:solid;border-color:@border-color;padding:0.5em;}tr:nth-child(even){background:@almost-white;}tr:nth-child(odd){background:@white;}}table.no-borders{th,td{border:none;}}table.padded-2{th,td{padding:2em;}}table.padded-1{th,td{padding:1em;}}img{max-width:100%;margin:15px 0;& + img{margin-top:0;}}.xl-emoji *{font-size:40px;line-height:1.2;}pre{border-radius:4px;background:", ";padding:8px;margin-bottom:10px;overflow:auto;white-space:pre;position:relative;overflow:visible;*:not(p){font-weight:inherit;font-family:\"IBM Plex Mono\",monospace !important;}.content-copy-btn{position:absolute;top:0;right:0;padding:", " 0;}:hover button{opacity:1;}}blockquote{border-top:2px solid ", ";border-bottom:2px solid ", ";font-size:18px;padding:24px 48px;position:relative;text-align:center;margin:48px 0;}.mention{border-radius:0.2em;background-color:#fff5cc;border:1px solid #fff5cc;color:#0f1621;font-weight:400;&:hover{text-decoration:none;border:1px solid #fabd24;}}.extraMention{cursor:inherit;}", ""], sansFamily, function (_ref2) {
230
+ })(["", " h1.highlight,h2.highlight,h3.highlight,h4.highlight,h5.highlight,h6.highlight{border-radius:6px;position:relative;:after{content:\" \";position:absolute;top:-8px;bottom:-8px;left:-8px;right:-8px;border-radius:6px;border:5px solid ", ";}}pre{border-radius:4px;background:", ";padding:8px;margin-bottom:10px;overflow:auto;white-space:pre;position:relative;overflow:visible;*:not(p){font-weight:inherit;font-family:\"IBM Plex Mono\",monospace !important;}.content-copy-btn{position:absolute;top:0;right:0;padding:", " 0;}:hover button{opacity:1;}}.mention{border-radius:0.2em;background-color:#fff5cc;border:1px solid #fff5cc;color:#0f1621;font-weight:400;&:hover{text-decoration:none;border:1px solid #fabd24;}}.extraMention{cursor:inherit;}", " ", ""], function (_ref2) {
226
231
  var createAnchors = _ref2.createAnchors;
227
232
  return createAnchors ? "\n h1, h2, h3, h4, h5, h6 {\n display: flex;\n align-items: center;\n :hover button {\n opacity: 0.4;\n }\n }\n " : "";
228
- }, lighten(warn, 0.8), active, gray09, padding200, gray07, gray07, function (_ref3) {
229
- var widgetType = _ref3.widgetType,
230
- bodyFont = _ref3.bodyFont,
231
- headerFont = _ref3.headerFont;
233
+ }, lighten(warn, 0.8), gray09, padding200, function (_ref3) {
234
+ var widgetType = _ref3.widgetType;
235
+ return widgetType === "page" ? "" : "\n * {\n font-family: ".concat(sansFamily, " !important;\n }\n\n \n\n h1 {\n font-weight: 500;\n font-size: 24px;\n letter-spacing: -0.5px;\n line-height: 32px;\n + h1,\n + h2,\n + h3,\n + h4,\n + p {\n margin-top: 16px;\n }\n }\n h2 {\n font-weight: 500;\n font-size: 20px;\n line-height: 28px;\n + h1,\n + h2,\n + h3,\n + h4,\n + p {\n margin-top: 14px;\n }\n }\n h3 {\n font-weight: 500;\n font-size: 16px;\n line-height: 24px;\n + h1,\n + h2,\n + h3,\n + h4,\n + p {\n margin-top: 12px;\n }\n }\n h4 {\n font-size: 16px;\n line-height: 24px;\n + h1,\n + h2,\n + h3,\n + h4,\n + p {\n margin-top: 12px;\n }\n }\n p {\n font-size: 16px;\n line-height: 24px;\n letter-spacing: 0px;\n + h1,\n + h2,\n + h3,\n + h4,\n + p {\n margin-top: 12px;\n }\n }\n a {\n color: ").concat(active, ";\n text-decoration: none;\n :hover {\n text-decoration: underline;\n }\n }\n ul {\n list-style-type: disc;\n }\n ol {\n list-style-type: decimal;\n counter-reset: item;\n li {\n padding-left: 12px;\n text-indent: -33px;\n list-style-type: none;\n counter-increment: item;\n &:before {\n display: inline-block;\n width: 13px;\n padding-right: 15px;\n padding-left: 5px;\n font-weight: bold;\n text-align: right;\n content: counter(item) \".\";\n }\n }\n }\n ol,\n ul {\n font-size: 16px;\n line-height: 24px;\n margin: 18px 16px;\n display: block;\n -webkit-margin-before: 0.5em;\n -webkit-margin-after: 0.5em;\n -webkit-padding-start: 0.5em;\n li,\n li {\n margin-bottom: 5px;\n padding-left: 10px;\n > ul,\n > ol {\n -webkit-margin-before: 8px !important;\n -webkit-margin-after: 8px !important;\n }\n }\n\n &:first-child,\n &:first-child {\n -webkit-margin-before: 0;\n }\n &:last-child,\n &:last-child {\n -webkit-margin-after: 0;\n }\n }\n hr {\n -webkit-margin-before: 16px;\n -webkit-margin-after: 13px;\n border-color: @border-color;\n border-style: solid;\n height: 0px;\n border-width: 1px 0 0 0;\n }\n table {\n width: 100%;\n margin: 15px 0;\n text-align: left;\n thead th {\n background: @white;\n }\n th,\n td {\n border-width: 1px;\n border-style: solid;\n border-color: @border-color;\n padding: 0.5em;\n }\n tr:nth-child(even) {\n background: @almost-white;\n }\n tr:nth-child(odd) {\n background: @white;\n }\n }\n table.no-borders {\n th,\n td {\n border: none;\n }\n }\n table.padded-2 {\n th,\n td {\n padding: 2em;\n }\n }\n table.padded-1 {\n th,\n td {\n padding: 1em;\n }\n }\n img {\n max-width: 100%;\n margin: 15px 0;\n & + img {\n margin-top: 0;\n }\n }\n .xl-emoji * {\n font-size: 40px;\n line-height: 1.2;\n }\n \n blockquote {\n border-top: 2px solid ").concat(gray07, ";\n border-bottom: 2px solid ").concat(gray07, ";\n font-size: 18px;\n padding: 24px 48px;\n position: relative;\n text-align: center;\n margin: 48px 0;\n }\n ");
236
+ }, function (_ref4) {
237
+ var widgetType = _ref4.widgetType,
238
+ bodyFont = _ref4.bodyFont,
239
+ headerFont = _ref4.headerFont;
232
240
  return widgetType === "article" && css(["h1,h2,h3{font-family:", " !important;& *{font-family:", " !important;}}h4,h5,h6,p,ul,ol,li,table{font-family:", " !important;& *{font-family:", " !important;}}h1{font-size:32px;line-height:1.13;margin:0 0 20px;letter-spacing:-0.75px;}h2{font-size:24px;line-height:1.17;margin:0 0 16px;letter-spacing:-0.5px;}h3{font-size:20px;line-height:1.2;margin:0 0 14px;}h4,h5,h6,p,ul,ol,li{font-size:18px;line-height:1.56;margin-bottom:13px;margin-top:0;}hr{border-color:", ";}table{width:100% !important;margin:15px 0;font-size:18px !important;text-align:left;border-collapse:collapse;border-spacing:0;thead th{background:@white;}th,td{border-width:1px;border-style:solid;border-color:", ";padding:0.5em;word-wrap:break-word;}tr:nth-child(odd){background:", ";}tr:nth-child(even){background:@white;}}table.no-borders{th,td{border:none;}tr:nth-child(odd){background:", ";}tr:nth-child(even){background:@white;}}pre{position:relative;background-color:", ";padding:1em;margin:1em 0;border-radius:4px;white-space:pre;}img,span.fr-img-caption,span.fr-video{display:block;margin:30px 0;", ";&.article-align-left{margin:30px auto;display:block;max-width:100%;", "}&.article-align-center{margin:30px auto;display:block;}&.article-align-right{margin:30px auto;display:block;max-width:100%;", "}&.article-align-overflow{margin:30px auto;display:block;max-width:100%;", " img{width:100%;}}}img{&.image-border-radius{border-radius:6px;}&.image-box-shadow{box-shadow:0 4px 16px 0 #bacad5;}}span.fr-img-caption{&.image-border-radius img{border-radius:6px;}&.image-box-shadow img{box-shadow:0 4px 16px 0 #bacad5;}}p{img:first-child,span.fr-img-caption:first-child{&.article-align-left,&.article-align-right{", "}}}span.fr-img-caption{text-align:center;img{margin:0;}.fr-inner{font-weight:400;font-size:16px;margin:8px auto 0 auto;padding:0 8px;color:", ";display:block;max-width:671px;}}span.fr-video{position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden;border-radius:6px;&.article-align-overflow{padding-top:170px;}iframe{position:absolute;top:0;left:0;width:100% !important;height:100% !important;}}blockquote{border-left:0 !important;padding-left:0 !important;margin:0 !important;color:inherit !important;p{line-height:30px;font-size:26px;text-align:center;margin:0 auto;}}.content-attachments{width:100%;}"], headerFont, headerFont, bodyFont, bodyFont, gray07, gray07, gray09, gray09, gray09, media.only.xs(_templateObject()), media.min.md(_templateObject2()), media.min.md(_templateObject3()), media.min.md(_templateObject4()), media.min.md(_templateObject5()), gray04);
233
241
  });
234
242
  ContentRenderer.propTypes = {
235
243
  content: PropTypes.string,
236
- type: PropTypes.oneOf(["html", "markdown", "text"]),
244
+ type: function type(props, propName) {
245
+ if (propName === "type" && props[propName] !== props[propName].toUpperCase()) {
246
+ // eslint-disable-next-line no-console
247
+ console.warn("Please use uppercase type 'HTML', 'MARKDOWN' or 'TEXT'");
248
+ }
249
+
250
+ if (propName === "type" && !["HTML", "MARKDOWN", "TEXT"].includes(props[propName].toUpperCase())) {
251
+ throw new Error("Invalid type, use 'HTML', 'MARKDOWN' or 'TEXT'");
252
+ }
253
+ },
237
254
  widgetType: PropTypes.oneOf(["post", "article", "page"]),
238
255
  bodyFont: PropTypes.string,
239
256
  headerFont: PropTypes.string,
@@ -54,6 +54,7 @@ function getConverter() {
54
54
 
55
55
  converter.setOption("tables", true); // <table>
56
56
 
57
+ converter.setOption("openLinksInNewWindow", true);
57
58
  return converter;
58
59
  }
59
60
 
package/dist/utils.js CHANGED
@@ -1,3 +1,9 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+
3
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
4
+
5
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
6
+
1
7
  import linkifyHtml from "linkifyjs/html";
2
8
  import xss from "xss";
3
9
  import { decode as decodeHtmlEntities } from "html-entities";
@@ -22,6 +28,26 @@ var processMentions = function processMentions() {
22
28
  var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
23
29
  return str.replace(mentioningRegexGroupCaseInsensitive, processMatch).replace(mentioningHtmlRegexGroupCaseInsensitive, processMatch);
24
30
  };
31
+ /**
32
+ * @name toSafeText
33
+ * @description Returns filtered text with filterXSS library
34
+ * @author Antero Hanhirova
35
+ * @param {String} - Text to whitelist
36
+ * @returns {String}
37
+ */
38
+
39
+
40
+ var toSafeText = function toSafeText(text) {
41
+ return xss(text, {
42
+ whiteList: safeTagsAndAttributes,
43
+ css: {
44
+ whiteList: _objectSpread({}, xss.getDefaultCSSWhiteList(), {
45
+ "vertical-align": true
46
+ })
47
+ },
48
+ stripIgnoreTag: true
49
+ }).replace(/&nbsp;/g, " ").replace(/\u2060/g, ""); // Replaces a hidden character that resulted in a "?"-character in backend
50
+ };
25
51
  /**
26
52
  * @name toHtml
27
53
  * @description Converts markdown to html, adds space to end of String
@@ -44,26 +70,12 @@ var toHtml = function toHtml() {
44
70
  newString = processMentions(newString);
45
71
  }
46
72
 
73
+ newString = toSafeText(newString);
47
74
  return linkifyHtml(newString, {
48
75
  target: "_blank",
49
76
  ignoreTags: ["pre", "code", "script", "style"]
50
77
  });
51
78
  };
52
- /**
53
- * @name toSafeText
54
- * @description Returns filtered text with filterXSS library
55
- * @author Antero Hanhirova
56
- * @param {String} - Text to whitelist
57
- * @returns {String}
58
- */
59
-
60
-
61
- var toSafeText = function toSafeText(text) {
62
- return xss(text, {
63
- whiteList: safeTagsAndAttributes,
64
- stripIgnoreTag: true
65
- }).replace(/&nbsp;/g, " ").replace(/\u2060/g, ""); // Replaces a hidden character that resulted in a "?"-character in backend
66
- };
67
79
 
68
80
  var loadCodeFont = function loadCodeFont() {
69
81
  var id = "ibmMonoOnDemand";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happeouikit/content-renderer",
3
- "version": "1.0.48",
3
+ "version": "3.0.1",
4
4
  "description": "Rendering html and markdown content",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.js",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "peerDependencies": {
31
31
  "prop-types": "^15.6.2",
32
- "react": "^16.6.3",
33
- "styled-components": "^4.1.1"
32
+ "react": "^17.0.2",
33
+ "styled-components": "^5.3.1"
34
34
  },
35
35
  "publishConfig": {
36
36
  "access": "public"