Compare commits

..

1 commit

Author SHA1 Message Date
ByronEricPerez
d7c6b5039c Swap comment servers without going to settings page
work in progress

added input to select custom and default server when creating comment

the problem of synchronizing the two commentServer of the application was solved

btn moved to the correct component and syncs correctly

Fixed why it didn't show comments

Aligned input

size and location of the refresh btn changed, and added maximum and minimum width for the comment server

margin removed

change using overflow

refresh
2022-10-24 11:29:27 -03:00
53 changed files with 514 additions and 780 deletions

View file

@ -38,22 +38,7 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1 - uses: maxim-lobanov/setup-xcode@v1
if: startsWith(runner.os, 'mac') if: startsWith(runner.os, 'mac')
with: with:
xcode-version: '13.1.0' xcode-version: '12.4.0'
# This is gonna be hacky.
# Github made us upgrade xcode, which would force an upgrade of electron-builder to fix mac.
# But there were bugs with copyfiles / extraFiles that kept seeing duplicates erroring on ln.
# A flag USE_HARD_LINKS=false in electron-builder.json was suggested in comments, but that broke windows builds.
# So for now we'll install python2 on mac and make sure it can find it.
# Remove this after successfully upgrading electron-builder.
# HACK part 1
- uses: Homebrew/actions/setup-homebrew@master
if: startsWith(runner.os, 'mac')
# HACK part 2
- name: Install Python2
if: startsWith(runner.os, 'mac')
run: |
/bin/bash -c "$(curl -fsSL https://github.com/alfredapp/dependency-scripts/raw/main/scripts/install-python2.sh)"
echo "PYTHON_PATH=/usr/local/bin/python" >> $GITHUB_ENV
- name: Download blockchain headers - name: Download blockchain headers
run: | run: |
@ -73,7 +58,7 @@ jobs:
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
WIN_CSC_LINK: https://raw.githubusercontent.com/lbryio/lbry-desktop/master/build/cert2023.pfx WIN_CSC_LINK: https://raw.githubusercontent.com/lbryio/lbry-desktop/master/build/cert-2021-2022.pfx
CSC_LINK: https://s3.amazonaws.com/files.lbry.io/cert/osx-csc-2021-2022.p12 CSC_LINK: https://s3.amazonaws.com/files.lbry.io/cert/osx-csc-2021-2022.p12
# UI # UI

View file

View file

View file

View file

View file

View file

