From a574a5c1deaccd75df9f55969b55fdd95503e91e Mon Sep 17 00:00:00 2001 From: zeppi Date: Mon, 14 Feb 2022 17:35:14 -0500 Subject: [PATCH] markdown fixes - blockquotes and embedded videos --- ui/component/common/markdown-preview.jsx | 57 +++++++++----------- ui/component/fileRender/view.jsx | 7 ++- ui/scss/all.scss | 1 + ui/scss/component/_embed-player.scss | 67 ++++++++++++++++++++++++ ui/util/remark-lbry.js | 22 +++++--- web/middleware/iframe-destroyer.js | 16 ++++++ 6 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 ui/scss/component/_embed-player.scss create mode 100644 web/middleware/iframe-destroyer.js diff --git a/ui/component/common/markdown-preview.jsx b/ui/component/common/markdown-preview.jsx index 23b28f4ef..ad4424589 100644 --- a/ui/component/common/markdown-preview.jsx +++ b/ui/component/common/markdown-preview.jsx @@ -1,24 +1,24 @@ // @flow -import * as React from 'react'; -import classnames from 'classnames'; -import remark from 'remark'; -import remarkAttr from 'remark-attr'; -import remarkStrip from 'strip-markdown'; -import remarkEmoji from 'remark-emoji'; -import remarkBreaks from 'remark-breaks'; -import remarkFrontMatter from 'remark-frontmatter'; -import reactRenderer from 'remark-react'; -import MarkdownLink from 'component/markdownLink'; -import defaultSchema from 'hast-util-sanitize/lib/github.json'; +import { CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS } from 'config'; import { formattedLinks, inlineLinks } from 'util/remark-lbry'; import { formattedTimestamp, inlineTimestamp } from 'util/remark-timestamp'; import { formattedEmote, inlineEmote } from 'util/remark-emote'; -import ZoomableImage from 'component/zoomableImage'; -import { CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS } from 'config'; -import Button from 'component/button'; import * as ICONS from 'constants/icons'; -import { parse } from 'node-html-parser'; +import * as React from 'react'; +import Button from 'component/button'; +import classnames from 'classnames'; +import defaultSchema from 'hast-util-sanitize/lib/github.json'; +import MarkdownLink from 'component/markdownLink'; import OptimizedImage from 'component/optimizedImage'; +import reactRenderer from 'remark-react'; +import remark from 'remark'; +import remarkAttr from 'remark-attr'; +import remarkBreaks from 'remark-breaks'; +import remarkEmoji from 'remark-emoji'; +import remarkFrontMatter from 'remark-frontmatter'; +import remarkStrip from 'strip-markdown'; +import ZoomableImage from 'component/zoomableImage'; +import { parse } from 'node-html-parser'; const RE_EMOTE = /:\+1:|:-1:|:[\w-]+:/; @@ -171,17 +171,20 @@ const MarkdownPreview = (props: MarkdownProps) => { } catch (e) {} if (lbrySrc && lbrySrc.startsWith('lbry://')) { + console.log('lbrysrc', lbrySrc); return lbrySrc; } + // + // if (lbrySrc && lbrySrc.startsWith('https://odysee.com')) { + // console.log('lbrysrc', lbrySrc) + // return lbrySrc; + // } + // console.log('iframeh', iframeHtml) return iframeHtml; }) : ''; - const initialQuote = strippedContent.split(' ').find((word) => word.length > 0 || word.charAt(0) === '>'); - let stripQuote; - if (initialQuote && initialQuote.charAt(0) === '>') stripQuote = true; - const remarkOptions: Object = { sanitize: schema, fragment: React.Fragment, @@ -216,22 +219,10 @@ const MarkdownPreview = (props: MarkdownProps) => { }; // Strip all content and just render text - if (strip || stripQuote) { + if (strip) { // Remove new lines and extra space remarkOptions.remarkReactComponents.p = SimpleText; - return stripQuote ? ( - -
- { - remark() - .use(remarkStrip) - .use(remarkFrontMatter, ['yaml']) - .use(reactRenderer, remarkOptions) - .processSync(content).contents - } -
-
- ) : ( + return ( { remark() diff --git a/ui/component/fileRender/view.jsx b/ui/component/fileRender/view.jsx index 0ffe92027..440061ed8 100644 --- a/ui/component/fileRender/view.jsx +++ b/ui/component/fileRender/view.jsx @@ -24,6 +24,7 @@ import PdfViewer from 'component/viewers/pdfViewer'; type Props = { uri: string, streamingUrl: string, + embedded?: boolean, contentType: string, claim: StreamClaim, currentTheme: string, @@ -44,8 +45,9 @@ class FileRender extends React.PureComponent { } componentDidMount() { + const { embedded } = this.props; window.addEventListener('keydown', this.escapeListener, true); - analytics.playerLoadedEvent(); + analytics.playerLoadedEvent(embedded); } componentWillUnmount() { @@ -144,12 +146,13 @@ class FileRender extends React.PureComponent { } render() { - const { renderMode, className } = this.props; + const { embedded, renderMode, className } = this.props; return (
diff --git a/ui/scss/all.scss b/ui/scss/all.scss index 87257d350..9deafe5b0 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -69,3 +69,4 @@ @import 'component/swipe-list'; @import 'component/utils'; @import 'component/settings'; +@import 'component/embed-player'; diff --git a/ui/scss/component/_embed-player.scss b/ui/scss/component/_embed-player.scss new file mode 100644 index 000000000..59ee8119f --- /dev/null +++ b/ui/scss/component/_embed-player.scss @@ -0,0 +1,67 @@ +.embed__wrapper { + height: 100vh; + width: 100vw; + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + background-color: var(--color-black); +} + +.embed__wrapper--light-background { + @extend .embed__wrapper; + + .vjs-poster, + video { + background-color: var(--color-white); + } +} + +.embed__inline-button { + @include thumbnail; + position: relative; + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: 100%; + width: 100%; + height: auto; + display: flex; + justify-content: center; + align-items: center; + border-top-left-radius: var(--border-radius); + border-top-right-radius: var(--border-radius); + background-color: var(--color-black); + + @media (max-width: $breakpoint-small) { + height: 200px; + } +} + +.embed__inline-button-preview { + @extend .embed__inline-button; + background-color: var(--color-editor-inline-code-bg); + width: 50%; +} + +.embed__loading { + width: 100%; + height: 100%; +} + +.embed__loading-text { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-white); + + h1 { + font-size: var(--font-large); + } +} + +.embed__overlay-logo { + max-height: 2rem; + max-width: 7rem; +} diff --git a/ui/util/remark-lbry.js b/ui/util/remark-lbry.js index 129ed45a9..d478751cd 100644 --- a/ui/util/remark-lbry.js +++ b/ui/util/remark-lbry.js @@ -12,13 +12,19 @@ const invalidRegex = /[-_.+=?!@#$%^&*:;,{}<>\w/\\]/; const mentionRegex = /@[^\s()"-_.+=?!@$%^&*;,{}<>/\\]*/gm; function handlePunctuation(value) { - const modifierIndex = - (value.indexOf(':') >= 0 && value.indexOf(':')) || (value.indexOf('#') >= 0 && value.indexOf('#')); + const protocolIndex = value.indexOf('lbry://') === 0 ? protocol.length - 1 : 0; + 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; punctuationMarks.some((p) => { - if (modifierIndex) { - punctuationIndex = value.indexOf(p, modifierIndex + 1) >= 0 && value.indexOf(p, modifierIndex + 1); + if (claimModifierIndex) { + punctuationIndex = value.indexOf(p, claimModifierIndex + 1) >= 0 && value.indexOf(p, claimModifierIndex + 1); } return punctuationIndex; }); @@ -108,15 +114,15 @@ function tokenizeURI(eat, value, silent) { // Configure tokenizer for lbry urls tokenizeURI.locator = locateURI; -tokenizeURI.notInList = true; +tokenizeURI.notInList = false; tokenizeURI.notInLink = true; -tokenizeURI.notInBlock = true; +tokenizeURI.notInBlock = false; // Configure tokenizer for lbry channels tokenizeMention.locator = locateMention; -tokenizeMention.notInList = true; +tokenizeMention.notInList = false; tokenizeMention.notInLink = true; -tokenizeMention.notInBlock = true; +tokenizeMention.notInBlock = false; const visitor = (node, index, parent) => { if (node.type === 'link' && parent && parent.type === 'paragraph') { diff --git a/web/middleware/iframe-destroyer.js b/web/middleware/iframe-destroyer.js new file mode 100644 index 000000000..13d6a7b2c --- /dev/null +++ b/web/middleware/iframe-destroyer.js @@ -0,0 +1,16 @@ +const PAGES = require('../../ui/constants/pages'); + +async function iframeDestroyerMiddleware(ctx, next) { + const { + request: { path }, + } = ctx; + const decodedPath = decodeURIComponent(path); + + if (!decodedPath.startsWith(`/$/${PAGES.EMBED}`)) { + ctx.set('X-Frame-Options', 'DENY'); + } + + return next(); +} + +module.exports = iframeDestroyerMiddleware;