Render and sanitize chat messages server-side. (#237)
* Render and sanitize chat messages server-side. Closes #235 * Render content.md server-side and return it in the client config * Remove showdown from web project * Update api spec * Move example user content file
This commit is contained in:
parent
9eab6d7553
commit
d7c3991b59
192
build/javascript/package-lock.json
generated
192
build/javascript/package-lock.json
generated
@ -613,7 +613,8 @@
|
|||||||
"camelcase": {
|
"camelcase": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"camelcase-css": {
|
"camelcase-css": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@ -702,31 +703,6 @@
|
|||||||
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cliui": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
|
|
||||||
"requires": {
|
|
||||||
"string-width": "^3.1.0",
|
|
||||||
"strip-ansi": "^5.2.0",
|
|
||||||
"wrap-ansi": "^5.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "^4.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"clone-response": {
|
"clone-response": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
|
||||||
@ -872,7 +848,8 @@
|
|||||||
"decamelize": {
|
"decamelize": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"decompress-response": {
|
"decompress-response": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
@ -969,11 +946,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.576.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.576.tgz",
|
||||||
"integrity": "sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew=="
|
"integrity": "sha512-uSEI0XZ//5ic+0NdOqlxp0liCD44ck20OAGyLMSymIWTEAtHKVJi6JM18acOnRgUgX7Q65QqnI+sNncNvIy8ew=="
|
||||||
},
|
},
|
||||||
"emoji-regex": {
|
|
||||||
"version": "7.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
|
|
||||||
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
|
|
||||||
},
|
|
||||||
"end-of-stream": {
|
"end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
@ -1142,14 +1114,6 @@
|
|||||||
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
|
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"find-up": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
|
||||||
"requires": {
|
|
||||||
"locate-path": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"focus-trap": {
|
"focus-trap": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-5.1.0.tgz",
|
||||||
@ -1211,11 +1175,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz",
|
||||||
"integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ=="
|
"integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ=="
|
||||||
},
|
},
|
||||||
"get-caller-file": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
|
||||||
},
|
|
||||||
"get-stream": {
|
"get-stream": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||||
@ -1459,11 +1418,6 @@
|
|||||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"is-fullwidth-code-point": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
|
||||||
},
|
|
||||||
"is-function": {
|
"is-function": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||||
@ -1570,15 +1524,6 @@
|
|||||||
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
|
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"locate-path": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
|
||||||
"requires": {
|
|
||||||
"p-locate": "^3.0.0",
|
|
||||||
"path-exists": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.20",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
@ -1913,18 +1858,11 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-try": "^2.0.0"
|
"p-try": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-locate": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
|
||||||
"requires": {
|
|
||||||
"p-limit": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-map": {
|
"p-map": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
|
||||||
@ -1956,7 +1894,8 @@
|
|||||||
"p-try": {
|
"p-try": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"parent-module": {
|
"parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@ -1979,11 +1918,6 @@
|
|||||||
"lines-and-columns": "^1.1.6"
|
"lines-and-columns": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path-exists": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
|
|
||||||
},
|
|
||||||
"path-is-absolute": {
|
"path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
@ -2360,16 +2294,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||||
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
|
||||||
},
|
},
|
||||||
"require-directory": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
|
||||||
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
|
|
||||||
},
|
|
||||||
"require-main-filename": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
|
|
||||||
},
|
|
||||||
"requires-port": {
|
"requires-port": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
@ -2474,48 +2398,6 @@
|
|||||||
"rust-result": "^1.0.0"
|
"rust-result": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"set-blocking": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
|
|
||||||
},
|
|
||||||
"showdown": {
|
|
||||||
"version": "1.9.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz",
|
|
||||||
"integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==",
|
|
||||||
"requires": {
|
|
||||||
"yargs": "^14.2"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"yargs": {
|
|
||||||
"version": "14.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
|
|
||||||
"integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
|
|
||||||
"requires": {
|
|
||||||
"cliui": "^5.0.0",
|
|
||||||
"decamelize": "^1.2.0",
|
|
||||||
"find-up": "^3.0.0",
|
|
||||||
"get-caller-file": "^2.0.1",
|
|
||||||
"require-directory": "^2.1.1",
|
|
||||||
"require-main-filename": "^2.0.0",
|
|
||||||
"set-blocking": "^2.0.0",
|
|
||||||
"string-width": "^3.0.0",
|
|
||||||
"which-module": "^2.0.0",
|
|
||||||
"y18n": "^4.0.0",
|
|
||||||
"yargs-parser": "^15.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yargs-parser": {
|
|
||||||
"version": "15.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
|
|
||||||
"integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
|
|
||||||
"requires": {
|
|
||||||
"camelcase": "^5.0.0",
|
|
||||||
"decamelize": "^1.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||||
@ -2758,31 +2640,6 @@
|
|||||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"string-width": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
|
|
||||||
"requires": {
|
|
||||||
"emoji-regex": "^7.0.1",
|
|
||||||
"is-fullwidth-code-point": "^2.0.0",
|
|
||||||
"strip-ansi": "^5.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "^4.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
@ -3159,36 +3016,6 @@
|
|||||||
"global": "^4.3.1"
|
"global": "^4.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"which-module": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
|
|
||||||
},
|
|
||||||
"wrap-ansi": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-styles": "^3.2.0",
|
|
||||||
"string-width": "^3.0.0",
|
|
||||||
"strip-ansi": "^5.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
|
|
||||||
},
|
|
||||||
"strip-ansi": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
|
||||||
"requires": {
|
|
||||||
"ansi-regex": "^4.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
@ -3210,11 +3037,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||||
},
|
},
|
||||||
"y18n": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
|
|
||||||
},
|
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
"@videojs/themes": "^1.0.0",
|
"@videojs/themes": "^1.0.0",
|
||||||
"htm": "^3.0.4",
|
"htm": "^3.0.4",
|
||||||
"preact": "^10.5.3",
|
"preact": "^10.5.3",
|
||||||
"showdown": "^1.9.1",
|
|
||||||
"tailwindcss": "^1.8.10",
|
"tailwindcss": "^1.8.10",
|
||||||
"video.js": "^7.9.6"
|
"video.js": "^7.9.6"
|
||||||
},
|
},
|
||||||
@ -27,7 +26,6 @@
|
|||||||
"@justinribeiro/lite-youtube",
|
"@justinribeiro/lite-youtube",
|
||||||
"htm",
|
"htm",
|
||||||
"preact",
|
"preact",
|
||||||
"showdown",
|
|
||||||
"tailwindcss/dist/tailwind.min.css"
|
"tailwindcss/dist/tailwind.min.css"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -28,15 +28,15 @@ type config struct {
|
|||||||
|
|
||||||
// InstanceDetails defines the user-visible information about this particular instance.
|
// InstanceDetails defines the user-visible information about this particular instance.
|
||||||
type InstanceDetails struct {
|
type InstanceDetails struct {
|
||||||
Name string `yaml:"name" json:"name"`
|
Name string `yaml:"name" json:"name"`
|
||||||
Title string `yaml:"title" json:"title"`
|
Title string `yaml:"title" json:"title"`
|
||||||
Summary string `yaml:"summary" json:"summary"`
|
Summary string `yaml:"summary" json:"summary"`
|
||||||
Logo logo `yaml:"logo" json:"logo"`
|
Logo logo `yaml:"logo" json:"logo"`
|
||||||
Tags []string `yaml:"tags" json:"tags"`
|
Tags []string `yaml:"tags" json:"tags"`
|
||||||
SocialHandles []socialHandle `yaml:"socialHandles" json:"socialHandles"`
|
SocialHandles []socialHandle `yaml:"socialHandles" json:"socialHandles"`
|
||||||
ExtraInfoFile string `yaml:"extraUserInfoFileName" json:"extraUserInfoFileName"`
|
Version string `json:"version"`
|
||||||
Version string `json:"version"`
|
NSFW bool `yaml:"nsfw" json:"nsfw"`
|
||||||
NSFW bool `yaml:"nsfw" json:"nsfw"`
|
ExtraUserContent string `json:"extraUserContent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type logo struct {
|
type logo struct {
|
||||||
@ -118,6 +118,13 @@ func (c *config) load(filePath string) error {
|
|||||||
|
|
||||||
c.VideoSettings.HighestQualityStreamIndex = findHighestQuality(c.VideoSettings.StreamQualities)
|
c.VideoSettings.HighestQualityStreamIndex = findHighestQuality(c.VideoSettings.StreamQualities)
|
||||||
|
|
||||||
|
// Add custom page content to the instance details.
|
||||||
|
customContentMarkdownData, err := ioutil.ReadFile(ExtraInfoFile)
|
||||||
|
if err == nil {
|
||||||
|
customContentMarkdownString := string(customContentMarkdownData)
|
||||||
|
c.InstanceDetails.ExtraUserContent = utils.RenderSimpleMarkdown(customContentMarkdownString)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,14 +231,5 @@ func Load(filePath string, versionInfo string) error {
|
|||||||
|
|
||||||
Config.VersionInfo = versionInfo
|
Config.VersionInfo = versionInfo
|
||||||
|
|
||||||
// Defaults
|
|
||||||
|
|
||||||
// This is relative to the webroot, not the project root.
|
|
||||||
// Has to be set here instead of pulled from a getter
|
|
||||||
// since it's serialized to JSON.
|
|
||||||
if Config.InstanceDetails.ExtraInfoFile == "" {
|
|
||||||
Config.InstanceDetails.ExtraInfoFile = _default.InstanceDetails.ExtraInfoFile
|
|
||||||
}
|
|
||||||
|
|
||||||
return Config.verifySettings()
|
return Config.verifySettings()
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ const (
|
|||||||
WebRoot = "webroot"
|
WebRoot = "webroot"
|
||||||
PrivateHLSStoragePath = "hls"
|
PrivateHLSStoragePath = "hls"
|
||||||
GeoIPDatabasePath = "data/GeoLite2-City.mmdb"
|
GeoIPDatabasePath = "data/GeoLite2-City.mmdb"
|
||||||
|
ExtraInfoFile = "data/content.md"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -13,7 +13,6 @@ func getDefaults() config {
|
|||||||
defaults.VideoSettings.ChunkLengthInSeconds = 4
|
defaults.VideoSettings.ChunkLengthInSeconds = 4
|
||||||
defaults.Files.MaxNumberInPlaylist = 5
|
defaults.Files.MaxNumberInPlaylist = 5
|
||||||
defaults.VideoSettings.OfflineContent = "static/offline.m4v"
|
defaults.VideoSettings.OfflineContent = "static/offline.m4v"
|
||||||
defaults.InstanceDetails.ExtraInfoFile = "/static/content.md"
|
|
||||||
defaults.YP.Enabled = false
|
defaults.YP.Enabled = false
|
||||||
defaults.YP.YPServiceURL = "https://yp.owncast.online"
|
defaults.YP.YPServiceURL = "https://yp.owncast.online"
|
||||||
|
|
||||||
|
33
core/chat/messageRendering_test.go
Normal file
33
core/chat/messageRendering_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package chat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/owncast/owncast/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test a bunch of arbitrary markup and markdown to make sure we get sanitized
|
||||||
|
// and fully rendered HTML out of it.
|
||||||
|
func TestRenderAndSanitize(t *testing.T) {
|
||||||
|
messageContent := `
|
||||||
|
Test one two three! I go to http://yahoo.com and search for _sports_ and **answers**.
|
||||||
|
Here is an iframe <iframe src="http://yahoo.com"></iframe>
|
||||||
|
|
||||||
|
## blah blah blah
|
||||||
|
[test link](http://owncast.online)
|
||||||
|
<img class="emoji" alt="bananadance.gif" width="600px" src="https://goth.land/img/emoji/bananadance.gif">
|
||||||
|
<script src="http://hackers.org/hack.js"></script>
|
||||||
|
`
|
||||||
|
|
||||||
|
expected := `<p>Test one two three! I go to <a href="http://yahoo.com" rel="nofollow noreferrer noopener" target="_blank">http://yahoo.com</a> and search for <em>sports</em> and <strong>answers</strong>.
|
||||||
|
Here is an iframe </p>
|
||||||
|
blah blah blah
|
||||||
|
<p><a href="http://owncast.online" rel="nofollow noreferrer noopener" target="_blank">test link</a>
|
||||||
|
<img class="emoji" alt="bananadance.gif" src="https://goth.land/img/emoji/bananadance.gif"></p>`
|
||||||
|
|
||||||
|
result := models.RenderAndSanitize(messageContent)
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("message rendering/sanitation does not match expected. Got\n%s, \n\n want:\n%s", result, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -110,10 +110,17 @@ func (s *server) Listen() {
|
|||||||
delete(s.Clients, c.socketID)
|
delete(s.Clients, c.socketID)
|
||||||
s.listener.ClientRemoved(c.ClientID)
|
s.listener.ClientRemoved(c.ClientID)
|
||||||
|
|
||||||
// broadcast a message to all clients
|
// message was recieved from a client and should be sanitized, validated
|
||||||
|
// and distributed to other clients.
|
||||||
case msg := <-s.sendAllCh:
|
case msg := <-s.sendAllCh:
|
||||||
|
// Will turn markdown into html, sanitize user-supplied raw html
|
||||||
|
// and standardize this message into something safe we can send everyone else.
|
||||||
|
msg.RenderAndSanitizeMessageBody()
|
||||||
|
|
||||||
s.listener.MessageSent(msg)
|
s.listener.MessageSent(msg)
|
||||||
s.sendAll(msg)
|
s.sendAll(msg)
|
||||||
|
|
||||||
|
// Store in the message history
|
||||||
addMessage(msg)
|
addMessage(msg)
|
||||||
case ping := <-s.pingCh:
|
case ping := <-s.pingCh:
|
||||||
fmt.Println("PING?", ping)
|
fmt.Println("PING?", ping)
|
||||||
|
4
go.mod
4
go.mod
@ -8,7 +8,9 @@ require (
|
|||||||
github.com/aws/aws-sdk-go v1.34.0
|
github.com/aws/aws-sdk-go v1.34.0
|
||||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.0
|
github.com/mattn/go-sqlite3 v1.14.0
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.4
|
||||||
github.com/mssola/user_agent v0.5.2
|
github.com/mssola/user_agent v0.5.2
|
||||||
|
github.com/mvdan/xurls v1.1.0 // indirect
|
||||||
github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88
|
github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/oschwald/geoip2-golang v1.4.0
|
github.com/oschwald/geoip2-golang v1.4.0
|
||||||
@ -16,9 +18,11 @@ require (
|
|||||||
github.com/shirou/gopsutil v2.20.7+incompatible
|
github.com/shirou/gopsutil v2.20.7+incompatible
|
||||||
github.com/sirupsen/logrus v1.6.0
|
github.com/sirupsen/logrus v1.6.0
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf
|
||||||
|
github.com/yuin/goldmark v1.2.1
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||||
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 // indirect
|
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 // indirect
|
||||||
golang.org/x/text v0.3.3 // indirect
|
golang.org/x/text v0.3.3 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
|
mvdan.cc/xurls v1.1.0
|
||||||
)
|
)
|
||||||
|
15
go.sum
15
go.sum
@ -6,12 +6,18 @@ github.com/amalfra/etag v0.0.0-20190921100247-cafc8de96bc5/go.mod h1:Qk51jPgvIaO
|
|||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo=
|
github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo=
|
||||||
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
|
||||||
|
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||||
|
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||||
@ -21,8 +27,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
|
||||||
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo=
|
github.com/mssola/user_agent v0.5.2 h1:CZkTUahjL1+OcZ5zv3kZr8QiJ8jy2H08vZIEkBeRbxo=
|
||||||
github.com/mssola/user_agent v0.5.2/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
github.com/mssola/user_agent v0.5.2/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||||
|
github.com/mvdan/xurls v1.1.0 h1:OpuDelGQ1R1ueQ6sSryzi6P+1RtBpfQHM8fJwlE45ww=
|
||||||
|
github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
|
||||||
github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88 h1:CXq5QLPMcfGEZMx8uBMyLdDiUNV72vlkSiyqg+jf7AI=
|
github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88 h1:CXq5QLPMcfGEZMx8uBMyLdDiUNV72vlkSiyqg+jf7AI=
|
||||||
github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88/go.mod h1:XmAOs6UJXpNXRwKk+KY/nv5kL6xXYXyellk+A1pTlko=
|
github.com/nareix/joy5 v0.0.0-20200712071056-a55089207c88/go.mod h1:XmAOs6UJXpNXRwKk+KY/nv5kL6xXYXyellk+A1pTlko=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
@ -50,9 +60,12 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
|||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
|
||||||
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
|
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
|
||||||
|
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -78,3 +91,5 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc=
|
||||||
|
mvdan.cc/xurls v1.1.0/go.mod h1:TNWuhvo+IqbUCmtUIb/3LJSQdrzel8loVpgFm0HikbI=
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"mvdan.cc/xurls"
|
||||||
|
)
|
||||||
|
|
||||||
//ChatMessage represents a single chat message
|
//ChatMessage represents a single chat message
|
||||||
type ChatMessage struct {
|
type ChatMessage struct {
|
||||||
@ -16,6 +26,91 @@ type ChatMessage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Valid checks to ensure the message is valid
|
//Valid checks to ensure the message is valid
|
||||||
func (s ChatMessage) Valid() bool {
|
func (m ChatMessage) Valid() bool {
|
||||||
return s.Author != "" && s.Body != "" && s.ID != ""
|
return m.Author != "" && m.Body != "" && m.ID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderAndSanitizeMessageBody will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
|
||||||
|
// the message into something safe and renderable for clients.
|
||||||
|
func (m *ChatMessage) RenderAndSanitizeMessageBody() {
|
||||||
|
raw := m.Body
|
||||||
|
|
||||||
|
// Set the new, sanitized and rendered message body
|
||||||
|
m.Body = RenderAndSanitize(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderAndSanitize will turn markdown into HTML, sanitize raw user-supplied HTML and standardize
|
||||||
|
// the message into something safe and renderable for clients.
|
||||||
|
func RenderAndSanitize(raw string) string {
|
||||||
|
rendered := renderMarkdown(raw)
|
||||||
|
safe := sanitize(rendered)
|
||||||
|
|
||||||
|
// Set the new, sanitized and rendered message body
|
||||||
|
return strings.TrimSpace(safe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderMarkdown(raw string) string {
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.NewLinkify(
|
||||||
|
extension.WithLinkifyAllowedProtocols([][]byte{
|
||||||
|
[]byte("http:"),
|
||||||
|
[]byte("https:"),
|
||||||
|
}),
|
||||||
|
extension.WithLinkifyURLRegexp(
|
||||||
|
xurls.Strict,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
trimmed := strings.TrimSpace(raw)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := markdown.Convert([]byte(trimmed), &buf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitize(raw string) string {
|
||||||
|
p := bluemonday.StrictPolicy()
|
||||||
|
|
||||||
|
// Require URLs to be parseable by net/url.Parse
|
||||||
|
p.AllowStandardURLs()
|
||||||
|
|
||||||
|
// Allow links
|
||||||
|
p.AllowAttrs("href").OnElements("a")
|
||||||
|
|
||||||
|
// Force all URLs to have "noreferrer" in their rel attribute.
|
||||||
|
p.RequireNoReferrerOnLinks(true)
|
||||||
|
|
||||||
|
// Links will get target="_blank" added to them.
|
||||||
|
p.AddTargetBlankToFullyQualifiedLinks(true)
|
||||||
|
|
||||||
|
// Allow paragraphs
|
||||||
|
p.AllowElements("br")
|
||||||
|
p.AllowElements("p")
|
||||||
|
|
||||||
|
// Allow img tags
|
||||||
|
p.AllowElements("img")
|
||||||
|
p.AllowAttrs("src").OnElements("img")
|
||||||
|
p.AllowAttrs("alt").OnElements("img")
|
||||||
|
p.AllowAttrs("title").OnElements("img")
|
||||||
|
|
||||||
|
// Custom emoji have a class already specified.
|
||||||
|
// We should only allow classes on emoji, not *all* imgs.
|
||||||
|
// But TODO.
|
||||||
|
p.AllowAttrs("class").OnElements("img")
|
||||||
|
|
||||||
|
// Allow bold
|
||||||
|
p.AllowElements("strong")
|
||||||
|
|
||||||
|
// Allow emphasis
|
||||||
|
p.AllowElements("em")
|
||||||
|
|
||||||
|
return p.Sanitize(raw)
|
||||||
}
|
}
|
||||||
|
@ -112,9 +112,10 @@ components:
|
|||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
example: http://github.com/owncast/owncast
|
example: http://github.com/owncast/owncast
|
||||||
extraUserInfoFileName:
|
extraUserContent:
|
||||||
type: string
|
type: string
|
||||||
description: Path to markdown file that has additional rich content about the server.
|
description: Additional HTML content to render in the body of the web interface.
|
||||||
|
example: "<p>This page is <strong>super</strong> cool!"
|
||||||
version:
|
version:
|
||||||
type: string
|
type: string
|
||||||
example: Owncast v0.0.2-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)
|
example: Owncast v0.0.2-macOS (ef3796a033b32a312ebf5b334851cbf9959e7ecb)
|
||||||
@ -236,7 +237,7 @@ paths:
|
|||||||
/api/config:
|
/api/config:
|
||||||
get:
|
get:
|
||||||
summary: Information
|
summary: Information
|
||||||
description: Get the public information about the server. Adds context to the server, as well as information useful for the user interface.
|
description: The client configuration. Information useful for the user interface.
|
||||||
tags: ["Server"]
|
tags: ["Server"]
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mssola/user_agent"
|
"github.com/mssola/user_agent"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"mvdan.cc/xurls"
|
||||||
)
|
)
|
||||||
|
|
||||||
//GetTemporaryPipePath gets the temporary path for the streampipe.flv file
|
//GetTemporaryPipePath gets the temporary path for the streampipe.flv file
|
||||||
@ -65,3 +70,30 @@ func IsUserAgentABot(userAgent string) bool {
|
|||||||
ua := user_agent.New(userAgent)
|
ua := user_agent.New(userAgent)
|
||||||
return ua.Bot()
|
return ua.Bot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RenderSimpleMarkdown(raw string) string {
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.NewLinkify(
|
||||||
|
extension.WithLinkifyAllowedProtocols([][]byte{
|
||||||
|
[]byte("http:"),
|
||||||
|
[]byte("https:"),
|
||||||
|
}),
|
||||||
|
extension.WithLinkifyURLRegexp(
|
||||||
|
xurls.Strict,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
trimmed := strings.TrimSpace(raw)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := markdown.Convert([]byte(trimmed), &buf); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { h, Component } from '/js/web_modules/preact.js';
|
import { h, Component } from '/js/web_modules/preact.js';
|
||||||
import htm from '/js/web_modules/htm.js';
|
import htm from '/js/web_modules/htm.js';
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
import showdown from '/js/web_modules/showdown.js';
|
|
||||||
|
|
||||||
import { OwncastPlayer } from './components/player.js';
|
import { OwncastPlayer } from './components/player.js';
|
||||||
import SocialIconsList from './components/social-icons-list.js';
|
import SocialIconsList from './components/social-icons-list.js';
|
||||||
@ -97,7 +96,6 @@ export default class App extends Component {
|
|||||||
// fetch events
|
// fetch events
|
||||||
this.getConfig = this.getConfig.bind(this);
|
this.getConfig = this.getConfig.bind(this);
|
||||||
this.getStreamStatus = this.getStreamStatus.bind(this);
|
this.getStreamStatus = this.getStreamStatus.bind(this);
|
||||||
this.getExtraUserContent = this.getExtraUserContent.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -164,30 +162,9 @@ export default class App extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch content.md
|
|
||||||
getExtraUserContent(path) {
|
|
||||||
fetch(path)
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Network response was not ok ${response.ok}`);
|
|
||||||
}
|
|
||||||
return response.text();
|
|
||||||
})
|
|
||||||
.then((text) => {
|
|
||||||
this.setState({
|
|
||||||
extraUserContent: new showdown.Converter().makeHtml(text),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfigData(data = {}) {
|
setConfigData(data = {}) {
|
||||||
const { title, extraUserInfoFileName, summary } = data;
|
const { title, summary } = data;
|
||||||
|
|
||||||
window.document.title = title;
|
window.document.title = title;
|
||||||
if (extraUserInfoFileName) {
|
|
||||||
this.getExtraUserContent(extraUserInfoFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
configData: {
|
configData: {
|
||||||
@ -349,7 +326,6 @@ export default class App extends Component {
|
|||||||
chatInputEnabled,
|
chatInputEnabled,
|
||||||
configData,
|
configData,
|
||||||
displayChat,
|
displayChat,
|
||||||
extraUserContent,
|
|
||||||
orientation,
|
orientation,
|
||||||
playerActive,
|
playerActive,
|
||||||
streamOnline,
|
streamOnline,
|
||||||
@ -371,6 +347,7 @@ export default class App extends Component {
|
|||||||
summary,
|
summary,
|
||||||
tags = [],
|
tags = [],
|
||||||
title,
|
title,
|
||||||
|
extraUserContent,
|
||||||
} = configData;
|
} = configData;
|
||||||
const {
|
const {
|
||||||
small: smallLogo = TEMP_IMAGE,
|
small: smallLogo = TEMP_IMAGE,
|
||||||
|
@ -2,6 +2,8 @@ import { h, Component, createRef } from '/js/web_modules/preact.js';
|
|||||||
import htm from '/js/web_modules/htm.js';
|
import htm from '/js/web_modules/htm.js';
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
|
|
||||||
|
import '/js/web_modules/@justinribeiro/lite-youtube.js';
|
||||||
|
|
||||||
import Message from './message.js';
|
import Message from './message.js';
|
||||||
import ChatInput from './chat-input.js';
|
import ChatInput from './chat-input.js';
|
||||||
import { CALLBACKS, SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
|
import { CALLBACKS, SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
|
||||||
|
@ -3,15 +3,14 @@ import htm from '/js/web_modules/htm.js';
|
|||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
|
|
||||||
import { messageBubbleColorForString } from '../../utils/user-colors.js';
|
import { messageBubbleColorForString } from '../../utils/user-colors.js';
|
||||||
import { formatMessageText, formatTimestamp } from '../../utils/chat.js';
|
|
||||||
import { generateAvatar } from '../../utils/helpers.js';
|
import { generateAvatar } from '../../utils/helpers.js';
|
||||||
|
import { convertToText } from '../../utils/chat.js';
|
||||||
import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
|
import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
|
||||||
|
|
||||||
export default class Message extends Component {
|
export default class Message extends Component {
|
||||||
render(props) {
|
render(props) {
|
||||||
const { message, username } = props;
|
const { message, username } = props;
|
||||||
const { type } = message;
|
const { type } = message;
|
||||||
|
|
||||||
if (type === SOCKET_MESSAGE_TYPES.CHAT) {
|
if (type === SOCKET_MESSAGE_TYPES.CHAT) {
|
||||||
const { image, author, body, timestamp } = message;
|
const { image, author, body, timestamp } = message;
|
||||||
const formattedMessage = formatMessageText(body, username);
|
const formattedMessage = formatMessageText(body, username);
|
||||||
@ -66,3 +65,118 @@ export default class Message extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatMessageText(message, username) {
|
||||||
|
let formattedText = highlightUsername(message, username);
|
||||||
|
formattedText = getMessageWithEmbeds(formattedText);
|
||||||
|
return convertToMarkup(formattedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightUsername(message, username) {
|
||||||
|
const pattern = new RegExp(
|
||||||
|
'@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||||
|
'gi'
|
||||||
|
);
|
||||||
|
return message.replace(
|
||||||
|
pattern,
|
||||||
|
'<span class="highlighted px-1 rounded font-bold bg-orange-500">$&</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageWithEmbeds(message) {
|
||||||
|
var embedText = '';
|
||||||
|
// Make a temporary element so we can actually parse the html and pull anchor tags from it.
|
||||||
|
// This is a better approach than regex.
|
||||||
|
var container = document.createElement('p');
|
||||||
|
container.innerHTML = message;
|
||||||
|
|
||||||
|
var anchors = container.getElementsByTagName('a');
|
||||||
|
for (var i = 0; i < anchors.length; i++) {
|
||||||
|
const url = anchors[i].href;
|
||||||
|
if (getYoutubeIdFromURL(url)) {
|
||||||
|
const youtubeID = getYoutubeIdFromURL(url);
|
||||||
|
embedText += getYoutubeEmbedFromID(youtubeID);
|
||||||
|
} else if (url.indexOf('instagram.com/p/') > -1) {
|
||||||
|
embedText += getInstagramEmbedFromURL(url);
|
||||||
|
} else if (isImage(url)) {
|
||||||
|
embedText += getImageForURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this message only consists of a single embeddable link
|
||||||
|
// then only return the embed and strip the link url from the text.
|
||||||
|
if (embedText !== '' && anchors.length == 1 && isMessageJustAnchor(message, anchors[0])) {
|
||||||
|
return embedText;
|
||||||
|
}
|
||||||
|
return message + embedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYoutubeIdFromURL(url) {
|
||||||
|
try {
|
||||||
|
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
||||||
|
var match = url.match(regExp);
|
||||||
|
|
||||||
|
if (match && match[2].length == 11) {
|
||||||
|
return match[2];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getYoutubeEmbedFromID(id) {
|
||||||
|
return `<div class="chat-embed youtube-embed"><lite-youtube videoid="${id}" /></div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstagramEmbedFromURL(url) {
|
||||||
|
const urlObject = new URL(url.replace(/\/$/, ''));
|
||||||
|
urlObject.pathname += '/embed';
|
||||||
|
return `<iframe class="chat-embed instagram-embed" src="${urlObject.href}" frameborder="0" allowfullscreen></iframe>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImage(url) {
|
||||||
|
const re = /\.(jpe?g|png|gif)$/i;
|
||||||
|
return re.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImageForURL(url) {
|
||||||
|
return `<a target="_blank" href="${url}"><img class="chat-embed embedded-image" src="${url}" /></a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMessageJustAnchor(message, anchor) {
|
||||||
|
return stripTags(message) === stripTags(anchor.innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimestamp(sentAt) {
|
||||||
|
sentAt = new Date(sentAt);
|
||||||
|
if (isNaN(sentAt)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let diffInDays = (new Date() - sentAt) / (24 * 3600 * 1000);
|
||||||
|
if (diffInDays >= 1) {
|
||||||
|
return (
|
||||||
|
`Sent at ${sentAt.toLocaleDateString('en-US', {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
})} at ` + sentAt.toLocaleTimeString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Sent at ${sentAt.toLocaleTimeString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
You would call this when receiving a plain text
|
||||||
|
value back from an API, and before inserting the
|
||||||
|
text into the `contenteditable` area on a page.
|
||||||
|
*/
|
||||||
|
function convertToMarkup(str = '') {
|
||||||
|
return convertToText(str).replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripTags(str) {
|
||||||
|
return str.replace(/<\/?[^>]+(>|$)/g, '');
|
||||||
|
}
|
||||||
|
@ -4,134 +4,6 @@ import {
|
|||||||
CHAT_PLACEHOLDER_OFFLINE,
|
CHAT_PLACEHOLDER_OFFLINE,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
|
|
||||||
import showdown from '/js/web_modules/showdown.js';
|
|
||||||
export function formatMessageText(message, username) {
|
|
||||||
showdown.setFlavor('github');
|
|
||||||
let formattedText = new showdown.Converter({
|
|
||||||
emoji: true,
|
|
||||||
openLinksInNewWindow: true,
|
|
||||||
tables: false,
|
|
||||||
simplifiedAutoLink: false,
|
|
||||||
literalMidWordUnderscores: true,
|
|
||||||
strikethrough: true,
|
|
||||||
ghMentions: false,
|
|
||||||
}).makeHtml(message);
|
|
||||||
|
|
||||||
formattedText = linkify(formattedText, message);
|
|
||||||
formattedText = highlightUsername(formattedText, username);
|
|
||||||
|
|
||||||
return convertToMarkup(formattedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlightUsername(message, username) {
|
|
||||||
const pattern = new RegExp('@?' + username.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
|
|
||||||
return message.replace(
|
|
||||||
pattern,
|
|
||||||
'<span class="highlighted px-1 rounded font-bold bg-orange-500">$&</span>'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function linkify(text, rawText) {
|
|
||||||
const urls = getURLs(stripTags(rawText));
|
|
||||||
if (urls) {
|
|
||||||
urls.forEach(function (url) {
|
|
||||||
let linkURL = url;
|
|
||||||
|
|
||||||
// Add http prefix if none exist in the URL so it actually
|
|
||||||
// will work in an anchor tag.
|
|
||||||
if (linkURL.indexOf('http') === -1) {
|
|
||||||
linkURL = 'http://' + linkURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the protocol prefix in the display URLs just to make
|
|
||||||
// things look a little nicer.
|
|
||||||
const displayURL = url.replace(/(^\w+:|^)\/\//, '');
|
|
||||||
const link = `<a href="${linkURL}" target="_blank">${displayURL}</a>`;
|
|
||||||
text = text.replace(url, link);
|
|
||||||
|
|
||||||
if (getYoutubeIdFromURL(url)) {
|
|
||||||
if (isTextJustURLs(text, [url, displayURL])) {
|
|
||||||
text = '';
|
|
||||||
} else {
|
|
||||||
text += '<br/>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const youtubeID = getYoutubeIdFromURL(url);
|
|
||||||
text += getYoutubeEmbedFromID(youtubeID);
|
|
||||||
} else if (url.indexOf('instagram.com/p/') > -1) {
|
|
||||||
if (isTextJustURLs(text, [url, displayURL])) {
|
|
||||||
text = '';
|
|
||||||
} else {
|
|
||||||
text += `<br/>`;
|
|
||||||
}
|
|
||||||
text += getInstagramEmbedFromURL(url);
|
|
||||||
} else if (isImage(url)) {
|
|
||||||
if (isTextJustURLs(text, [url, displayURL])) {
|
|
||||||
text = '';
|
|
||||||
} else {
|
|
||||||
text += `<br/>`;
|
|
||||||
}
|
|
||||||
text += getImageForURL(url);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTextJustURLs(text, urls) {
|
|
||||||
for (var i = 0; i < urls.length; i++) {
|
|
||||||
const url = urls[i];
|
|
||||||
if (stripTags(text) === url) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function stripTags(str) {
|
|
||||||
return str.replace(/<\/?[^>]+(>|$)/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getURLs(str) {
|
|
||||||
var exp = /((\w+:\/\/\S+)|(\w+[\.:]\w+\S+))[^\s,\.]/ig;
|
|
||||||
return str.match(exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getYoutubeIdFromURL(url) {
|
|
||||||
try {
|
|
||||||
var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
|
|
||||||
var match = url.match(regExp);
|
|
||||||
|
|
||||||
if (match && match[2].length == 11) {
|
|
||||||
return match[2];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getYoutubeEmbedFromID(id) {
|
|
||||||
return `<div class="chat-embed youtube-embed"><lite-youtube videoid="${id}" /></div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInstagramEmbedFromURL(url) {
|
|
||||||
const urlObject = new URL(url.replace(/\/$/, ""));
|
|
||||||
urlObject.pathname += "/embed";
|
|
||||||
return `<iframe class="chat-embed instagram-embed" src="${urlObject.href}" frameborder="0" allowfullscreen></iframe>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isImage(url) {
|
|
||||||
const re = /\.(jpe?g|png|gif)$/i;
|
|
||||||
return re.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageForURL(url) {
|
|
||||||
return `<a target="_blank" href="${url}"><img class="chat-embed embedded-image" src="${url}" /></a>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Taken from https://stackoverflow.com/questions/3972014/get-contenteditable-caret-index-position
|
// Taken from https://stackoverflow.com/questions/3972014/get-contenteditable-caret-index-position
|
||||||
export function getCaretPosition(editableDiv) {
|
export function getCaretPosition(editableDiv) {
|
||||||
@ -234,15 +106,6 @@ export function convertToText(str = '') {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
You would call this when receiving a plain text
|
|
||||||
value back from an API, and before inserting the
|
|
||||||
text into the `contenteditable` area on a page.
|
|
||||||
*/
|
|
||||||
export function convertToMarkup(str = '') {
|
|
||||||
return convertToText(str).replace(/\n/g, '<br>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
You would call this when a user pastes from
|
You would call this when a user pastes from
|
||||||
the clipboard into a `contenteditable` area.
|
the clipboard into a `contenteditable` area.
|
||||||
@ -279,18 +142,3 @@ export function convertOnPaste( event = { preventDefault() {} }) {
|
|||||||
document.execCommand('insertText', false, value);
|
document.execCommand('insertText', false, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatTimestamp(sentAt) {
|
|
||||||
sentAt = new Date(sentAt);
|
|
||||||
if (isNaN(sentAt)) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
let diffInDays = ((new Date()) - sentAt) / (24 * 3600 * 1000);
|
|
||||||
if (diffInDays >= 1) {
|
|
||||||
return `Sent at ${sentAt.toLocaleDateString('en-US', {dateStyle: 'medium'})} at ` +
|
|
||||||
sentAt.toLocaleTimeString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return `Sent at ${sentAt.toLocaleTimeString()}`;
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { c as createCommonjsModule, a as commonjsGlobal, g as getDefaultExportFromCjs } from '../../../common/_commonjsHelpers-37fa8da4.js';
|
import { c as createCommonjsModule, a as commonjsGlobal, d as document_1, w as window_1$1, g as getDefaultExportFromCjs } from '../../../common/window-1e586371.js';
|
||||||
import { d as document_1, w as window_1$1 } from '../../../common/window-2f8a9a85.js';
|
|
||||||
|
|
||||||
var _extends_1 = createCommonjsModule(function (module) {
|
var _extends_1 = createCommonjsModule(function (module) {
|
||||||
function _extends() {
|
function _extends() {
|
||||||
|
66
webroot/js/web_modules/common/window-1e586371.js
Normal file
66
webroot/js/web_modules/common/window-1e586371.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||||
|
|
||||||
|
function getDefaultExportFromCjs (x) {
|
||||||
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCommonjsModule(fn, basedir, module) {
|
||||||
|
return module = {
|
||||||
|
path: basedir,
|
||||||
|
exports: {},
|
||||||
|
require: function (path, base) {
|
||||||
|
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
|
||||||
|
}
|
||||||
|
}, fn(module, module.exports), module.exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultExportFromNamespaceIfNotNamed (n) {
|
||||||
|
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function commonjsRequire () {
|
||||||
|
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
|
||||||
|
}
|
||||||
|
|
||||||
|
var _nodeResolve_empty = {};
|
||||||
|
|
||||||
|
var _nodeResolve_empty$1 = /*#__PURE__*/Object.freeze({
|
||||||
|
__proto__: null,
|
||||||
|
'default': _nodeResolve_empty
|
||||||
|
});
|
||||||
|
|
||||||
|
var minDoc = /*@__PURE__*/getDefaultExportFromNamespaceIfNotNamed(_nodeResolve_empty$1);
|
||||||
|
|
||||||
|
var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
|
||||||
|
typeof window !== 'undefined' ? window : {};
|
||||||
|
|
||||||
|
|
||||||
|
var doccy;
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
doccy = document;
|
||||||
|
} else {
|
||||||
|
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
|
||||||
|
|
||||||
|
if (!doccy) {
|
||||||
|
doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var document_1 = doccy;
|
||||||
|
|
||||||
|
var win;
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
win = window;
|
||||||
|
} else if (typeof commonjsGlobal !== "undefined") {
|
||||||
|
win = commonjsGlobal;
|
||||||
|
} else if (typeof self !== "undefined"){
|
||||||
|
win = self;
|
||||||
|
} else {
|
||||||
|
win = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var window_1 = win;
|
||||||
|
|
||||||
|
export { commonjsGlobal as a, createCommonjsModule as c, document_1 as d, getDefaultExportFromCjs as g, window_1 as w };
|
1
webroot/js/web_modules/import-map.json
vendored
1
webroot/js/web_modules/import-map.json
vendored
@ -6,7 +6,6 @@
|
|||||||
"@videojs/themes/fantasy/index.css": "./@videojs/themes/fantasy/index.css",
|
"@videojs/themes/fantasy/index.css": "./@videojs/themes/fantasy/index.css",
|
||||||
"htm": "./htm.js",
|
"htm": "./htm.js",
|
||||||
"preact": "./preact.js",
|
"preact": "./preact.js",
|
||||||
"showdown": "./showdown.js",
|
|
||||||
"tailwindcss/dist/tailwind.min.css": "./tailwindcss/dist/tailwind.min.css",
|
"tailwindcss/dist/tailwind.min.css": "./tailwindcss/dist/tailwind.min.css",
|
||||||
"video.js/dist/video-js.min.css": "./videojs/dist/video-js.min.css",
|
"video.js/dist/video-js.min.css": "./videojs/dist/video-js.min.css",
|
||||||
"video.js/dist/video.min.js": "./videojs/dist/video.min.js"
|
"video.js/dist/video.min.js": "./videojs/dist/video.min.js"
|
||||||
|
5044
webroot/js/web_modules/showdown.js
vendored
5044
webroot/js/web_modules/showdown.js
vendored
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
|||||||
import { c as createCommonjsModule, a as commonjsGlobal } from '../../common/_commonjsHelpers-37fa8da4.js';
|
import { c as createCommonjsModule, w as window_1, d as document_1, a as commonjsGlobal } from '../../common/window-1e586371.js';
|
||||||
import { w as window_1, d as document_1 } from '../../common/window-2f8a9a85.js';
|
|
||||||
|
|
||||||
var video_min = createCommonjsModule(function (module, exports) {
|
var video_min = createCommonjsModule(function (module, exports) {
|
||||||
/**
|
/**
|
||||||
|
@ -152,6 +152,8 @@
|
|||||||
|
|
||||||
|
|
||||||
.message-text .emoji {
|
.message-text .emoji {
|
||||||
|
position: relative;
|
||||||
|
top: -5px;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
padding: .25rem
|
padding: .25rem
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user