[Comment/Livestream] Markdown and style fixes (#55)
* Fix CSS for live chat embeds * Fix Markdown Lists in Comments * Disable copy link menu option on livestream comments * Fix nested indents in Live Chat * Fix mentions and timestamps not parsed in bullet lists * Highlight livestream comment and menu button on hover * Fix mention parsing
This commit is contained in:
parent
6f3c43c95f
commit
7c518aa712
9 changed files with 81 additions and 33 deletions
|
@ -20,6 +20,7 @@ type Props = {
|
||||||
disableEdit?: boolean,
|
disableEdit?: boolean,
|
||||||
disableRemove?: boolean,
|
disableRemove?: boolean,
|
||||||
supportAmount?: any,
|
supportAmount?: any,
|
||||||
|
isLiveComment: boolean,
|
||||||
// --- select ---
|
// --- select ---
|
||||||
claim: ?Claim,
|
claim: ?Claim,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
|
@ -54,6 +55,7 @@ function CommentMenuList(props: Props) {
|
||||||
disableEdit,
|
disableEdit,
|
||||||
disableRemove,
|
disableRemove,
|
||||||
supportAmount,
|
supportAmount,
|
||||||
|
isLiveComment,
|
||||||
doToast,
|
doToast,
|
||||||
handleEditComment,
|
handleEditComment,
|
||||||
openModal,
|
openModal,
|
||||||
|
@ -229,7 +231,7 @@ function CommentMenuList(props: Props) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{IS_WEB && (
|
{IS_WEB && !isLiveComment && (
|
||||||
<MenuItem className="comment__menu-option" onSelect={handleCopyCommentLink}>
|
<MenuItem className="comment__menu-option" onSelect={handleCopyCommentLink}>
|
||||||
<div className="menu__link">
|
<div className="menu__link">
|
||||||
<Icon aria-hidden icon={ICONS.COPY_LINK} />
|
<Icon aria-hidden icon={ICONS.COPY_LINK} />
|
||||||
|
|
|
@ -10,7 +10,7 @@ import remarkFrontMatter from 'remark-frontmatter';
|
||||||
import reactRenderer from 'remark-react';
|
import reactRenderer from 'remark-react';
|
||||||
import MarkdownLink from 'component/markdownLink';
|
import MarkdownLink from 'component/markdownLink';
|
||||||
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
||||||
import { formatedLinks, inlineLinks } from 'util/remark-lbry';
|
import { formattedLinks, inlineLinks } from 'util/remark-lbry';
|
||||||
import { formattedTimestamp, inlineTimestamp } from 'util/remark-timestamp';
|
import { formattedTimestamp, inlineTimestamp } from 'util/remark-timestamp';
|
||||||
import ZoomableImage from 'component/zoomableImage';
|
import ZoomableImage from 'component/zoomableImage';
|
||||||
import { CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS, SIMPLE_SITE } from 'config';
|
import { CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS, SIMPLE_SITE } from 'config';
|
||||||
|
@ -144,8 +144,9 @@ const MarkdownPreview = (props: MarkdownProps) => {
|
||||||
disableTimestamps,
|
disableTimestamps,
|
||||||
stakedLevel,
|
stakedLevel,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const strippedContent = content
|
const strippedContent = content
|
||||||
? content.replace(REPLACE_REGEX, (iframeHtml, y, iframeSrc) => {
|
? content.replace(REPLACE_REGEX, (iframeHtml) => {
|
||||||
// Let the browser try to create an iframe to see if the markup is valid
|
// Let the browser try to create an iframe to see if the markup is valid
|
||||||
const outer = document.createElement('div');
|
const outer = document.createElement('div');
|
||||||
outer.innerHTML = iframeHtml;
|
outer.innerHTML = iframeHtml;
|
||||||
|
@ -163,6 +164,10 @@ const MarkdownPreview = (props: MarkdownProps) => {
|
||||||
})
|
})
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
const initialQuote = strippedContent.split(' ').find((word) => word.length > 0 || word.charAt(0) === '>');
|
||||||
|
let stripQuote;
|
||||||
|
if (initialQuote && initialQuote.charAt(0) === '>') stripQuote = true;
|
||||||
|
|
||||||
const remarkOptions: Object = {
|
const remarkOptions: Object = {
|
||||||
sanitize: schema,
|
sanitize: schema,
|
||||||
fragment: React.Fragment,
|
fragment: React.Fragment,
|
||||||
|
@ -203,10 +208,22 @@ const MarkdownPreview = (props: MarkdownProps) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Strip all content and just render text
|
// Strip all content and just render text
|
||||||
if (strip) {
|
if (strip || stripQuote) {
|
||||||
// Remove new lines and extra space
|
// Remove new lines and extra space
|
||||||
remarkOptions.remarkReactComponents.p = SimpleText;
|
remarkOptions.remarkReactComponents.p = SimpleText;
|
||||||
return (
|
return stripQuote ? (
|
||||||
|
<span dir="auto" className="markdown-preview">
|
||||||
|
<blockquote>
|
||||||
|
{
|
||||||
|
remark()
|
||||||
|
.use(remarkStrip)
|
||||||
|
.use(remarkFrontMatter, ['yaml'])
|
||||||
|
.use(reactRenderer, remarkOptions)
|
||||||
|
.processSync(content).contents
|
||||||
|
}
|
||||||
|
</blockquote>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
<span dir="auto" className="markdown-preview">
|
<span dir="auto" className="markdown-preview">
|
||||||
{
|
{
|
||||||
remark()
|
remark()
|
||||||
|
@ -226,7 +243,7 @@ const MarkdownPreview = (props: MarkdownProps) => {
|
||||||
.use(remarkAttr, remarkAttrOpts)
|
.use(remarkAttr, remarkAttrOpts)
|
||||||
// Remark plugins for lbry urls
|
// Remark plugins for lbry urls
|
||||||
// Note: The order is important
|
// Note: The order is important
|
||||||
.use(formatedLinks)
|
.use(formattedLinks)
|
||||||
.use(inlineLinks)
|
.use(inlineLinks)
|
||||||
.use(disableTimestamps || isMarkdownPost ? null : inlineTimestamp)
|
.use(disableTimestamps || isMarkdownPost ? null : inlineTimestamp)
|
||||||
.use(disableTimestamps || isMarkdownPost ? null : formattedTimestamp)
|
.use(disableTimestamps || isMarkdownPost ? null : formattedTimestamp)
|
||||||
|
|
|
@ -119,10 +119,11 @@ function LivestreamComment(props: Props) {
|
||||||
commentId={commentId}
|
commentId={commentId}
|
||||||
authorUri={authorUri}
|
authorUri={authorUri}
|
||||||
commentIsMine={commentIsMine}
|
commentIsMine={commentIsMine}
|
||||||
disableEdit
|
|
||||||
isTopLevel
|
|
||||||
isPinned={isPinned}
|
isPinned={isPinned}
|
||||||
disableRemove={supportAmount > 0}
|
disableRemove={supportAmount > 0}
|
||||||
|
isTopLevel
|
||||||
|
disableEdit
|
||||||
|
isLiveComment
|
||||||
/>
|
/>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -268,9 +268,12 @@ $thumbnailWidthSmall: 1rem;
|
||||||
max-width: 35rem;
|
max-width: 35rem;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
|
|
||||||
ul li,
|
ul li {
|
||||||
ol li {
|
list-style-type: disc;
|
||||||
list-style-position: inside;
|
|
||||||
|
ul li {
|
||||||
|
list-style-type: circle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
|
|
@ -87,16 +87,39 @@ $recent-msg-button__height: 2rem;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@media (min-width: $breakpoint-small) {
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-card-background-highlighted);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:hover) {
|
||||||
|
.menu__button:not(:focus):not([aria-expanded='true']) {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.channel-name {
|
.channel-name {
|
||||||
font-size: var(--font-xsmall);
|
font-size: var(--font-xsmall);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.livestream-comment__info {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.livestream-comment--superchat {
|
.livestream-comment--superchat {
|
||||||
+ .livestream-comment--superchat {
|
+ .livestream-comment--superchat {
|
||||||
margin-bottom: var(--spacing-xxs);
|
margin-bottom: var(--spacing-xxs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.livestream-comment__body {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: unset;
|
||||||
|
flex: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.livestream-comment__info {
|
.livestream-comment__info {
|
||||||
margin-top: calc(var(--spacing-xxs) / 2);
|
margin-top: calc(var(--spacing-xxs) / 2);
|
||||||
}
|
}
|
||||||
|
@ -116,13 +139,10 @@ $recent-msg-button__height: 2rem;
|
||||||
|
|
||||||
.livestream-comment__body {
|
.livestream-comment__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
flex-direction: column;
|
||||||
}
|
flex: 2;
|
||||||
|
|
||||||
.livestream-comment__body {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-left: var(--spacing-s);
|
margin-left: var(--spacing-s);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
@include handleChannelGif(2rem);
|
@include handleChannelGif(2rem);
|
||||||
|
@ -354,6 +374,7 @@ $recent-msg-button__height: 2rem;
|
||||||
.livestream-comment__text {
|
.livestream-comment__text {
|
||||||
padding-right: var(--spacing-xl);
|
padding-right: var(--spacing-xl);
|
||||||
padding-bottom: var(--spacing-xxs);
|
padding-bottom: var(--spacing-xxs);
|
||||||
|
|
||||||
.markdown-preview {
|
.markdown-preview {
|
||||||
p {
|
p {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
.markdown-preview {
|
.markdown-preview {
|
||||||
|
word-break: break-all;
|
||||||
|
|
||||||
> :first-child {
|
> :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,19 @@ const mentionRegex = /@[^\s()"]*/gm;
|
||||||
const invalidRegex = /[-_.+=?!@#$%^&*:;,{}<>\w/\\]/;
|
const invalidRegex = /[-_.+=?!@#$%^&*:;,{}<>\w/\\]/;
|
||||||
|
|
||||||
function handlePunctuation(value) {
|
function handlePunctuation(value) {
|
||||||
const modifierIndex =
|
const protocolIndex = value.indexOf('lbry://') === 0 ? protocol.length - 1 : 0;
|
||||||
(value.indexOf(':') >= 0 && value.indexOf(':')) || (value.indexOf('#') >= 0 && value.indexOf('#'));
|
const channelModifierIndex =
|
||||||
|
(value.indexOf(':', protocolIndex) >= 0 && value.indexOf(':', protocolIndex)) ||
|
||||||
|
(value.indexOf('#', protocolIndex) >= 0 && value.indexOf('#', protocolIndex));
|
||||||
|
const claimModifierIndex =
|
||||||
|
(value.indexOf(':', channelModifierIndex + 1) >= 0 && value.indexOf(':', channelModifierIndex + 1)) ||
|
||||||
|
(value.indexOf('#', channelModifierIndex + 1) >= 0 && value.indexOf('#', channelModifierIndex + 1)) ||
|
||||||
|
channelModifierIndex;
|
||||||
|
|
||||||
let punctuationIndex;
|
let punctuationIndex;
|
||||||
punctuationMarks.some((p) => {
|
punctuationMarks.some((p) => {
|
||||||
if (modifierIndex) {
|
if (claimModifierIndex) {
|
||||||
punctuationIndex = value.indexOf(p, modifierIndex + 1) >= 0 && value.indexOf(p, modifierIndex + 1);
|
punctuationIndex = value.indexOf(p, claimModifierIndex + 1) >= 0 && value.indexOf(p, claimModifierIndex + 1);
|
||||||
}
|
}
|
||||||
return punctuationIndex;
|
return punctuationIndex;
|
||||||
});
|
});
|
||||||
|
@ -108,15 +114,15 @@ function tokenizeURI(eat, value, silent) {
|
||||||
|
|
||||||
// Configure tokenizer for lbry urls
|
// Configure tokenizer for lbry urls
|
||||||
tokenizeURI.locator = locateURI;
|
tokenizeURI.locator = locateURI;
|
||||||
tokenizeURI.notInList = true;
|
tokenizeURI.notInList = false;
|
||||||
tokenizeURI.notInLink = true;
|
tokenizeURI.notInLink = true;
|
||||||
tokenizeURI.notInBlock = true;
|
tokenizeURI.notInBlock = false;
|
||||||
|
|
||||||
// Configure tokenizer for lbry channels
|
// Configure tokenizer for lbry channels
|
||||||
tokenizeMention.locator = locateMention;
|
tokenizeMention.locator = locateMention;
|
||||||
tokenizeMention.notInList = true;
|
tokenizeMention.notInList = false;
|
||||||
tokenizeMention.notInLink = true;
|
tokenizeMention.notInLink = true;
|
||||||
tokenizeMention.notInBlock = true;
|
tokenizeMention.notInBlock = false;
|
||||||
|
|
||||||
const visitor = (node, index, parent) => {
|
const visitor = (node, index, parent) => {
|
||||||
if (node.type === 'link' && parent && parent.type === 'paragraph') {
|
if (node.type === 'link' && parent && parent.type === 'paragraph') {
|
||||||
|
@ -149,7 +155,7 @@ const transform = (tree) => {
|
||||||
visit(tree, ['link'], visitor);
|
visit(tree, ['link'], visitor);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatedLinks = () => transform;
|
export const formattedLinks = () => transform;
|
||||||
|
|
||||||
// Main module
|
// Main module
|
||||||
export function inlineLinks() {
|
export function inlineLinks() {
|
||||||
|
|
|
@ -108,9 +108,9 @@ function tokenizeTimestamp(eat, value, silent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenizeTimestamp.locator = locateTimestamp;
|
tokenizeTimestamp.locator = locateTimestamp;
|
||||||
tokenizeTimestamp.notInList = true; // Flag doesn't work? It'll always tokenizes in List and never in Bullet.
|
tokenizeTimestamp.notInList = false; // Flag doesn't work? It'll always tokenizes in List and never in Bullet.
|
||||||
tokenizeTimestamp.notInLink = true;
|
tokenizeTimestamp.notInLink = true;
|
||||||
tokenizeTimestamp.notInBlock = true;
|
tokenizeTimestamp.notInBlock = false;
|
||||||
|
|
||||||
export function inlineTimestamp() {
|
export function inlineTimestamp() {
|
||||||
const Parser = this.Parser;
|
const Parser = this.Parser;
|
||||||
|
|
|
@ -60,14 +60,10 @@ ol {
|
||||||
position: relative;
|
position: relative;
|
||||||
list-style-position: outside;
|
list-style-position: outside;
|
||||||
margin: var(--spacing-xs) 0;
|
margin: var(--spacing-xs) 0;
|
||||||
margin-left: var(--spacing-s);
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
@media (min-width: $breakpoint-small) {
|
|
||||||
margin-left: var(--spacing-xl);
|
margin-left: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.ul--no-style {
|
.ul--no-style {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|
Loading…
Reference in a new issue