diff --git a/web/markdown/markdown.css b/web/markdown/markdown.css --- a/web/markdown/markdown.css +++ b/web/markdown/markdown.css @@ -17,14 +17,9 @@ box-sizing: border-box; width: 100%; margin: 6px 0; -} -div.darkBackground blockquote { - background: #a9a9a9; - border-left: 5px solid #808080; -} -div.lightBackground blockquote { - background: #d3d3d3; - border-left: 5px solid #c0c0c0; + border-radius: 8px; + border-left-width: 8px; + border-left-style: solid; } div.markdown code { diff --git a/web/markdown/markdown.react.js b/web/markdown/markdown.react.js --- a/web/markdown/markdown.react.js +++ b/web/markdown/markdown.react.js @@ -13,16 +13,14 @@ }; function Markdown(props: Props): React.Node { const { children, rules } = props; - const { simpleMarkdownRules, useDarkStyle } = rules; + const { simpleMarkdownRules } = rules; const markdownClassName = React.useMemo( () => classNames({ [css.markdown]: true, - [css.darkBackground]: useDarkStyle, - [css.lightBackground]: !useDarkStyle, }), - [useDarkStyle], + [], ); const parser = React.useMemo( diff --git a/web/markdown/rules.react.js b/web/markdown/rules.react.js --- a/web/markdown/rules.react.js +++ b/web/markdown/rules.react.js @@ -3,7 +3,9 @@ import _memoize from 'lodash/memoize'; import * as React from 'react'; import * as SimpleMarkdown from 'simple-markdown'; +import tinycolor from 'tinycolor2'; +import { threadInfoSelector } from 'lib/selectors/thread-selectors'; import { relativeMemberInfoSelectorForMembersOfThread } from 'lib/selectors/user-selectors'; import * as SharedMarkdown from 'lib/shared/markdown'; import type { RelativeMemberInfo } from 'lib/types/thread-types'; @@ -65,76 +67,117 @@ }; }); -const markdownRules: boolean => MarkdownRules = _memoize(useDarkStyle => { - const linkMarkdownRules = linkRules(useDarkStyle); +const markdownRules: ( + useDarkStyle: boolean, + threadColor: ?string, +) => MarkdownRules = _memoize( + (useDarkStyle, threadColor) => { + const linkMarkdownRules = linkRules(useDarkStyle); - const simpleMarkdownRules = { - ...linkMarkdownRules.simpleMarkdownRules, - autolink: SimpleMarkdown.defaultRules.autolink, - link: { - ...linkMarkdownRules.simpleMarkdownRules.link, - match: SimpleMarkdown.defaultRules.link.match, - }, - blockQuote: { - ...SimpleMarkdown.defaultRules.blockQuote, - // match end of blockQuote by either \n\n or end of string - match: SharedMarkdown.matchBlockQuote(SharedMarkdown.blockQuoteRegex), - parse: SharedMarkdown.parseBlockQuote, - }, - inlineCode: SimpleMarkdown.defaultRules.inlineCode, - em: SimpleMarkdown.defaultRules.em, - strong: SimpleMarkdown.defaultRules.strong, - del: SimpleMarkdown.defaultRules.del, - u: SimpleMarkdown.defaultRules.u, - heading: { - ...SimpleMarkdown.defaultRules.heading, - match: SimpleMarkdown.blockRegex(SharedMarkdown.headingRegex), - }, - mailto: SimpleMarkdown.defaultRules.mailto, - codeBlock: { - ...SimpleMarkdown.defaultRules.codeBlock, - match: SimpleMarkdown.blockRegex(SharedMarkdown.codeBlockRegex), - parse: (capture: SharedMarkdown.Capture) => ({ - content: capture[0].replace(/^ {4}/gm, ''), - }), - }, - fence: { - ...SimpleMarkdown.defaultRules.fence, - match: SimpleMarkdown.blockRegex(SharedMarkdown.fenceRegex), - parse: (capture: SharedMarkdown.Capture) => ({ - type: 'codeBlock', - content: capture[2], - }), - }, - json: { - order: SimpleMarkdown.defaultRules.paragraph.order - 1, - match: (source: string, state: SharedMarkdown.State) => { - if (state.inline) { - return null; - } - return SharedMarkdown.jsonMatch(source); + const simpleMarkdownRules = { + ...linkMarkdownRules.simpleMarkdownRules, + autolink: SimpleMarkdown.defaultRules.autolink, + link: { + ...linkMarkdownRules.simpleMarkdownRules.link, + match: SimpleMarkdown.defaultRules.link.match, + }, + blockQuote: { + ...SimpleMarkdown.defaultRules.blockQuote, + // match end of blockQuote by either \n\n or end of string + match: SimpleMarkdown.blockRegex(SharedMarkdown.blockQuoteRegex), + parse( + capture: SharedMarkdown.Capture, + parse: SharedMarkdown.Parser, + state: SharedMarkdown.State, + ) { + const content = capture[1].replace(/^ *> ?/gm, ''); + return { + content: parse(content, state), + }; + }, + // eslint-disable-next-line react/display-name + react: ( + node: SharedMarkdown.SingleASTNode, + output: SharedMarkdown.Output, + state: SharedMarkdown.State, + ) => { + const backgroundColor = threadColor + ? tinycolor(threadColor).darken(20).toString() + : tinycolor('var(--text-message-default-background)') + .lighten(70) + .toString(); + const borderLeftColor = threadColor + ? tinycolor(threadColor).darken(30).toString() + : tinycolor('var(--text-message-default-background)') + .lighten(50) + .toString(); + + return ( +
+ {output(node.content, state)} +
+ ); + }, + }, + inlineCode: SimpleMarkdown.defaultRules.inlineCode, + em: SimpleMarkdown.defaultRules.em, + strong: SimpleMarkdown.defaultRules.strong, + del: SimpleMarkdown.defaultRules.del, + u: SimpleMarkdown.defaultRules.u, + heading: { + ...SimpleMarkdown.defaultRules.heading, + match: SimpleMarkdown.blockRegex(SharedMarkdown.headingRegex), }, - parse: (capture: SharedMarkdown.Capture) => { - const jsonCapture: SharedMarkdown.JSONCapture = (capture: any); - return { + mailto: SimpleMarkdown.defaultRules.mailto, + codeBlock: { + ...SimpleMarkdown.defaultRules.codeBlock, + match: SimpleMarkdown.blockRegex(SharedMarkdown.codeBlockRegex), + parse: (capture: SharedMarkdown.Capture) => ({ + content: capture[0].replace(/^ {4}/gm, ''), + }), + }, + fence: { + ...SimpleMarkdown.defaultRules.fence, + match: SimpleMarkdown.blockRegex(SharedMarkdown.fenceRegex), + parse: (capture: SharedMarkdown.Capture) => ({ type: 'codeBlock', - content: SharedMarkdown.jsonPrint(jsonCapture), - }; + content: capture[2], + }), }, - }, - list: { - ...SimpleMarkdown.defaultRules.list, - match: SharedMarkdown.matchList, - parse: SharedMarkdown.parseList, - }, - escape: SimpleMarkdown.defaultRules.escape, - }; - return { - ...linkMarkdownRules, - simpleMarkdownRules, - useDarkStyle, - }; -}); + json: { + order: SimpleMarkdown.defaultRules.paragraph.order - 1, + match: (source: string, state: SharedMarkdown.State) => { + if (state.inline) { + return null; + } + return SharedMarkdown.jsonMatch(source); + }, + parse: (capture: SharedMarkdown.Capture) => { + const jsonCapture: SharedMarkdown.JSONCapture = (capture: any); + return { + type: 'codeBlock', + content: SharedMarkdown.jsonPrint(jsonCapture), + }; + }, + }, + list: { + ...SimpleMarkdown.defaultRules.list, + match: SharedMarkdown.matchList, + parse: SharedMarkdown.parseList, + }, + escape: SimpleMarkdown.defaultRules.escape, + }; + return { + ...linkMarkdownRules, + simpleMarkdownRules, + useDarkStyle, + }; + }, + (...args) => JSON.stringify(args), +); function useTextMessageRulesFunc( threadID: ?string, @@ -142,21 +185,30 @@ const threadMembers = useSelector( relativeMemberInfoSelectorForMembersOfThread(threadID), ); + + const threadColor = useSelector(state => { + if (threadID) { + const threadInfo = threadInfoSelector(state)[threadID]; + return threadInfo ? threadInfo.color : null; + } + }); + return React.useMemo(() => { if (!threadMembers) { return undefined; } return _memoize<[boolean], MarkdownRules>((useDarkStyle: boolean) => - textMessageRules(threadMembers, useDarkStyle), + textMessageRules(threadMembers, useDarkStyle, threadColor), ); - }, [threadMembers]); + }, [threadMembers, threadColor]); } function textMessageRules( members: $ReadOnlyArray, useDarkStyle: boolean, + threadColor: ?string, ): MarkdownRules { - const baseRules = markdownRules(useDarkStyle); + const baseRules = markdownRules(useDarkStyle, threadColor); return { ...baseRules, simpleMarkdownRules: { @@ -182,7 +234,7 @@ function getDefaultTextMessageRules(): MarkdownRules { if (!defaultTextMessageRules) { - defaultTextMessageRules = textMessageRules([], false); + defaultTextMessageRules = textMessageRules([], false, null); } return defaultTextMessageRules; } diff --git a/web/modals/threads/settings/thread-settings-relationship-button.react.js b/web/modals/threads/settings/thread-settings-relationship-button.react.js --- a/web/modals/threads/settings/thread-settings-relationship-button.react.js +++ b/web/modals/threads/settings/thread-settings-relationship-button.react.js @@ -1,4 +1,11 @@ // @flow +import { + faUserMinus, + faUserPlus, + faUserShield, + faUserSlash, +} from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import invariant from 'invariant'; import * as React from 'react'; @@ -24,6 +31,7 @@ import Button from '../../../components/button.react'; import { useSelector } from '../../../redux/redux-utils'; +import css from './thread-settings-relationship-tab.css'; const loadingStatusSelector = createLoadingStatusSelector( updateRelationshipsActionTypes, @@ -67,6 +75,35 @@ }; }, [relationshipButton, username]); + const icon = React.useMemo(() => { + let buttonIcon = null; + + if (relationshipButton === relationshipButtons.FRIEND) { + buttonIcon = faUserPlus; + } else if (relationshipButton === relationshipButtons.UNFRIEND) { + buttonIcon = faUserMinus; + } else if (relationshipButton === relationshipButtons.BLOCK) { + buttonIcon = faUserShield; + } else if (relationshipButton === relationshipButtons.UNBLOCK) { + buttonIcon = faUserShield; + } else if (relationshipButton === relationshipButtons.ACCEPT) { + buttonIcon = faUserPlus; + } else if (relationshipButton === relationshipButtons.REJECT) { + buttonIcon = faUserSlash; + } else if (relationshipButton === relationshipButtons.WITHDRAW) { + buttonIcon = faUserMinus; + } + + if (buttonIcon) { + return ( + + ); + } + }, [relationshipButton]); + const dispatchActionPromise = useDispatchActionPromise(); const callUpdateRelationships = useServerCall(updateRelationships); @@ -91,7 +128,10 @@ return ( ); } diff --git a/web/modals/threads/settings/thread-settings-relationship-tab.css b/web/modals/threads/settings/thread-settings-relationship-tab.css --- a/web/modals/threads/settings/thread-settings-relationship-tab.css +++ b/web/modals/threads/settings/thread-settings-relationship-tab.css @@ -3,3 +3,18 @@ flex-direction: column; row-gap: 10px; } + +div.relationshipButtonContent { + display: flex; + flex: 1; + align-items: center; +} + +div.relationshipButtonText { + flex: 1; + margin: 0 24px; +} + +svg.relationshipButtonIcon { + position: absolute; +}