@ -2,37 +2,6 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.53.9] - [2023-2-8]
### Changed
- Updated lbrynet to [0.113.0](https://github.com/lbryio/lbry-sdk/releases/tag/v0.113.0)
## [0.53.8] - [2022-11-17]
### Fixed
- Selecting a large file in publish no longer crashes ([#7736](https://github.com/lbryio/lbry-desktop/pull/7736))
- Unfollowing unpublished channels ([#7737](https://github.com/lbryio/lbry-desktop/pull/7737))
### Changed
- Updated xcode to 13.1 and hacked a fix for release ([#7736](https://github.com/lbryio/lbry-desktop/pull/7736))
## [0.53.7] - [2022-11-10]
### Added
- 'Collections' to txo filter _community pr!_ ([#7711](https://github.com/lbryio/lbry-desktop/pull/7711))
- Swap comment servers _community pr!_ ([#7670](https://github.com/lbryio/lbry-desktop/pull/7670))
### Fixed
- Thumbnails no longer disable publish ([#7714](https://github.com/lbryio/lbry-desktop/pull/7714))
- Publishing posts were empty ([#7715](https://github.com/lbryio/lbry-desktop/pull/7715))
- Minor layout fixes _community pr!_ ([#7709](https://github.com/lbryio/lbry-desktop/pull/7709))
- Comment section buttons layout ([#7716](https://github.com/lbryio/lbry-desktop/pull/7716))
### Changed
- Removed watchman and its errors ([#7710](https://github.com/lbryio/lbry-desktop/pull/7710))
- Updated lbrynet to [0.112.0](https://github.com/lbryio/lbry-sdk/releases/tag/v0.112.0)
## [0.53.6] - [2022-10-21] ## [0.53.6] - [2022-10-21]
### Fixed ### Fixed
@ -40,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Show downloads newest first ([#7684](https://github.com/lbryio/lbry-desktop/pull/7684)) - Show downloads newest first ([#7684](https://github.com/lbryio/lbry-desktop/pull/7684))
- Only allow images in image uploader ([#7672](https://github.com/lbryio/lbry-desktop/pull/7672)) - Only allow images in image uploader ([#7672](https://github.com/lbryio/lbry-desktop/pull/7672))
- Fixed bug with csv exports ([#7697](https://github.com/lbryio/lbry-desktop/pull/7697)) - Fixed bug with csv exports ([#7697](https://github.com/lbryio/lbry-desktop/pull/7697))
- Fixed small screen viewer position ([#7677](https://github.com/lbryio/lbry-desktop/pull/7677))
- Fixed various upload bugs including transcoding ([#7688](https://github.com/lbryio/lbry-desktop/pull/7688)) - Fixed various upload bugs including transcoding ([#7688](https://github.com/lbryio/lbry-desktop/pull/7688))
- Fallback for files with no extension ([#7704](https://github.com/lbryio/lbry-desktop/pull/7704)) - Fallback for files with no extension ([#7704](https://github.com/lbryio/lbry-desktop/pull/7704))

View file

@ -66,7 +66,7 @@ _Note: If coming from a deb install, the directory structure is different and yo
| | Flatpak | Arch | Nixpkgs | ARM/ARM64 | | | Flatpak | Arch | Nixpkgs | ARM/ARM64 |
| -------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------- | | -------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------- |
| Latest Release | [FlatHub Page](https://flathub.org/apps/details/io.lbry.lbry-app) | [AUR Package](https://aur.archlinux.org/packages/lbry-desktop-bin/) | [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=lbry&query=lbry) | [Build Guide](https://lbry.tv/@LBRYarm:5) | | Latest Release | [FlatHub Page](https://flathub.org/apps/details/io.lbry.lbry-app) | [AUR Package](https://aur.archlinux.org/packages/lbry-desktop-bin/) | [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=lbry&query=lbry) | [Build Guide](https://lbry.tv/@LBRYarm:5) |
| Maintainers | N/A | [@RubenKelevra](https://github.com/RubenKelevra) | [@Enderger](https://github.com/enderger) | [@Madiator2011](https://github.com/kodxana) | | Maintainers | [@kcSeb](https://keybase.io/kcseb) | [@RubenKelevra](https://github.com/RubenKelevra) | [@Enderger](https://github.com/enderger) | [@Madiator2011](https://github.com/kodxana) |
## Usage ## Usage

Binary file not shown.

View file

@ -24,8 +24,6 @@ const mime = require('mime');
const remote = require('@electron/remote/main'); const remote = require('@electron/remote/main');
const os = require('os'); const os = require('os');
const sudo = require('sudo-prompt'); const sudo = require('sudo-prompt');
const probe = require('ffmpeg-probe');
const MAX_IPC_SEND_BUFFER_SIZE = 500000000; // large files crash when serialized for ipc message
remote.initialize(); remote.initialize();
const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled'); const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled');
@ -355,43 +353,6 @@ ipcMain.handle('get-file-from-path', (event, path, readContents = true) => {
}); });
}); });
ipcMain.handle('get-file-details-from-path', async (event, path) => {
const isFfMp4 = (ffprobeResults) => {
return ffprobeResults &&
ffprobeResults.format &&
ffprobeResults.format.format_name &&
ffprobeResults.format.format_name.includes('mp4');
};
const folders = path.split(/[\\/]/);
const name = folders[folders.length - 1];
let duration = 0, size = 0, mimeType;
try {
await fs.promises.stat(path);
let ffprobeResults;
try {
ffprobeResults = await probe(path);
duration = ffprobeResults.format.duration;
size = ffprobeResults.format.size;
} catch (e) {
}
let fileReadResult;
if (size < MAX_IPC_SEND_BUFFER_SIZE) {
try {
fileReadResult = await fs.promises.readFile(path);
} catch (e) {
}
}
// TODO: use mmmagic to inspect file and get mime type
mimeType = isFfMp4(ffprobeResults) ? 'video/mp4' : mime.getType(name);
const fileData = {name, mime: mimeType || undefined, path, duration: duration, size, buffer: fileReadResult };
return fileData;
} catch (e) {
// no stat
return { error: 'no file' };
}
});
ipcMain.on('get-disk-space', async (event) => { ipcMain.on('get-disk-space', async (event) => {
try { try {
const { data_dir } = await Lbry.settings_get(); const { data_dir } = await Lbry.settings_get();

View file

@ -1,10 +0,0 @@
// @flow
declare type FileData = {
file?: Blob,
path: string,
duration?: number,
size?: number,
mimeType: string,
error?: string,
}

View file

@ -1,6 +1,6 @@
{ {
"name": "lbry", "name": "lbry",
"version": "0.53.9", "version": "0.53.6",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"keywords": [ "keywords": [
"lbry" "lbry"
@ -51,7 +51,6 @@
"electron-notarize": "^1.0.0", "electron-notarize": "^1.0.0",
"electron-updater": "^4.2.4", "electron-updater": "^4.2.4",
"express": "^4.17.1", "express": "^4.17.1",
"ffmpeg-probe": "^1.0.6",
"humanize-duration": "^3.27.0", "humanize-duration": "^3.27.0",
"match-sorter": "^6.3.0", "match-sorter": "^6.3.0",
"mime": "^3.0.0", "mime": "^3.0.0",
@ -217,7 +216,7 @@
"yarn": "^1.3" "yarn": "^1.3"
}, },
"lbrySettings": { "lbrySettings": {
"lbrynetDaemonVersion": "0.113.0", "lbrynetDaemonVersion": "0.111.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon", "lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet" "lbrynetDaemonFileName": "lbrynet"

View file

@ -2318,9 +2318,5 @@
"Odysee Connect --[Section in Help Page]--": "Odysee Connect", "Odysee Connect --[Section in Help Page]--": "Odysee Connect",
"Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:", "Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:",
"Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.", "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.",
"Autoplay Next is on.": "Autoplay Next is on.",
"This will be visible in a few minutes after you submit this form.": "This will be visible in a few minutes after you submit this form.",
"Anon --[used in <%anonymous% Reposted>]--": "Anon",
"Your update is now pending. It will take a few minutes to appear for other users.": "Your update is now pending. It will take a few minutes to appear for other users.",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -1,11 +1,4 @@
// @flow // @flow
/*
Removed Watchman (internal view tracking) code.
This file may eventually implement cantina
Refer to 0cc0e213a5c5bf9e2a76316df5d9da4b250a13c3 for initial integration commit
refer to ___ for removal commit.
*/
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import MatomoTracker from '@datapunt/matomo-tracker-js'; import MatomoTracker from '@datapunt/matomo-tracker-js';
@ -21,6 +14,9 @@ const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.inc
export const SHARE_INTERNAL = 'shareInternal'; export const SHARE_INTERNAL = 'shareInternal';
const SHARE_THIRD_PARTY = 'shareThirdParty'; const SHARE_THIRD_PARTY = 'shareThirdParty';
const WATCHMAN_BACKEND_ENDPOINT = 'https://watchman.na-backend.odysee.com/reports/playback';
// const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
if (isProduction) { if (isProduction) {
ElectronCookies.enable({ ElectronCookies.enable({
origin: 'https://lbry.tv', origin: 'https://lbry.tv',
@ -72,10 +68,114 @@ type LogPublishParams = {
let internalAnalyticsEnabled: boolean = false; let internalAnalyticsEnabled: boolean = false;
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true; if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true;
/**
* Determine the mobile device type viewing the data
* This function returns one of 'and' (Android), 'ios', or 'web'.
*
* @returns {String}
*/
function getDeviceType() {
return 'dsk';
}
// variables initialized for watchman
let amountOfBufferEvents = 0;
let amountOfBufferTimeInMS = 0;
let videoType, userId, claimUrl, playerPoweredBy, videoPlayer, bitrateAsBitsPerSecond;
let lastSentTime;
// calculate data for backend, send them, and reset buffer data for next interval
async function sendAndResetWatchmanData() {
if (!userId) {
return 'Can only be used with a user id';
}
if (!videoPlayer) {
return 'Video player not initialized';
}
let timeSinceLastIntervalSend = new Date() - lastSentTime;
lastSentTime = new Date();
let protocol;
if (videoType === 'application/x-mpegURL') {
protocol = 'hls';
// get bandwidth if it exists from the texttrack (so it's accurate if user changes quality)
// $FlowFixMe
bitrateAsBitsPerSecond = videoPlayer.textTracks?.().tracks_[0]?.activeCues[0]?.value?.bandwidth;
} else {
protocol = 'stb';
}
// current position in video in MS
const positionInVideo = Math.round(videoPlayer.currentTime()) * 1000;
// get the duration marking the time in the video for relative position calculation
const totalDurationInSeconds = Math.round(videoPlayer.duration());
// build object for watchman backend
const objectToSend = {
rebuf_count: amountOfBufferEvents,
rebuf_duration: amountOfBufferTimeInMS,
url: claimUrl.replace('lbry://', ''),
device: getDeviceType(),
duration: timeSinceLastIntervalSend,
protocol,
player: playerPoweredBy,
user_id: userId.toString(),
position: Math.round(positionInVideo),
rel_position: Math.round((positionInVideo / (totalDurationInSeconds * 1000)) * 100),
bitrate: bitrateAsBitsPerSecond,
bandwidth: undefined,
// ...(userDownloadBandwidthInBitsPerSecond && {bandwidth: userDownloadBandwidthInBitsPerSecond}), // add bandwidth if populated
};
// post to watchman
await sendWatchmanData(objectToSend);
// reset buffer data
amountOfBufferEvents = 0;
amountOfBufferTimeInMS = 0;
}
let watchmanInterval;
// clear watchman interval and mark it as null (when video paused)
function stopWatchmanInterval() {
clearInterval(watchmanInterval);
watchmanInterval = null;
}
// creates the setInterval that will run send to watchman on recurring basis
function startWatchmanIntervalIfNotRunning() {
if (!watchmanInterval) {
// instantiate the first time to calculate duration from
lastSentTime = new Date();
}
}
// post data to the backend
async function sendWatchmanData(body) {
try {
const response = await fetch(WATCHMAN_BACKEND_ENDPOINT, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
return response;
} catch (err) {}
}
const analytics: Analytics = { const analytics: Analytics = {
// receive buffer events from tracking plugin and save buffer amounts and times for backend call // receive buffer events from tracking plugin and save buffer amounts and times for backend call
videoBufferEvent: async (claim, data) => { videoBufferEvent: async (claim, data) => {
// stub amountOfBufferEvents = amountOfBufferEvents + 1;
amountOfBufferTimeInMS = amountOfBufferTimeInMS + data.bufferDuration;
},
onDispose: () => {
stopWatchmanInterval();
}, },
/** /**
* Is told whether video is being started or paused, and adjusts interval accordingly * Is told whether video is being started or paused, and adjusts interval accordingly
@ -83,9 +183,40 @@ const analytics: Analytics = {
* @param {object} passedPlayer - VideoJS Player object * @param {object} passedPlayer - VideoJS Player object
*/ */
videoIsPlaying: (isPlaying, passedPlayer) => { videoIsPlaying: (isPlaying, passedPlayer) => {
// stub let playerIsSeeking = false;
// have to use this because videojs pauses/unpauses during seek
// sometimes the seeking function isn't populated yet so check for it as well
if (passedPlayer && passedPlayer.seeking) {
playerIsSeeking = passedPlayer.seeking();
}
// if being paused, and not seeking, send existing data and stop interval
if (!isPlaying && !playerIsSeeking) {
sendAndResetWatchmanData();
stopWatchmanInterval();
// if being told to pause, and seeking, send and restart interval
} else if (!isPlaying && playerIsSeeking) {
sendAndResetWatchmanData();
stopWatchmanInterval();
startWatchmanIntervalIfNotRunning();
// is being told to play, and seeking, don't do anything,
// assume it's been started already from pause
} else if (isPlaying && playerIsSeeking) {
// start but not a seek, assuming a start from paused content
} else if (isPlaying && !playerIsSeeking) {
startWatchmanIntervalIfNotRunning();
}
}, },
videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => { videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
// populate values for watchman when video starts
userId = passedUserId;
claimUrl = canonicalUrl;
playerPoweredBy = poweredBy;
videoType = passedPlayer.currentSource().type;
videoPlayer = passedPlayer;
bitrateAsBitsPerSecond = videoBitrate;
// sendPromMetric('time_to_start', duration); // sendPromMetric('time_to_start', duration);
sendMatomoEvent('Media', 'TimeToStart', claimId, timeToStartVideo); sendMatomoEvent('Media', 'TimeToStart', claimId, timeToStartVideo);
}, },
@ -251,9 +382,24 @@ function sendMatomoEvent(category, action, name, value) {
} }
} }
// Prometheus
// function sendPromMetric(name: string, value?: number) {
// if (IS_WEB) {
// let url = new URL(SDK_API_PATH + '/metric/ui');
// const params = { name: name, value: value ? value.toString() : '' };
// url.search = new URLSearchParams(params).toString();
// return fetch(url, { method: 'post' });
// }
// }
const MatomoInstance = new MatomoTracker({ const MatomoInstance = new MatomoTracker({
urlBase: MATOMO_URL, urlBase: MATOMO_URL,
siteId: MATOMO_ID, // optional, default value: `1` siteId: MATOMO_ID, // optional, default value: `1`
// heartBeat: { // optional, enabled by default
// active: true, // optional, default value: true
// seconds: 10 // optional, default value: `15
// },
// linkTracking: false // optional, default value: true
}); });
analytics.pageView(generateInitialUrl(window.location.hash)); analytics.pageView(generateInitialUrl(window.location.hash));

View file

@ -3,7 +3,7 @@ import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { FormField, FormFieldAreaAdvanced } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import TagsSearch from 'component/tagsSearch'; import TagsSearch from 'component/tagsSearch';
import ErrorText from 'component/common/error-text'; import ErrorText from 'component/common/error-text';
@ -376,7 +376,7 @@ function ChannelForm(props: Props) {
onChange={(e) => setParams({ ...params, title: e.target.value })} onChange={(e) => setParams({ ...params, title: e.target.value })}
maxLength={MAX_TITLE_LEN} maxLength={MAX_TITLE_LEN}
/> />
<FormFieldAreaAdvanced <FormField
type="markdown" type="markdown"
name="content_description2" name="content_description2"
label={__('Description')} label={__('Description')}

View file

@ -17,7 +17,7 @@ import { useHistory } from 'react-router-dom';
import { isNameValid, regexInvalidURI } from 'util/lbryURI'; import { isNameValid, regexInvalidURI } from 'util/lbryURI';
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses'; import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
import { FormField, FormFieldAreaAdvanced } from 'component/common/form'; import { FormField } from 'component/common/form';
import { handleBidChange } from 'util/publish'; import { handleBidChange } from 'util/publish';
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
import { INVALID_NAME_ERROR } from 'constants/claim'; import { INVALID_NAME_ERROR } from 'constants/claim';
@ -371,7 +371,7 @@ function CollectionForm(props: Props) {
usePublishFormMode usePublishFormMode
/> />
</fieldset-section> </fieldset-section>
<FormFieldAreaAdvanced <FormField
type="markdown" type="markdown"
name="content_description2" name="content_description2"
label={__('Description')} label={__('Description')}

View file

@ -17,7 +17,7 @@ import CommentBadge from 'component/common/comment-badge'; // have this?
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import { Menu, MenuButton } from '@reach/menu-button'; import { Menu, MenuButton } from '@reach/menu-button';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import { FormFieldAreaAdvanced, Form } from 'component/common/form'; import { FormField, Form } from 'component/common/form';
import classnames from 'classnames'; import classnames from 'classnames';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import CommentReactions from 'component/commentReactions'; import CommentReactions from 'component/commentReactions';
@ -319,7 +319,7 @@ function CommentView(props: Props) {
<div> <div>
{isEditing ? ( {isEditing ? (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
<FormFieldAreaAdvanced <FormField
className="comment__edit-input" className="comment__edit-input"
type={advancedEditor ? 'markdown' : 'textarea'} type={advancedEditor ? 'markdown' : 'textarea'}
name="editing_comment" name="editing_comment"

View file

@ -1,32 +0,0 @@
// @flow
import React from 'react';
import SelectChannel from 'component/selectChannel';
import Button from 'component/button';
import * as ICONS from 'constants/icons';
type Props = {
isReply: boolean,
advancedHandler: () => void,
advanced: boolean,
};
export default function CommentCreateHeader(props: Props) {
const { isReply, advancedHandler, advanced } = props;
return (
<div className="comment-create__header">
<div className="comment-create__label-wrapper">
<span className="comment-create__label">{(isReply ? __('Replying as') : __('Comment as')) + ' '}</span>
<SelectChannel tiny />
</div>
<div className="form-field__quick-action">
<Button
button="alt"
icon={advanced ? ICONS.SIMPLE_EDITOR : ICONS.ADVANCED_EDITOR}
onClick={advancedHandler}
aria-label={isReply ? undefined : advanced ? __('Simple Editor') : __('Advanced Editor')}
/>
</div>
</div>
);
}

View file

@ -4,7 +4,7 @@ import 'scss/component/_comment-create.scss';
import { buildValidSticker } from 'util/comments'; import { buildValidSticker } from 'util/comments';
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field'; import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { FormFieldAreaAdvanced, Form } from 'component/common/form'; import { FormField, Form } from 'component/common/form';
import { getChannelIdFromClaim } from 'util/claim'; import { getChannelIdFromClaim } from 'util/claim';
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
@ -22,8 +22,8 @@ import I18nMessage from 'component/i18nMessage';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import OptimizedImage from 'component/optimizedImage'; import OptimizedImage from 'component/optimizedImage';
import React from 'react'; import React from 'react';
import SelectChannel from 'component/selectChannel';
import StickerSelector from './sticker-selector'; import StickerSelector from './sticker-selector';
import CommentCreateHeader from './comment-create-header';
import type { ElementRef } from 'react'; import type { ElementRef } from 'react';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
@ -363,6 +363,31 @@ export function CommentCreate(props: Props) {
.catch(() => {}); .catch(() => {});
}, [canReceiveFiatTip, claim.claim_id, claim.name, claim.signing_channel, stickerSelector]); }, [canReceiveFiatTip, claim.claim_id, claim.name, claim.signing_channel, stickerSelector]);
// LIVESTREAM ONLY - REMOVE
// Handle keyboard shortcut comment creation
// React.useEffect(() => {
// function altEnterListener(e: SyntheticKeyboardEvent<*>) {
// const inputRef = formFieldRef && formFieldRef.current && formFieldRef.current.input;
//
// if (inputRef && inputRef.current === document.activeElement) {
// // $FlowFixMe
// const isTyping = e.target.attributes['term'];
//
// if (((isLivestream && !isTyping) || e.ctrlKey || e.metaKey) && e.keyCode === KEYCODES.ENTER) {
// e.preventDefault();
// buttonRef.current.click();
// }
// }
// }
//
// window.addEventListener('keydown', altEnterListener);
//
// // removes the listener so it doesn't cause problems elsewhere in the app
// return () => {
// window.removeEventListener('keydown', altEnterListener);
// };
// }, [isLivestream]);
// ************************************************************************** // **************************************************************************
// Render // Render
// ************************************************************************** // **************************************************************************
@ -384,11 +409,7 @@ export function CommentCreate(props: Props) {
push(pathPlusRedirect); push(pathPlusRedirect);
}} }}
> >
<FormFieldAreaAdvanced <FormField type="textarea" name={'comment_signup_prompt'} placeholder={__('Say something about this...')} />
type="textarea"
name={'comment_signup_prompt'}
placeholder={__('Say something about this...')}
/>
<div className="section__actions--no-margin"> <div className="section__actions--no-margin">
<Button disabled button="primary" label={__('Post --[button to submit something]--')} /> <Button disabled button="primary" label={__('Post --[button to submit something]--')} />
</div> </div>
@ -399,22 +420,22 @@ export function CommentCreate(props: Props) {
return ( return (
<Form <Form
onSubmit={() => {}} onSubmit={() => {}}
className={classnames('comment-create', { className={classnames('commentCreate', {
'comment-create--reply': isReply, 'commentCreate--reply': isReply,
'comment-create--nestedReply': isNested, 'commentCreate--nestedReply': isNested,
'comment-create--bottom': bottom, 'commentCreate--bottom': bottom,
})} })}
> >
{/* Input Box/Preview Box */} {/* Input Box/Preview Box */}
{stickerSelector ? ( {stickerSelector ? (
<StickerSelector onSelect={(sticker) => handleSelectSticker(sticker)} claimIsMine={claimIsMine} /> <StickerSelector onSelect={(sticker) => handleSelectSticker(sticker)} claimIsMine={claimIsMine} />
) : isReviewingStickerComment && activeChannelClaim && selectedSticker ? ( ) : isReviewingStickerComment && activeChannelClaim && selectedSticker ? (
<div className="comment-create__stickerPreview"> <div className="commentCreate__stickerPreview">
<div className="comment-create__stickerPreviewInfo"> <div className="commentCreate__stickerPreviewInfo">
<ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} /> <ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} />
<UriIndicator uri={activeChannelClaim.canonical_url} link /> <UriIndicator uri={activeChannelClaim.canonical_url} link />
</div> </div>
<div className="comment-create__stickerPreviewImage"> <div className="commentCreate__stickerPreviewImage">
<OptimizedImage src={selectedSticker && selectedSticker.url} waitLoad loading="lazy" /> <OptimizedImage src={selectedSticker && selectedSticker.url} waitLoad loading="lazy" />
</div> </div>
{/* figure out lbc sticker prices */} {/* figure out lbc sticker prices */}
@ -426,15 +447,15 @@ export function CommentCreate(props: Props) {
)} )}
</div> </div>
) : isReviewingSupportComment && activeChannelClaim ? ( ) : isReviewingSupportComment && activeChannelClaim ? (
<div className="comment-create__supportCommentPreview"> <div className="commentCreate__supportCommentPreview">
<CreditAmount <CreditAmount
amount={tipAmount} amount={tipAmount}
className="comment-create__supportCommentPreviewAmount" className="commentCreate__supportCommentPreviewAmount"
isFiat={activeTab === TAB_FIAT} isFiat={activeTab === TAB_FIAT}
size={activeTab === TAB_LBC ? 18 : 2} size={activeTab === TAB_LBC ? 18 : 2}
/> />
<ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} /> <ChannelThumbnail xsmall uri={activeChannelClaim.canonical_url} />
<div className="comment-create__supportCommentBody"> <div className="commentCreate__supportCommentBody">
<UriIndicator uri={activeChannelClaim.canonical_url} link /> <UriIndicator uri={activeChannelClaim.canonical_url} link />
<div>{commentValue}</div> <div>{commentValue}</div>
</div> </div>
@ -449,22 +470,23 @@ export function CommentCreate(props: Props) {
/> />
)} )}
<FormFieldAreaAdvanced <FormField
autoFocus={isReply} autoFocus={isReply}
charCount={charCount} charCount={charCount}
className={isReply ? 'content_reply' : 'content_comment'} className={isReply ? 'content_reply' : 'content_comment'}
disabled={isFetchingChannels} disabled={isFetchingChannels}
header={ label={
<CommentCreateHeader <div className="commentCreate__labelWrapper">
isReply={isReply} <span className="commentCreate__label">{(isReply ? __('Replying as') : __('Comment as')) + ' '}</span>
advanced={advancedEditor} <SelectChannel tiny />
advancedHandler={() => setAdvancedEditor(!advancedEditor)} </div>
/>
} }
name={isReply ? 'content_reply' : 'content_description'} name={isReply ? 'content_reply' : 'content_description'}
quickActionLabel={isReply ? undefined : advancedEditor ? __('Simple Editor') : __('Advanced Editor')}
ref={formFieldRef} ref={formFieldRef}
onChange={handleCommentChange} onChange={handleCommentChange}
openEmoteMenu={() => setShowEmotes(!showEmotes)} openEmoteMenu={() => setShowEmotes(!showEmotes)}
quickActionHandler={() => setAdvancedEditor(!advancedEditor)}
onFocus={onTextareaFocus} onFocus={onTextareaFocus}
onBlur={onTextareaBlur} onBlur={onTextareaBlur}
placeholder={__('Say something about this...')} placeholder={__('Say something about this...')}
@ -632,7 +654,7 @@ export function CommentCreate(props: Props) {
{/* Help Text */} {/* Help Text */}
{deletedComment && <div className="error__text">{__('This comment has been deleted.')}</div>} {deletedComment && <div className="error__text">{__('This comment has been deleted.')}</div>}
{!!minAmount && ( {!!minAmount && (
<div className="help--notice comment-create__minAmountNotice"> <div className="help--notice commentCreate__minAmountNotice">
<I18nMessage tokens={{ lbc: <CreditAmount noFormat amount={minAmount} /> }}> <I18nMessage tokens={{ lbc: <CreditAmount noFormat amount={minAmount} /> }}>
{minTip ? 'Comment min: %lbc%' : minSuper ? 'HyperChat min: %lbc%' : ''} {minTip ? 'Comment min: %lbc%' : minSuper ? 'HyperChat min: %lbc%' : ''}
</I18nMessage> </I18nMessage>

View file

@ -366,9 +366,9 @@ const CommentActionButtons = (actionButtonsProps: ActionButtonsProps) => {
const sortButtonProps = { activeSort: sort, changeSort }; const sortButtonProps = { activeSort: sort, changeSort };
return ( return (
<div className={'comment__actions-row'}> <>
{totalComments > 1 && ENABLE_COMMENT_REACTIONS && ( {totalComments > 1 && ENABLE_COMMENT_REACTIONS && (
<div className="comment__sort-group"> <span className="comment__sort">
<SortButton {...sortButtonProps} label={__('Best')} icon={ICONS.BEST} sortOption={SORT_BY.POPULARITY} /> <SortButton {...sortButtonProps} label={__('Best')} icon={ICONS.BEST} sortOption={SORT_BY.POPULARITY} />
<SortButton <SortButton
{...sortButtonProps} {...sortButtonProps}
@ -377,11 +377,13 @@ const CommentActionButtons = (actionButtonsProps: ActionButtonsProps) => {
sortOption={SORT_BY.CONTROVERSY} sortOption={SORT_BY.CONTROVERSY}
/> />
<SortButton {...sortButtonProps} label={__('New')} icon={ICONS.NEW} sortOption={SORT_BY.NEWEST} /> <SortButton {...sortButtonProps} label={__('New')} icon={ICONS.NEW} sortOption={SORT_BY.NEWEST} />
</div> </span>
)} )}
<div className="button_refresh">
<Button button="alt" icon={ICONS.REFRESH} title={__('Refresh')} onClick={() => setPage(0)} />
</div>
{allServers.length >= 2 && ( {allServers.length >= 2 && (
<div className="button__selected-server"> <div className="button_selectedServer">
<FormField <FormField
type="select-tiny" type="select-tiny"
onChange={function (x) { onChange={function (x) {
@ -406,10 +408,7 @@ const CommentActionButtons = (actionButtonsProps: ActionButtonsProps) => {
</FormField> </FormField>
</div> </div>
)} )}
<div className="button_refresh"> </>
<Button button="alt" icon={ICONS.REFRESH} title={__('Refresh')} onClick={() => setPage(0)} />
</div>
</div>
); );
}; };

View file

@ -1,240 +0,0 @@
// @flow
import 'easymde/dist/easymde.min.css';
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
import * as ICONS from 'constants/icons';
import Button from 'component/button';
import MarkdownPreview from 'component/common/markdown-preview';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import SimpleMDE from 'react-simplemde-editor';
import TextareaWithSuggestions from 'component/textareaWithSuggestions';
import type { ElementRef, Node } from 'react';
type Props = {
autoFocus?: boolean,
blockWrap: boolean,
charCount?: number,
children?: React$Node,
disabled?: boolean,
helper?: string | React$Node,
hideSuggestions?: boolean,
isLivestream?: boolean,
label?: string | Node,
labelOnLeft: boolean,
name: string,
noEmojis?: boolean,
placeholder?: string | number,
quickActionLabel?: string,
textAreaMaxLength?: number,
type?: string,
value?: string | number,
onChange?: (any) => any,
openEmoteMenu?: () => void,
quickActionHandler?: (any) => any,
render?: () => React$Node,
header?: React$Node,
};
export class FormFieldAreaAdvanced extends React.PureComponent<Props> {
static defaultProps = { labelOnLeft: false, blockWrap: true };
input: { current: ElementRef<any> };
constructor(props: Props) {
super(props);
this.input = React.createRef();
}
componentDidMount() {
const { autoFocus } = this.props;
const input = this.input.current;
if (input && autoFocus) input.focus();
}
render() {
const {
autoFocus,
blockWrap,
charCount,
children,
helper,
hideSuggestions,
isLivestream,
label,
header,
labelOnLeft,
name,
noEmojis,
quickActionLabel,
textAreaMaxLength,
type,
openEmoteMenu,
quickActionHandler,
render,
...inputProps
} = this.props;
// Ideally, the character count should (and can) be appended to the
// SimpleMDE's "options::status" bar. However, I couldn't figure out how
// to pass the current value to it's callback, nor query the current
// text length from the callback. So, we'll use our own widget.
const hasCharCount = charCount !== undefined && charCount >= 0;
const countInfo = hasCharCount && textAreaMaxLength !== undefined && (
<span className="comment__char-count-mde">{`${charCount || '0'}/${textAreaMaxLength}`}</span>
);
const quickAction =
quickActionLabel && quickActionHandler ? (
<div className="form-field__quick-action">
<Button button="link" onClick={quickActionHandler} label={quickActionLabel} />
</div>
) : null;
const input = () => {
switch (type) {
case 'markdown':
const handleEvents = { contextmenu: openEditorMenu };
const getInstance = (editor) => {
// SimpleMDE max char check
editor.codemirror.on('beforeChange', (instance, changes) => {
if (textAreaMaxLength && changes.update) {
var str = changes.text.join('\n');
var delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from));
if (delta <= 0) return;
delta = instance.getValue().length + delta - textAreaMaxLength;
if (delta > 0) {
str = str.substring(0, str.length - delta);
changes.update(changes.from, changes.to, str.split('\n'));
}
}
});
// "Create Link (Ctrl-K)": highlight URL instead of label:
editor.codemirror.on('changes', (instance, changes) => {
try {
// Grab the last change from the buffered list. I assume the
// buffered one ('changes', instead of 'change') is more efficient,
// and that "Create Link" will always end up last in the list.
const lastChange = changes[changes.length - 1];
if (lastChange.origin === '+input') {
// https://github.com/Ionaru/easy-markdown-editor/blob/8fa54c496f98621d5f45f57577ce630bee8c41ee/src/js/easymde.js#L765
const EASYMDE_URL_PLACEHOLDER = '(https://)';
// The URL placeholder is always placed last, so just look at the
// last text in the array to also cover the multi-line case:
const urlLineText = lastChange.text[lastChange.text.length - 1];
if (urlLineText.endsWith(EASYMDE_URL_PLACEHOLDER) && urlLineText !== '[]' + EASYMDE_URL_PLACEHOLDER) {
const from = lastChange.from;
const to = lastChange.to;
const isSelectionMultiline = lastChange.text.length > 1;
const baseIndex = isSelectionMultiline ? 0 : from.ch;
// Everything works fine for the [Ctrl-K] case, but for the
// [Button] case, this handler happens before the original
// code, thus our change got wiped out.
// Add a small delay to handle that case.
setTimeout(() => {
instance.setSelection(
{ line: to.line, ch: baseIndex + urlLineText.lastIndexOf('(') + 1 },
{ line: to.line, ch: baseIndex + urlLineText.lastIndexOf(')') }
);
}, 25);
}
}
} catch (e) {} // Do nothing (revert to original behavior)
});
};
return (
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
<fieldset-section>
{!header && (
<div className="form-field__two-column">
<div>
<label htmlFor={name}>{label}</label>
</div>
{quickAction}
</div>
)}
{!!header && <div className="form-field__textarea-header">{header}</div>}
<SimpleMDE
{...inputProps}
id={name}
type="textarea"
events={handleEvents}
getMdeInstance={getInstance}
options={{
spellChecker: true,
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
previewRender(plainText) {
const preview = <MarkdownPreview content={plainText} noDataStore />;
return ReactDOMServer.renderToString(preview);
},
}}
/>
{countInfo}
</fieldset-section>
</div>
);
case 'textarea':
return (
<fieldset-section>
{!header && (label || quickAction) && (
<div className="form-field__two-column">
<label htmlFor={name}>{label}</label>
{quickAction}
</div>
)}
{!!header && <div className="form-field__textarea-header">{header}</div>}
{hideSuggestions ? (
<textarea
type={type}
id={name}
maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
ref={this.input}
{...inputProps}
/>
) : (
<TextareaWithSuggestions
type={type}
id={name}
maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
inputRef={this.input}
isLivestream={isLivestream}
{...inputProps}
/>
)}
<div className="form-field__textarea-info">
{!noEmojis && openEmoteMenu && (
<Button
type="alt"
className="button--comment-icons"
title="Emotes"
onClick={openEmoteMenu}
icon={ICONS.EMOJI}
iconSize={20}
/>
)}
{countInfo}
</div>
</fieldset-section>
);
}
};
return (
<>
{type && input()}
{helper && <div className="form-field__help">{helper}</div>}
</>
);
}
}
export default FormFieldAreaAdvanced;

View file

@ -1,7 +1,14 @@
// @flow // @flow
import 'easymde/dist/easymde.min.css'; import 'easymde/dist/easymde.min.css';
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field'; import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
import * as ICONS from 'constants/icons';
import Button from 'component/button';
import MarkdownPreview from 'component/common/markdown-preview';
import React from 'react'; import React from 'react';
import ReactDOMServer from 'react-dom/server';
import SimpleMDE from 'react-simplemde-editor';
import TextareaWithSuggestions from 'component/textareaWithSuggestions';
import type { ElementRef, Node } from 'react'; import type { ElementRef, Node } from 'react';
type Props = { type Props = {
@ -14,15 +21,19 @@ type Props = {
disabled?: boolean, disabled?: boolean,
error?: string | boolean, error?: string | boolean,
helper?: string | React$Node, helper?: string | React$Node,
hideSuggestions?: boolean,
inputButton?: React$Node, inputButton?: React$Node,
isLivestream?: boolean,
label?: string | Node, label?: string | Node,
labelOnLeft: boolean, labelOnLeft: boolean,
max?: number, max?: number,
min?: number, min?: number,
name: string, name: string,
noEmojis?: boolean,
placeholder?: string | number, placeholder?: string | number,
postfix?: string, postfix?: string,
prefix?: string, prefix?: string,
quickActionLabel?: string,
range?: number, range?: number,
readOnly?: boolean, readOnly?: boolean,
stretch?: boolean, stretch?: boolean,
@ -30,6 +41,8 @@ type Props = {
type?: string, type?: string,
value?: string | number, value?: string | number,
onChange?: (any) => any, onChange?: (any) => any,
openEmoteMenu?: () => void,
quickActionHandler?: (any) => any,
render?: () => React$Node, render?: () => React$Node,
}; };
@ -59,15 +72,21 @@ export class FormField extends React.PureComponent<Props> {
children, children,
error, error,
helper, helper,
hideSuggestions,
inputButton, inputButton,
isLivestream,
label, label,
labelOnLeft, labelOnLeft,
name, name,
noEmojis,
postfix, postfix,
prefix, prefix,
quickActionLabel,
stretch, stretch,
textAreaMaxLength, textAreaMaxLength,
type, type,
openEmoteMenu,
quickActionHandler,
render, render,
...inputProps ...inputProps
} = this.props; } = this.props;
@ -82,10 +101,18 @@ export class FormField extends React.PureComponent<Props> {
const countInfo = hasCharCount && textAreaMaxLength !== undefined && ( const countInfo = hasCharCount && textAreaMaxLength !== undefined && (
<span className="comment__char-count-mde">{`${charCount || '0'}/${textAreaMaxLength}`}</span> <span className="comment__char-count-mde">{`${charCount || '0'}/${textAreaMaxLength}`}</span>
); );
const Wrapper = blockWrap const Wrapper = blockWrap
? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section> ? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section>
: ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>; : ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>;
const quickAction =
quickActionLabel && quickActionHandler ? (
<div className="form-field__quick-action">
<Button button="link" onClick={quickActionHandler} label={quickActionLabel} />
</div>
) : null;
const inputSimple = (type: string) => ( const inputSimple = (type: string) => (
<> <>
<input id={name} type={type} {...inputProps} /> <input id={name} type={type} {...inputProps} />
@ -116,22 +143,133 @@ export class FormField extends React.PureComponent<Props> {
return inputSelect(''); return inputSelect('');
case 'select-tiny': case 'select-tiny':
return inputSelect('select--slim'); return inputSelect('select--slim');
case 'markdown':
const handleEvents = { contextmenu: openEditorMenu };
const getInstance = (editor) => {
// SimpleMDE max char check
editor.codemirror.on('beforeChange', (instance, changes) => {
if (textAreaMaxLength && changes.update) {
var str = changes.text.join('\n');
var delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from));
if (delta <= 0) return;
delta = instance.getValue().length + delta - textAreaMaxLength;
if (delta > 0) {
str = str.substring(0, str.length - delta);
changes.update(changes.from, changes.to, str.split('\n'));
}
}
});
// "Create Link (Ctrl-K)": highlight URL instead of label:
editor.codemirror.on('changes', (instance, changes) => {
try {
// Grab the last change from the buffered list. I assume the
// buffered one ('changes', instead of 'change') is more efficient,
// and that "Create Link" will always end up last in the list.
const lastChange = changes[changes.length - 1];
if (lastChange.origin === '+input') {
// https://github.com/Ionaru/easy-markdown-editor/blob/8fa54c496f98621d5f45f57577ce630bee8c41ee/src/js/easymde.js#L765
const EASYMDE_URL_PLACEHOLDER = '(https://)';
// The URL placeholder is always placed last, so just look at the
// last text in the array to also cover the multi-line case:
const urlLineText = lastChange.text[lastChange.text.length - 1];
if (urlLineText.endsWith(EASYMDE_URL_PLACEHOLDER) && urlLineText !== '[]' + EASYMDE_URL_PLACEHOLDER) {
const from = lastChange.from;
const to = lastChange.to;
const isSelectionMultiline = lastChange.text.length > 1;
const baseIndex = isSelectionMultiline ? 0 : from.ch;
// Everything works fine for the [Ctrl-K] case, but for the
// [Button] case, this handler happens before the original
// code, thus our change got wiped out.
// Add a small delay to handle that case.
setTimeout(() => {
instance.setSelection(
{ line: to.line, ch: baseIndex + urlLineText.lastIndexOf('(') + 1 },
{ line: to.line, ch: baseIndex + urlLineText.lastIndexOf(')') }
);
}, 25);
}
}
} catch (e) {} // Do nothing (revert to original behavior)
});
};
return (
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
<fieldset-section>
<div className="form-field__two-column">
<div>
<label htmlFor={name}>{label}</label>
</div>
{quickAction}
</div>
<SimpleMDE
{...inputProps}
id={name}
type="textarea"
events={handleEvents}
getMdeInstance={getInstance}
options={{
spellChecker: true,
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
previewRender(plainText) {
const preview = <MarkdownPreview content={plainText} noDataStore />;
return ReactDOMServer.renderToString(preview);
},
}}
/>
{countInfo}
</fieldset-section>
</div>
);
case 'textarea': case 'textarea':
return ( return (
<fieldset-section> <fieldset-section>
{label && ( {(label || quickAction) && (
<div className="form-field__two-column"> <div className="form-field__two-column">
<label htmlFor={name}>{label}</label> <label htmlFor={name}>{label}</label>
{quickAction}
</div> </div>
)} )}
<textarea
type={type} {hideSuggestions ? (
id={name} <textarea
maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT} type={type}
ref={this.input} id={name}
{...inputProps} maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
/> ref={this.input}
<div className="form-field__textarea-info">{countInfo}</div> {...inputProps}
/>
) : (
<TextareaWithSuggestions
type={type}
id={name}
maxLength={textAreaMaxLength || FF_MAX_CHARS_DEFAULT}
inputRef={this.input}
isLivestream={isLivestream}
{...inputProps}
/>
)}
<div className="form-field__textarea-info">
{!noEmojis && openEmoteMenu && (
<Button
type="alt"
className="button--comment-icons"
title="Emotes"
onClick={openEmoteMenu}
icon={ICONS.EMOJI}
iconSize={20}
/>
)}
{countInfo}
</div>
</fieldset-section> </fieldset-section>
); );
default: default:

View file

@ -1,5 +1,4 @@
export { Form } from './form-components/form'; export { Form } from './form-components/form';
export { FormField } from './form-components/form-field'; export { FormField } from './form-components/form-field';
export { FormFieldAreaAdvanced } from './form-components/form-field-area-advanced';
export { FormFieldPrice } from './form-components/form-field-price'; export { FormFieldPrice } from './form-components/form-field-price';
export { Submit } from './form-components/submit'; export { Submit } from './form-components/submit';

View file

@ -2054,15 +2054,4 @@ export const icons = {
<path d="M12.5,23.24v-1A10.74,10.74,0,0,1,23.24,11.52" /> <path d="M12.5,23.24v-1A10.74,10.74,0,0,1,23.24,11.52" />
</g> </g>
), ),
[ICONS.SIMPLE_EDITOR]: buildIcon(
<g>
<path d="M1 18V6c0-1 1-2 2-2h18c1 0 2 1 2 2v12c0 1-1 2-2 2H3c-1 0-2-1-2-2ZM5 7v4" />
</g>
),
[ICONS.ADVANCED_EDITOR]: buildIcon(
<g>
<path d="M1 20V4c0-1 1-2 2-2h18c1 0 2 1 2 2v16c0 1-1 2-2 2H3c-1 0-2-1-2-2ZM1 11h22" />
<path d="M5 8V6h2v2H5ZM11 8V6h2v2h-2ZM17 8V6h2v2h-2ZM5 14v4" />
</g>
),
}; };

View file

@ -12,7 +12,7 @@ import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
import { onFullscreenChange } from 'util/full-screen'; import { onFullscreenChange } from 'util/full-screen';
import { generateListSearchUrlParams, formatLbryUrlForWeb } from 'util/url'; import { generateListSearchUrlParams, formatLbryUrlForWeb } from 'util/url';
import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize'; import { useIsMobile } from 'effects/use-screensize';
import debounce from 'util/debounce'; import debounce from 'util/debounce';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { isURIEqual } from 'util/lbryURI'; import { isURIEqual } from 'util/lbryURI';
@ -132,7 +132,6 @@ export default function FileRenderFloating(props: Props) {
const playingUriSource = playingUri && playingUri.source; const playingUriSource = playingUri && playingUri.source;
const isComment = playingUriSource === 'comment'; const isComment = playingUriSource === 'comment';
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isMediumScreen = useIsMediumScreen();
const mainFilePlaying = !isFloating && primaryUri && isURIEqual(uri, primaryUri); const mainFilePlaying = !isFloating && primaryUri && isURIEqual(uri, primaryUri);
const [fileViewerRect, setFileViewerRect] = useState(); const [fileViewerRect, setFileViewerRect] = useState();
@ -344,8 +343,7 @@ export default function FileRenderFloating(props: Props) {
'content__viewer--floating': isFloating, 'content__viewer--floating': isFloating,
'content__viewer--inline': !isFloating, 'content__viewer--inline': !isFloating,
'content__viewer--secondary': isComment, 'content__viewer--secondary': isComment,
'content__viewer--theater-mode': 'content__viewer--theater-mode': !isFloating && videoTheaterMode && playingUri?.uri === primaryUri,
!isFloating && videoTheaterMode && !isMediumScreen && playingUri?.uri === primaryUri,
'content__viewer--disable-click': wasDragging, 'content__viewer--disable-click': wasDragging,
})} })}
style={ style={

View file

@ -9,7 +9,6 @@ import * as PAGES from 'constants/pages';
import * as RENDER_MODES from 'constants/file_render_modes'; import * as RENDER_MODES from 'constants/file_render_modes';
import * as KEYCODES from 'constants/keycodes'; import * as KEYCODES from 'constants/keycodes';
import Button from 'component/button'; import Button from 'component/button';
import { useIsMediumScreen } from 'effects/use-screensize';
import isUserTyping from 'util/detect-typing'; import isUserTyping from 'util/detect-typing';
import { getThumbnailCdnUrl } from 'util/thumbnail'; import { getThumbnailCdnUrl } from 'util/thumbnail';
import Nag from 'component/common/nag'; import Nag from 'component/common/nag';
@ -64,7 +63,6 @@ export default function FileRenderInitiator(props: Props) {
const fileStatus = fileInfo && fileInfo.status; const fileStatus = fileInfo && fileInfo.status;
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode); const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode);
const isText = RENDER_MODES.TEXT_MODES.includes(renderMode); const isText = RENDER_MODES.TEXT_MODES.includes(renderMode);
const isMediumScreen = useIsMediumScreen();
const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder); const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder);
const containerRef = React.useRef<any>(); const containerRef = React.useRef<any>();
@ -153,7 +151,7 @@ export default function FileRenderInitiator(props: Props) {
style={thumbnail && !obscurePreview ? { backgroundImage: `url("${thumbnail}")` } : {}} style={thumbnail && !obscurePreview ? { backgroundImage: `url("${thumbnail}")` } : {}}
className={classnames('content__cover', { className={classnames('content__cover', {
'content__cover--disabled': disabled, 'content__cover--disabled': disabled,
'content__cover--theater-mode': videoTheaterMode && !isMediumScreen, 'content__cover--theater-mode': videoTheaterMode,
'content__cover--text': isText, 'content__cover--text': isText,
'card__media--nsfw': obscurePreview, 'card__media--nsfw': obscurePreview,
})} })}

View file

@ -13,7 +13,7 @@ const select = (state, props) => {
if (claimUriBeingPlayed) { if (claimUriBeingPlayed) {
const claim = makeSelectClaimForUri(props.uri)(state); const claim = makeSelectClaimForUri(props.uri)(state);
const claimBeingPlayed = makeSelectClaimForUri(claimUriBeingPlayed)(state); const claimBeingPlayed = makeSelectClaimForUri(claimUriBeingPlayed)(state);
isBeingPlayed = claim && claim.claim_id === claimBeingPlayed.claim_id; isBeingPlayed = claim.claim_id === claimBeingPlayed.claim_id;
} }
return { return {

View file

@ -92,7 +92,7 @@ function Page(props: Props) {
<div <div
className={classnames('main-wrapper__inner', { className={classnames('main-wrapper__inner', {
'main-wrapper__inner--filepage': isOnFilePage, 'main-wrapper__inner--filepage': isOnFilePage,
'main-wrapper__inner--theater-mode': isOnFilePage && videoTheaterMode && !isMediumScreen, 'main-wrapper__inner--theater-mode': isOnFilePage && videoTheaterMode,
})} })}
> >
{!authPage && {!authPage &&
@ -124,7 +124,7 @@ function Page(props: Props) {
'main--file-page': filePage, 'main--file-page': filePage,
'main--settings-page': settingsPage, 'main--settings-page': settingsPage,
'main--markdown': isMarkdown, 'main--markdown': isMarkdown,
'main--theater-mode': isOnFilePage && videoTheaterMode && !isMediumScreen && !isMarkdown, 'main--theater-mode': isOnFilePage && videoTheaterMode && !isMarkdown,
})} })}
> >
{children} {children}

View file

@ -1,6 +1,6 @@
// @flow // @flow
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { FormFieldAreaAdvanced } from 'component/common/form'; import { FormField } from 'component/common/form';
type Props = { type Props = {
uri: ?string, uri: ?string,
@ -99,7 +99,7 @@ function PostEditor(props: Props) {
]); ]);
return ( return (
<FormFieldAreaAdvanced <FormField
type={'markdown'} type={'markdown'}
name="content_post" name="content_post"
label={label} label={label}

View file

@ -1,7 +1,7 @@
// @flow // @flow
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
import React from 'react'; import React from 'react';
import { FormFieldAreaAdvanced } from 'component/common/form'; import { FormField } from 'component/common/form';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import Card from 'component/common/card'; import Card from 'component/common/card';
@ -27,7 +27,7 @@ function PublishDescription(props: Props) {
return ( return (
<Card <Card
actions={ actions={
<FormFieldAreaAdvanced <FormField
type={advancedEditor ? 'markdown' : 'textarea'} type={advancedEditor ? 'markdown' : 'textarea'}
name="content_description" name="content_description"
label={__('Description')} label={__('Description')}

View file

@ -14,7 +14,7 @@ import I18nMessage from 'component/i18nMessage';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import * as PUBLISH_MODES from 'constants/publish_types'; import * as PUBLISH_MODES from 'constants/publish_types';
import PublishName from 'component/publishName'; import PublishName from 'component/publishName';
import path from 'path';
type Props = { type Props = {
uri: ?string, uri: ?string,
mode: ?string, mode: ?string,
@ -99,27 +99,18 @@ function PublishFile(props: Props) {
if (!filePath) { if (!filePath) {
return; return;
} }
async function readSelectedFileDetails() { async function readSelectedFile() {
// Read the file to get the file's duration (if possible) // Read the file to get the file's duration (if possible)
// and offer transcoding it. // and offer transcoding it.
const result = await ipcRenderer.invoke('get-file-details-from-path', filePath); const readFileContents = true;
let file; const result = await ipcRenderer.invoke('get-file-from-path', filePath, readFileContents);
if (result.buffer) { const file = new File([result.buffer], result.name, {
file = new File([result.buffer], result.name, { type: result.mime,
type: result.mime, });
}); const fileWithPath = { file, path: result.path };
} processSelectedFile(fileWithPath);
const fileData: FileData = {
path: result.path,
name: result.name,
mimeType: result.mime || 'application/octet-stream',
size: result.size,
duration: result.duration,
file: file,
};
processSelectedFile(fileData);
} }
readSelectedFileDetails(); readSelectedFile();
}, [filePath]); }, [filePath]);
useEffect(() => { useEffect(() => {
@ -228,11 +219,11 @@ function PublishFile(props: Props) {
} }
} }
function processSelectedFile(fileData: FileData, clearName = true) { function processSelectedFile(fileWithPath: FileWithPath, clearName = true) {
window.URL = window.URL || window.webkitURL; window.URL = window.URL || window.webkitURL;
// select file, start to select a new one, then cancel // select file, start to select a new one, then cancel
if (!fileData || fileData.error) { if (!fileWithPath) {
if (isStillEditing || !clearName) { if (isStillEditing || !clearName) {
updatePublishForm({ filePath: '' }); updatePublishForm({ filePath: '' });
} else { } else {
@ -242,11 +233,8 @@ function PublishFile(props: Props) {
} }
// if video, extract duration so we can warn about bitrate if (typeof file !== 'string') // if video, extract duration so we can warn about bitrate if (typeof file !== 'string')
const file = fileData.file; const file = fileWithPath.file;
// Check to see if it's a video and if mp4 const contentType = file.type && file.type.split('/');
const contentType = fileData.mimeType && fileData.mimeType.split('/'); // get this from electron side
const duration = fileData.duration;
const size = fileData.size;
const isVideo = contentType && contentType[0] === 'video'; const isVideo = contentType && contentType[0] === 'video';
const isMp4 = contentType && contentType[1] === 'mp4'; const isMp4 = contentType && contentType[1] === 'mp4';
@ -254,25 +242,34 @@ function PublishFile(props: Props) {
if (contentType && contentType[0] === 'text') { if (contentType && contentType[0] === 'text') {
isTextPost = contentType[1] === 'plain' || contentType[1] === 'markdown'; isTextPost = contentType[1] === 'plain' || contentType[1] === 'markdown';
setCurrentFileType(contentType.join('/')); setCurrentFileType(contentType);
} else if (path.parse(fileData.path).ext) { } else if (file.name) {
// If user's machine is missing a valid content type registration // If user's machine is missing a valid content type registration
// for markdown content: text/markdown, file extension will be used instead // for markdown content: text/markdown, file extension will be used instead
const extension = path.parse(fileData.path).ext; const extension = file.name.split('.').pop();
isTextPost = MARKDOWN_FILE_EXTENSIONS.includes(extension); isTextPost = MARKDOWN_FILE_EXTENSIONS.includes(extension);
} }
if (isVideo) { if (isVideo) {
if (isMp4) { if (isMp4) {
updateFileInfo(duration || 0, size, isVideo); const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = () => {
updateFileInfo(video.duration, file.size, isVideo);
window.URL.revokeObjectURL(video.src);
};
video.onerror = () => {
updateFileInfo(0, file.size, isVideo);
};
video.src = window.URL.createObjectURL(file);
} else { } else {
updateFileInfo(duration || 0, size, isVideo); updateFileInfo(0, file.size, isVideo);
} }
} else { } else {
updateFileInfo(0, size, isVideo); updateFileInfo(0, file.size, isVideo);
} }
if (isTextPost && file) { if (isTextPost) {
// Create reader // Create reader
const reader = new FileReader(); const reader = new FileReader();
// Handler for file reader // Handler for file reader
@ -286,7 +283,7 @@ function PublishFile(props: Props) {
// Strip off extension and replace invalid characters // Strip off extension and replace invalid characters
if (!isStillEditing) { if (!isStillEditing) {
const fileWithoutExtension = path.parse(fileData.path).name; const fileWithoutExtension = name || (file.name && file.name.substring(0, file.name.lastIndexOf('.'))) || '';
updatePublishForm({ name: parseName(fileWithoutExtension) }); updatePublishForm({ name: parseName(fileWithoutExtension) });
} }
} }

View file

@ -208,6 +208,7 @@ function PublishForm(props: Props) {
isNameValid(name) && isNameValid(name) &&
title && title &&
bid && bid &&
thumbnail &&
!bidError && !bidError &&
!emptyPostError && !emptyPostError &&
!(thumbnailError && !thumbnailUploaded) && !(thumbnailError && !thumbnailUploaded) &&

View file

@ -106,7 +106,7 @@ function SelectThumbnail(props: Props) {
__('This will be visible in a few minutes after you submit this form.')} __('This will be visible in a few minutes after you submit this form.')}
<img <img
style={{ display: 'none' }} style={{ display: 'none' }}
src={thumbnail || ThumbnailMissingImage} src={thumbnail}
alt={__('Thumbnail Preview')} alt={__('Thumbnail Preview')}
onError={() => { onError={() => {
if (updateThumbnailParams) { if (updateThumbnailParams) {

View file

@ -103,8 +103,6 @@ function TxoList(props: Props) {
params[TXO.TX_TYPE] = currentUrlParams.type; params[TXO.TX_TYPE] = currentUrlParams.type;
} else if (currentUrlParams.type === TXO.PUBLISH) { } else if (currentUrlParams.type === TXO.PUBLISH) {
params[TXO.TX_TYPE] = TXO.STREAM; params[TXO.TX_TYPE] = TXO.STREAM;
} else if (currentUrlParams.type === TXO.COLLECTION) {
params[TXO.TX_TYPE] = currentUrlParams.type;
} }
} }
if (currentUrlParams.active) { if (currentUrlParams.active) {

View file

@ -186,5 +186,3 @@ export const MYSTERIES = 'Mysteries';
export const TECHNOLOGY = 'Technology'; export const TECHNOLOGY = 'Technology';
export const EMOJI = 'Emoji'; export const EMOJI = 'Emoji';
export const STICKER = 'Sticker'; export const STICKER = 'Sticker';
export const SIMPLE_EDITOR = 'SimpleEditor';
export const ADVANCED_EDITOR = 'AdvancedEditor';

View file

@ -11,8 +11,7 @@ export const SUPPORT = 'support';
export const CHANNEL = 'channel'; export const CHANNEL = 'channel';
export const PUBLISH = 'publish'; export const PUBLISH = 'publish';
export const REPOST = 'repost'; export const REPOST = 'repost';
export const COLLECTION = 'collection'; export const DROPDOWN_TYPES = [ALL, SENT, RECEIVED, SUPPORT, CHANNEL, PUBLISH, REPOST];
export const DROPDOWN_TYPES = [ALL, SENT, RECEIVED, SUPPORT, CHANNEL, PUBLISH, REPOST, COLLECTION];
// dropdown subtypes // dropdown subtypes
export const TIP = 'tip'; export const TIP = 'tip';
export const PURCHASE = 'purchase'; export const PURCHASE = 'purchase';

View file

@ -1,7 +1,6 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { useIsMediumScreen } from 'effects/use-screensize';
import Page from 'component/page'; import Page from 'component/page';
import * as RENDER_MODES from 'constants/file_render_modes'; import * as RENDER_MODES from 'constants/file_render_modes';
import FileTitleSection from 'component/fileTitleSection'; import FileTitleSection from 'component/fileTitleSection';
@ -60,7 +59,6 @@ function FilePage(props: Props) {
} = props; } = props;
const cost = costInfo ? costInfo.cost : null; const cost = costInfo ? costInfo.cost : null;
const hasFileInfo = fileInfo !== undefined; const hasFileInfo = fileInfo !== undefined;
const isMediumScreen = useIsMediumScreen();
const isMarkdown = renderMode === RENDER_MODES.MARKDOWN; const isMarkdown = renderMode === RENDER_MODES.MARKDOWN;
const videoPlayedEnoughToResetPosition = React.useMemo(() => { const videoPlayedEnoughToResetPosition = React.useMemo(() => {
const durationInSecs = const durationInSecs =
@ -171,10 +169,8 @@ function FilePage(props: Props) {
<div className={classnames('section card-stack', `file-page__${renderMode}`)}> <div className={classnames('section card-stack', `file-page__${renderMode}`)}>
<FileTitleSection uri={uri} isNsfwBlocked /> <FileTitleSection uri={uri} isNsfwBlocked />
</div> </div>
{collection && !isMarkdown && !videoTheaterMode && !isMediumScreen && ( {collection && !isMarkdown && !videoTheaterMode && <CollectionContent id={collectionId} uri={uri} />}
<CollectionContent id={collectionId} uri={uri} /> {!collection && !isMarkdown && !videoTheaterMode && <RecommendedContent uri={uri} />}
)}
{!collection && !isMarkdown && !videoTheaterMode && !isMediumScreen && <RecommendedContent uri={uri} />}
</Page> </Page>
); );
} }
@ -191,17 +187,13 @@ function FilePage(props: Props) {
{commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />} {commentsDisabled && <Empty text={__('The creator of this content has disabled comments.')} />}
{!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} />} {!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} />}
</div> </div>
{!collection && !isMarkdown && videoTheaterMode && !isMediumScreen && <RecommendedContent uri={uri} />} {!collection && !isMarkdown && videoTheaterMode && <RecommendedContent uri={uri} />}
{collection && !isMarkdown && videoTheaterMode && !isMediumScreen && ( {collection && !isMarkdown && videoTheaterMode && <CollectionContent id={collectionId} uri={uri} />}
<CollectionContent id={collectionId} uri={uri} />
)}
</div> </div>
)} )}
</div> </div>
{collection && !isMarkdown && !videoTheaterMode && !isMediumScreen && ( {collection && !isMarkdown && !videoTheaterMode && <CollectionContent id={collectionId} uri={uri} />}
<CollectionContent id={collectionId} uri={uri} /> {!collection && !isMarkdown && !videoTheaterMode && <RecommendedContent uri={uri} />}
)}
{!collection && !isMarkdown && !videoTheaterMode && !isMediumScreen && <RecommendedContent uri={uri} />}
{isMarkdown && ( {isMarkdown && (
<div className="file-page__post-comments"> <div className="file-page__post-comments">
{!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} commentsAreExpanded />} {!commentsDisabled && <CommentsList uri={uri} linkedCommentId={linkedCommentId} commentsAreExpanded />}

View file

@ -62,7 +62,7 @@ class ReportPage extends React.Component {
name="message" name="message"
stretch stretch
value={this.state.message} value={this.state.message}
onChange={(event) => { onChange={event => {
this.onMessageChange(event); this.onMessageChange(event);
}} }}
placeholder={__('Description of your issue or feature request')} placeholder={__('Description of your issue or feature request')}
@ -71,7 +71,7 @@ class ReportPage extends React.Component {
<div className="section__actions"> <div className="section__actions">
<Button <Button
button="primary" button="primary"
onClick={(event) => { onClick={event => {
this.submitMessage(event); this.submitMessage(event);
}} }}
className={`button-block button-primary ${this.state.submitting ? 'disabled' : ''}`} className={`button-block button-primary ${this.state.submitting ? 'disabled' : ''}`}

View file

@ -458,34 +458,32 @@ export function doAnalyticsView(uri, timeToStart) {
} }
export function doAnalyticsBuffer(uri, bufferData) { export function doAnalyticsBuffer(uri, bufferData) {
return () => { return (dispatch, getState) => {
// return (dispatch, getState) => { const state = getState();
// const state = getState(); const claim = selectClaimForUri(state, uri);
// const claim = selectClaimForUri(state, uri); const user = selectUser(state);
// const user = selectUser(state); const {
// const { value: { video, audio, source },
// value: { video, audio, source }, } = claim;
// } = claim; const timeAtBuffer = parseInt(bufferData.currentTime ? bufferData.currentTime * 1000 : 0);
// const timeAtBuffer = parseInt(bufferData.currentTime ? bufferData.currentTime * 1000 : 0); const bufferDuration = parseInt(bufferData.secondsToLoad ? bufferData.secondsToLoad * 1000 : 0);
// const bufferDuration = parseInt(bufferData.secondsToLoad ? bufferData.secondsToLoad * 1000 : 0); const fileDurationInSeconds = (video && video.duration) || (audio && audio.duration);
// const fileDurationInSeconds = (video && video.duration) || (audio && audio.duration); const fileSize = source.size; // size in bytes
// const fileSize = source.size; // size in bytes const fileSizeInBits = fileSize * 8;
// const fileSizeInBits = fileSize * 8; const bitRate = parseInt(fileSizeInBits / fileDurationInSeconds);
// const bitRate = parseInt(fileSizeInBits / fileDurationInSeconds); const userId = user && user.id.toString();
// const userId = user && user.id.toString();
// if there's a logged in user, send buffer event data to watchman // if there's a logged in user, send buffer event data to watchman
// if (<condition>) { if (userId) {
// STUB: any buffer events here analytics.videoBufferEvent(claim, {
// analytics.videoBufferEvent(claim, { timeAtBuffer,
// timeAtBuffer, bufferDuration,
// bufferDuration, bitRate,
// bitRate, userId,
// userId, duration: fileDurationInSeconds,
// duration: fileDurationInSeconds, playerPoweredBy: bufferData.playerPoweredBy,
// playerPoweredBy: bufferData.playerPoweredBy, readyState: bufferData.readyState,
// readyState: bufferData.readyState, });
// }); }
// }
}; };
} }

View file

@ -9,7 +9,7 @@ import {
selectClaimForUri, selectClaimForUri,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { swapKeyAndValue } from 'util/swap-json'; import { swapKeyAndValue } from 'util/swap-json';
import { getChannelFromClaim, isChannelClaim } from 'util/claim'; import { getChannelFromClaim } from 'util/claim';
// Returns the entire subscriptions state // Returns the entire subscriptions state
const selectState = (state) => state.subscriptions || {}; const selectState = (state) => state.subscriptions || {};
@ -114,18 +114,12 @@ export const makeSelectChannelInSubscriptions = (uri) =>
createSelector(selectSubscriptions, (subscriptions) => subscriptions.some((sub) => sub.uri === uri)); createSelector(selectSubscriptions, (subscriptions) => subscriptions.some((sub) => sub.uri === uri));
export const selectIsSubscribedForUri = createCachedSelector( export const selectIsSubscribedForUri = createCachedSelector(
(state, uri) => uri,
selectClaimForUri, selectClaimForUri,
selectSubscriptions, selectSubscriptions,
(uri, claim, subscriptions) => { (claim, subscriptions) => {
const channelClaim = getChannelFromClaim(claim); const channelClaim = getChannelFromClaim(claim);
if (channelClaim) { if (channelClaim) {
const permanentUrl = channelClaim.permanent_url; const uri = channelClaim.permanent_url;
return subscriptions.some((sub) => isURIEqual(sub.uri, permanentUrl));
}
// If it failed, it could be an abandoned channel. Try parseURI:
if (isChannelClaim(claim, uri)) {
return subscriptions.some((sub) => isURIEqual(sub.uri, uri)); return subscriptions.some((sub) => isURIEqual(sub.uri, uri));
} }
return false; return false;

View file

@ -167,7 +167,7 @@ a.button--alt {
.vjs-button--theater-mode.vjs-button { .vjs-button--theater-mode.vjs-button {
display: none; display: none;
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
display: block; display: block;
order: 1; order: 1;
background-repeat: no-repeat; background-repeat: no-repeat;

View file

@ -41,7 +41,7 @@
margin: 0px var(--spacing-xxs); margin: 0px var(--spacing-xxs);
} }
.button + .comment-create { .button + .commentCreate {
margin-top: var(--spacing-xxs); margin-top: var(--spacing-xxs);
} }
} }
@ -615,7 +615,7 @@
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
font-size: var(--font-small); font-size: var(--font-small);
border-bottom: none;
.button--link { .button--link {
font-size: var(--font-xsmall); font-size: var(--font-xsmall);
margin: 0px; margin: 0px;
@ -659,7 +659,7 @@
} }
} }
.button__selected-server { .button_selectedServer {
display: inline; display: inline;
float: right; float: right;
select { select {

View file

@ -621,7 +621,7 @@
@media (max-width: $breakpoint-xsmall) { @media (max-width: $breakpoint-xsmall) {
-webkit-line-clamp: 2 !important; -webkit-line-clamp: 2 !important;
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
-webkit-line-clamp: 1 !important; -webkit-line-clamp: 1 !important;
} }
} }

View file

@ -7,7 +7,7 @@ $thumbnailWidthSmall: 1rem;
position: relative; position: relative;
} }
.comment-create { .commentCreate {
font-size: var(--font-small); font-size: var(--font-small);
position: relative; position: relative;
@ -135,12 +135,12 @@ $thumbnailWidthSmall: 1rem;
} }
} }
.comment-create--reply { .commentCreate--reply {
margin-top: var(--spacing-m); margin-top: var(--spacing-m);
position: relative; position: relative;
} }
.comment-create--nestedReply { .commentCreate--nestedReply {
margin-top: var(--spacing-s); margin-top: var(--spacing-s);
margin-left: calc((#{$thumbnailWidthSmall} + var(--spacing-xs)) * 2 + var(--spacing-m) + 4px); margin-left: calc((#{$thumbnailWidthSmall} + var(--spacing-xs)) * 2 + var(--spacing-m) + 4px);
@ -149,40 +149,27 @@ $thumbnailWidthSmall: 1rem;
} }
} }
.comment-create--bottom { .commentCreate--bottom {
padding-bottom: 0; padding-bottom: 0;
} }
.comment-create__header {
display: flex;
justify-content: space-between;
align-items: flex-end;
.comment-create__header-button {
display: flex;
justify-content: space-between;
}
.button--alt {
padding: var(--spacing-xs);
height: unset;
margin-bottom: var(--spacing-xxs);
background: unset;
}
}
.comment-create__label-wrapper { .comment-create__label-wrapper {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
align-items: baseline; align-items: baseline;
flex-wrap: wrap; flex-wrap: wrap;
max-width: 50%; width: 100%;
.comment-create__label { .comment-create__label {
white-space: nowrap; white-space: nowrap;
margin-right: var(--spacing-xs); margin-right: var(--spacing-xs);
} }
fieldset-section {
max-width: 10rem;
}
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
fieldset-section { fieldset-section {
font-size: var(--font-xxsmall); font-size: var(--font-xxsmall);
@ -192,14 +179,14 @@ $thumbnailWidthSmall: 1rem;
font-size: var(--font-xxsmall); font-size: var(--font-xxsmall);
} }
//select { select {
// height: 1rem; height: 1rem;
// margin: var(--spacing-xxs) 0px; margin: var(--spacing-xxs) 0px;
//} }
} }
} }
.comment-create__supportCommentPreview { .commentCreate__supportCommentPreview {
display: flex; display: flex;
align-items: center; align-items: center;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
@ -207,7 +194,7 @@ $thumbnailWidthSmall: 1rem;
padding: var(--spacing-s); padding: var(--spacing-s);
margin: var(--spacing-s) 0; margin: var(--spacing-s) 0;
.comment-create__supportCommentPreviewAmount { .commentCreate__supportCommentPreviewAmount {
margin-right: var(--spacing-m); margin-right: var(--spacing-m);
font-size: var(--font-large); font-size: var(--font-large);
} }
@ -236,8 +223,8 @@ $thumbnailWidthSmall: 1rem;
} }
} }
.comment-create__stickerPreview { .commentCreate__stickerPreview {
@extend .comment-create; @extend .commentCreate;
display: flex; display: flex;
background-color: var(--color-header-background); background-color: var(--color-header-background);
border-radius: var(--border-radius); border-radius: var(--border-radius);
@ -247,12 +234,12 @@ $thumbnailWidthSmall: 1rem;
width: 100%; width: 100%;
height: 10rem; height: 10rem;
.comment-create__stickerPreviewInfo { .commentCreate__stickerPreviewInfo {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
} }
.comment-create__stickerPreviewImage { .commentCreate__stickerPreviewImage {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin-left: var(--spacing-m); margin-left: var(--spacing-m);

View file

@ -45,12 +45,6 @@ $thumbnailWidthSmall: 1rem;
} }
} }
.comment__actions-row {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.comment { .comment {
width: 100%; width: 100%;
display: flex; display: flex;
@ -507,7 +501,7 @@ $thumbnailWidthSmall: 1rem;
min-width: 100%; min-width: 100%;
max-width: 100%; max-width: 100%;
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
min-width: 40%; min-width: 40%;
max-width: 40%; max-width: 40%;
} }
@ -549,7 +543,7 @@ $thumbnailWidthSmall: 1rem;
} }
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
margin: 0 var(--spacing-xs); margin: 0 var(--spacing-xs);
} }
@ -564,7 +558,7 @@ $thumbnailWidthSmall: 1rem;
padding-left: var(--spacing-m); padding-left: var(--spacing-m);
border-left: 4px solid var(--color-border); border-left: 4px solid var(--color-border);
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
margin-top: 0; margin-top: 0;
margin-left: var(--spacing-s); margin-left: var(--spacing-s);
} }

View file

@ -29,12 +29,7 @@ select,
background-color: var(--color-secondary); background-color: var(--color-secondary);
} }
} }
textarea {
height: var(--height-input);
border-radius: var(--border-radius);
color: var(--color-input);
background-color: var(--color-input-bg);
}
@media (min-width: $breakpoint-small) { @media (min-width: $breakpoint-small) {
textarea { textarea {
height: var(--height-input); height: var(--height-input);
@ -537,7 +532,6 @@ fieldset-group {
} }
.form-field__quick-action { .form-field__quick-action {
text-align: right;
font-size: var(--font-xsmall); font-size: var(--font-xsmall);
} }

View file

@ -73,7 +73,7 @@ body {
} }
.sidebar--pusher--open { .sidebar--pusher--open {
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
transform: scaleX(0.9) translateX(calc(5.4 * var(--spacing-l))) scaleY(0.9); transform: scaleX(0.9) translateX(calc(5.4 * var(--spacing-l))) scaleY(0.9);
} }
} }
@ -155,7 +155,7 @@ body {
} }
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
flex-direction: row; flex-direction: row;
} }
@media (max-width: $breakpoint-medium) { @media (max-width: $breakpoint-medium) {
@ -461,6 +461,7 @@ body {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
margin-top: 0; margin-top: 0;
width: 100vw;
max-width: none; max-width: none;
> :first-child { > :first-child {
@ -803,7 +804,7 @@ body {
} }
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
> :first-child { > :first-child {
width: calc(30% - var(--spacing-l)); width: calc(30% - var(--spacing-l));
max-width: 25rem; max-width: 25rem;

View file

@ -70,7 +70,7 @@
color: var(--color-brand-contrast) !important; color: var(--color-brand-contrast) !important;
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
overflow-y: hidden; overflow-y: hidden;
justify-content: space-between; justify-content: space-between;
@ -235,7 +235,7 @@
@extend .navigation-link--highlighted; @extend .navigation-link--highlighted;
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
text-align: left; text-align: left;
margin-bottom: 0; margin-bottom: 0;

View file

@ -32,17 +32,13 @@ $contentMaxWidth: 60rem;
} }
} }
.comment-create { .commentCreate {
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
padding-top: var(--spacing-s); padding-top: var(--spacing-s);
.comment-create__label { .commentCreate__label {
color: var(--color-text); color: var(--color-text);
} }
.comment-create__header {
display: grid;
grid-template-columns: 3fr 1fr;
}
textarea, textarea,
select, select,
.button:not(.button--file-action) { .button:not(.button--file-action) {
@ -85,7 +81,7 @@ $contentMaxWidth: 60rem;
} }
} }
.comment-create, .commentCreate,
.comment__content { .comment__content {
margin: var(--spacing-m); margin: var(--spacing-m);
margin-bottom: 0; margin-bottom: 0;

View file

@ -85,7 +85,7 @@
border-radius: 0 !important; border-radius: 0 !important;
} }
.card__main-actions .comment-create .MuiOutlinedInput-notchedOutline { .card__main-actions .commentCreate .MuiOutlinedInput-notchedOutline {
border: 1px solid var(--color-border) !important; border: 1px solid var(--color-border) !important;
border-radius: var(--border-radius) !important; border-radius: var(--border-radius) !important;
} }
@ -104,7 +104,7 @@
textarea { textarea {
border: none; border: none;
padding: var(--spacing-xxs) var(--spacing-xxs); margin: 9px 0px;
} }
button { button {
@ -320,7 +320,7 @@
} }
@media (max-width: $breakpoint-small) { @media (max-width: $breakpoint-small) {
.comment-create { .commentCreate {
.section__actions { .section__actions {
.button { .button {
background-color: var(--color-header-button); background-color: var(--color-header-button);

View file

@ -358,7 +358,7 @@
max-width: unset; max-width: unset;
} }
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
width: 40%; width: 40%;
.button, .button,
@ -375,7 +375,7 @@
} }
.settings__row--value--multirow { .settings__row--value--multirow {
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
width: 80%; width: 80%;
margin-top: var(--spacing-l); margin-top: var(--spacing-l);
@ -389,7 +389,7 @@
} }
.settings__row--value--vertical-separator { .settings__row--value--vertical-separator {
@media not all and (max-width: $breakpoint-medium) { @media (min-width: $breakpoint-medium) {
border-left: 1px solid var(--color-border); border-left: 1px solid var(--color-border);
} }
} }

View file

@ -95,7 +95,7 @@ let baseConfig = {
}, },
plugins: [ plugins: [
new webpack.IgnorePlugin({resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/}), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
new webpack.EnvironmentPlugin(['NODE_ENV']), new webpack.EnvironmentPlugin(['NODE_ENV']),
new DefinePlugin({ new DefinePlugin({
__static: `"${path.join(__dirname, 'static').replace(/\\/g, '\\\\')}"`, __static: `"${path.join(__dirname, 'static').replace(/\\/g, '\\\\')}"`,

175
yarn.lock
View file

@ -3080,24 +3080,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@videojs/http-streaming@npm:2.15.0":
version: 2.15.0
resolution: "@videojs/http-streaming@npm:2.15.0"
dependencies:
"@babel/runtime": ^7.12.5
"@videojs/vhs-utils": 3.0.5
aes-decrypter: 3.1.3
global: ^4.4.0
m3u8-parser: 4.8.0
mpd-parser: 0.22.0
mux.js: 6.0.1
video.js: ^6 || ^7
peerDependencies:
video.js: ^6 || ^7
checksum: 3b04c78c42532419216abb0eae685bfa873aeea77a015875d1cfcc0e5f4eda7da3868e6362320b2843562ba735f728a9b7895384d01c673bc3845e3ef7ac3452
languageName: node
linkType: hard
"@videojs/vhs-utils@npm:3.0.5, @videojs/vhs-utils@npm:^3.0.4, @videojs/vhs-utils@npm:^3.0.5": "@videojs/vhs-utils@npm:3.0.5, @videojs/vhs-utils@npm:^3.0.4, @videojs/vhs-utils@npm:^3.0.5":
version: 3.0.5 version: 3.0.5
resolution: "@videojs/vhs-utils@npm:3.0.5" resolution: "@videojs/vhs-utils@npm:3.0.5"
@ -3309,9 +3291,9 @@ __metadata:
linkType: hard linkType: hard
"@xmldom/xmldom@npm:^0.7.2": "@xmldom/xmldom@npm:^0.7.2":
version: 0.7.6 version: 0.7.5
resolution: "@xmldom/xmldom@npm:0.7.6" resolution: "@xmldom/xmldom@npm:0.7.5"
checksum: 3c31dcd909aaefd65090033bd45e0aca42d636a9ae43c7313f4be87de570d046abba28362810d63c0718d6f08a9ce5f8da27b87437afc24d2290b6d4e75a6eee checksum: 8d7ec35c1ef6183b4f621df08e01d7e61f244fb964a4719025e65fe6ac06fac418919be64fb40fe5908e69158ef728f2d936daa082db326fe04603012b5f2a84
languageName: node languageName: node
linkType: hard linkType: hard
@ -3857,8 +3839,8 @@ __metadata:
linkType: hard linkType: hard
"asar@npm:^3.0.3": "asar@npm:^3.0.3":
version: 3.2.0 version: 3.1.0
resolution: "asar@npm:3.2.0" resolution: "asar@npm:3.1.0"
dependencies: dependencies:
"@types/glob": ^7.1.1 "@types/glob": ^7.1.1
chromium-pickle-js: ^0.2.0 chromium-pickle-js: ^0.2.0
@ -3870,7 +3852,7 @@ __metadata:
optional: true optional: true
bin: bin:
asar: bin/asar.js asar: bin/asar.js
checksum: f7d30b45970b053252ac124230bf319459d0728d7f6dedbe2f765cd2a83792d5a716d2c3f2861ceda69372b401f335e1f46460335169eadd0e91a0904a4f5a15 checksum: facc80845639fa4f9e1d1aa40b96adbd1e8b6fee0725d287e8c8e30a69b235cd5b7131b7b09ff700da06c919dd0595b373e372c55722808f983fdb71ef0d5399
languageName: node languageName: node
linkType: hard linkType: hard
@ -5315,17 +5297,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"cliui@npm:^8.0.1":
version: 8.0.1
resolution: "cliui@npm:8.0.1"
dependencies:
string-width: ^4.2.0
strip-ansi: ^6.0.1
wrap-ansi: ^7.0.0
checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56
languageName: node
linkType: hard
"clone-deep@npm:^4.0.1": "clone-deep@npm:^4.0.1":
version: 4.0.1 version: 4.0.1
resolution: "clone-deep@npm:4.0.1" resolution: "clone-deep@npm:4.0.1"
@ -8005,21 +7976,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"execa@npm:^0.10.0":
version: 0.10.0
resolution: "execa@npm:0.10.0"
dependencies:
cross-spawn: ^6.0.0
get-stream: ^3.0.0
is-stream: ^1.1.0
npm-run-path: ^2.0.0
p-finally: ^1.0.0
signal-exit: ^3.0.0
strip-eof: ^1.0.0
checksum: da132af2b209e69d79f91751ac6d15ddbb8d9414f9e5f7a53405232679a3dca00fe11eb14e0cd5c2c374a749061410a7717fcc3094f6dd779cf4d259faa58d9a
languageName: node
linkType: hard
"execa@npm:^0.7.0": "execa@npm:^0.7.0":
version: 0.7.0 version: 0.7.0
resolution: "execa@npm:0.7.0" resolution: "execa@npm:0.7.0"
@ -8334,15 +8290,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ffmpeg-probe@npm:^1.0.6":
version: 1.0.6
resolution: "ffmpeg-probe@npm:1.0.6"
dependencies:
execa: ^0.10.0
checksum: fe649b2ca41bd48b521d7cc5741663d4c608d7bc596033ee9c76d4c3f5e739881a4d421bdcfa3ea60e28301eae7a85b72cd74d6266e661bccf9aea6578fcfe3c
languageName: node
linkType: hard
"figgy-pudding@npm:^3.5.1": "figgy-pudding@npm:^3.5.1":
version: 3.5.2 version: 3.5.2
resolution: "figgy-pudding@npm:3.5.2" resolution: "figgy-pudding@npm:3.5.2"
@ -9109,11 +9056,11 @@ __metadata:
linkType: hard linkType: hard
"global-dirs@npm:^3.0.0": "global-dirs@npm:^3.0.0":
version: 3.0.1 version: 3.0.0
resolution: "global-dirs@npm:3.0.1" resolution: "global-dirs@npm:3.0.0"
dependencies: dependencies:
ini: 2.0.0 ini: 2.0.0
checksum: 70147b80261601fd40ac02a104581432325c1c47329706acd773f3a6ce99bb36d1d996038c85ccacd482ad22258ec233c586b6a91535b1a116b89663d49d6438 checksum: 953c17cf14bf6ee0e2100ae82a0d779934eed8a3ec5c94a7a4f37c5b3b592c31ea015fb9a15cf32484de13c79f4a814f3015152f3e1d65976cfbe47c1bfe4a88
languageName: node languageName: node
linkType: hard linkType: hard
@ -11485,7 +11432,6 @@ __metadata:
eslint-plugin-react-hooks: ^1.6.0 eslint-plugin-react-hooks: ^1.6.0
eslint-plugin-standard: ^4.0.1 eslint-plugin-standard: ^4.0.1
express: ^4.17.1 express: ^4.17.1
ffmpeg-probe: ^1.0.6
file-loader: ^4.2.0 file-loader: ^4.2.0
flow-bin: ^0.97.0 flow-bin: ^0.97.0
flow-typed: ^3.7.0 flow-typed: ^3.7.0
@ -12068,17 +12014,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"m3u8-parser@npm:4.8.0":
version: 4.8.0
resolution: "m3u8-parser@npm:4.8.0"
dependencies:
"@babel/runtime": ^7.12.5
"@videojs/vhs-utils": ^3.0.5
global: ^4.4.0
checksum: 850a8c5cacdbdc154d4fa18c79af77fdf45ef4afcb3d8a0507d04e8d9ddb5c0a9d18cb48c8e23be707d007c063038cdeab0d3de23100e5c02050a7aff6e2b2b8
languageName: node
linkType: hard
"make-dir@npm:^1.0.0": "make-dir@npm:^1.0.0":
version: 1.3.0 version: 1.3.0
resolution: "make-dir@npm:1.3.0" resolution: "make-dir@npm:1.3.0"
@ -12751,20 +12686,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"mpd-parser@npm:0.22.0":
version: 0.22.0
resolution: "mpd-parser@npm:0.22.0"
dependencies:
"@babel/runtime": ^7.12.5
"@videojs/vhs-utils": ^3.0.5
"@xmldom/xmldom": ^0.7.2
global: ^4.4.0
bin:
mpd-to-m3u8-json: bin/parse.js
checksum: 6266f03ec5eb9501e315ed567e70d9e1c8d114f84a8ec27b31ec5de877ab0cd80592dfb058e9abd7975734374f6f10cec1751ab8af90b577e902f0f38f868b6c
languageName: node
linkType: hard
"ms@npm:2.0.0": "ms@npm:2.0.0":
version: 2.0.0 version: 2.0.0
resolution: "ms@npm:2.0.0" resolution: "ms@npm:2.0.0"
@ -16426,7 +16347,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"semver@npm:^7.1.3, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.5": "semver@npm:^7.1.3, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5":
version: 7.3.7 version: 7.3.7
resolution: "semver@npm:7.3.7" resolution: "semver@npm:7.3.7"
dependencies: dependencies:
@ -16437,17 +16358,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"semver@npm:^7.3.4":
version: 7.3.8
resolution: "semver@npm:7.3.8"
dependencies:
lru-cache: ^6.0.0
bin:
semver: bin/semver.js
checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1
languageName: node
linkType: hard
"send@npm:0.18.0": "send@npm:0.18.0":
version: 0.18.0 version: 0.18.0
resolution: "send@npm:0.18.0" resolution: "send@npm:0.18.0"
@ -17688,20 +17598,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"terser@npm:^4.1.2": "terser@npm:^4.1.2, terser@npm:^4.6.12, terser@npm:^4.6.3":
version: 4.8.1
resolution: "terser@npm:4.8.1"
dependencies:
commander: ^2.20.0
source-map: ~0.6.1
source-map-support: ~0.5.12
bin:
terser: bin/terser
checksum: b342819bf7e82283059aaa3f22bb74deb1862d07573ba5a8947882190ad525fd9b44a15074986be083fd379c58b9a879457a330b66dcdb77b485c44267f9a55a
languageName: node
linkType: hard
"terser@npm:^4.6.12, terser@npm:^4.6.3":
version: 4.8.0 version: 4.8.0
resolution: "terser@npm:4.8.0" resolution: "terser@npm:4.8.0"
dependencies: dependencies:
@ -18816,28 +18713,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"video.js@npm:>=5.20.5": "video.js@npm:>=5.20.5, video.js@npm:^6 || ^7, video.js@npm:^7.14.3":
version: 7.21.0
resolution: "video.js@npm:7.21.0"
dependencies:
"@babel/runtime": ^7.12.5
"@videojs/http-streaming": 2.15.0
"@videojs/vhs-utils": ^3.0.4
"@videojs/xhr": 2.6.0
aes-decrypter: 3.1.3
global: ^4.4.0
keycode: ^2.2.0
m3u8-parser: 4.8.0
mpd-parser: 0.22.0
mux.js: 6.0.1
safe-json-parse: 4.0.0
videojs-font: 3.2.0
videojs-vtt.js: ^0.15.4
checksum: 9eea846bb610daca8b926ff9d0662bedf2d36063e312222c3af7e95494de09b8ce3fd0592082853e7aaaf9c92b8e37e958a41332e241376d88c4970ee717e3ab
languageName: node
linkType: hard
"video.js@npm:^6 || ^7, video.js@npm:^7.14.3":
version: 7.19.2 version: 7.19.2
resolution: "video.js@npm:7.19.2" resolution: "video.js@npm:7.19.2"
dependencies: dependencies:
@ -18895,15 +18771,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"videojs-vtt.js@npm:^0.15.4":
version: 0.15.4
resolution: "videojs-vtt.js@npm:0.15.4"
dependencies:
global: ^4.3.1
checksum: 10c6c861621d4314e7d4b60b7bef1afc60f1ac438879f6b3f22e8944d694c8e9dfc809a8187ed72f44e06c39a159044d8fa15e80695b9bf7b9bef99ea2740b70
languageName: node
linkType: hard
"villain-react@npm:^1.0.9": "villain-react@npm:^1.0.9":
version: 1.0.9 version: 1.0.9
resolution: "villain-react@npm:1.0.9" resolution: "villain-react@npm:1.0.9"
@ -19561,10 +19428,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"yargs-parser@npm:^21.1.1": "yargs-parser@npm:^21.0.0":
version: 21.1.1 version: 21.0.1
resolution: "yargs-parser@npm:21.1.1" resolution: "yargs-parser@npm:21.0.1"
checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c checksum: c3ea2ed12cad0377ce3096b3f138df8267edf7b1aa7d710cd502fe16af417bafe4443dd71b28158c22fcd1be5dfd0e86319597e47badf42ff83815485887323a
languageName: node languageName: node
linkType: hard linkType: hard
@ -19621,17 +19488,17 @@ __metadata:
linkType: hard linkType: hard
"yargs@npm:^17.0.1": "yargs@npm:^17.0.1":
version: 17.6.2 version: 17.5.1
resolution: "yargs@npm:17.6.2" resolution: "yargs@npm:17.5.1"
dependencies: dependencies:
cliui: ^8.0.1 cliui: ^7.0.2
escalade: ^3.1.1 escalade: ^3.1.1
get-caller-file: ^2.0.5 get-caller-file: ^2.0.5
require-directory: ^2.1.1 require-directory: ^2.1.1
string-width: ^4.2.3 string-width: ^4.2.3
y18n: ^5.0.5 y18n: ^5.0.5
yargs-parser: ^21.1.1 yargs-parser: ^21.0.0
checksum: 47da1b0d854fa16d45a3ded57b716b013b2179022352a5f7467409da5a04a1eef5b3b3d97a2dfc13e8bbe5f2ffc0afe3bc6a4a72f8254e60f5a4bd7947138643 checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde
languageName: node languageName: node
linkType: hard linkType: hard