Compare commits
9 commits
master
...
7681-remov
Author | SHA1 | Date | |
---|---|---|---|
|
871a933fb4 | ||
|
1d55a84c94 | ||
|
6d52763e19 | ||
|
8e5678af5b | ||
|
e3564a2d2b | ||
|
7288cc15ce | ||
|
63b19a92aa | ||
|
f822232642 | ||
|
d33856434e |
65 changed files with 406 additions and 1025 deletions
.github/workflows
.yarn/versions
CHANGELOG.mdREADME.mdbuild
electron
extras/lbryinc
flow-typed
package.jsonstatic
ui
component
channelForm
collectionEdit
collectionPreviewTile
comment
commentCreate
commentsList
common
fileThumbnail
postEditor
previewLink
publishDescription
publishFile
publishForm
selectThumbnail
viewers/videoViewer/internal
constants
index.jsxpage
reducers.jsredux
scss/component
19
.github/workflows/deploy.yml
vendored
19
.github/workflows/deploy.yml
vendored
|
@ -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
|
||||||
|
|
0
.yarn/versions/5bc94294.yml
vendored
0
.yarn/versions/5bc94294.yml
vendored
0
.yarn/versions/6b35c994.yml
vendored
0
.yarn/versions/6b35c994.yml
vendored
0
.yarn/versions/6be5ab70.yml
vendored
0
.yarn/versions/6be5ab70.yml
vendored
0
.yarn/versions/d1a18cef.yml
vendored
0
.yarn/versions/d1a18cef.yml
vendored
0
.yarn/versions/ec3a9ddf.yml
vendored
0
.yarn/versions/ec3a9ddf.yml
vendored
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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.
|
@ -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();
|
||||||
|
|
|
@ -50,18 +50,6 @@ export const GET_SUGGESTED_SUBSCRIPTIONS_FAIL = 'GET_SUGGESTED_SUBSCRIPTIONS_FAI
|
||||||
export const SUBSCRIPTION_FIRST_RUN_COMPLETED = 'SUBSCRIPTION_FIRST_RUN_COMPLETED';
|
export const SUBSCRIPTION_FIRST_RUN_COMPLETED = 'SUBSCRIPTION_FIRST_RUN_COMPLETED';
|
||||||
export const VIEW_SUGGESTED_SUBSCRIPTIONS = 'VIEW_SUGGESTED_SUBSCRIPTIONS';
|
export const VIEW_SUGGESTED_SUBSCRIPTIONS = 'VIEW_SUGGESTED_SUBSCRIPTIONS';
|
||||||
|
|
||||||
// Blacklist
|
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED';
|
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_FAILED = 'FETCH_BLACK_LISTED_CONTENT_FAILED';
|
|
||||||
export const BLACK_LISTED_CONTENT_SUBSCRIBE = 'BLACK_LISTED_CONTENT_SUBSCRIBE';
|
|
||||||
|
|
||||||
// Filtered list
|
|
||||||
export const FETCH_FILTERED_CONTENT_STARTED = 'FETCH_FILTERED_CONTENT_STARTED';
|
|
||||||
export const FETCH_FILTERED_CONTENT_COMPLETED = 'FETCH_FILTERED_CONTENT_COMPLETED';
|
|
||||||
export const FETCH_FILTERED_CONTENT_FAILED = 'FETCH_FILTERED_CONTENT_FAILED';
|
|
||||||
export const FILTERED_CONTENT_SUBSCRIBE = 'FILTERED_CONTENT_SUBSCRIBE';
|
|
||||||
|
|
||||||
// Cost Info
|
// Cost Info
|
||||||
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
|
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
|
||||||
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
|
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
|
||||||
|
|
|
@ -14,8 +14,6 @@ export { doTransifexUpload } from 'util/transifex-upload';
|
||||||
// actions
|
// actions
|
||||||
export { doGenerateAuthToken } from './redux/actions/auth';
|
export { doGenerateAuthToken } from './redux/actions/auth';
|
||||||
export { doFetchCostInfoForUri } from './redux/actions/cost_info';
|
export { doFetchCostInfoForUri } from './redux/actions/cost_info';
|
||||||
export { doBlackListedOutpointsSubscribe } from './redux/actions/blacklist';
|
|
||||||
export { doFilteredOutpointsSubscribe } from './redux/actions/filtered';
|
|
||||||
export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats';
|
export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats';
|
||||||
export {
|
export {
|
||||||
doCheckSync,
|
doCheckSync,
|
||||||
|
@ -30,8 +28,6 @@ export {
|
||||||
// reducers
|
// reducers
|
||||||
export { authReducer } from './redux/reducers/auth';
|
export { authReducer } from './redux/reducers/auth';
|
||||||
export { costInfoReducer } from './redux/reducers/cost_info';
|
export { costInfoReducer } from './redux/reducers/cost_info';
|
||||||
export { blacklistReducer } from './redux/reducers/blacklist';
|
|
||||||
export { filteredReducer } from './redux/reducers/filtered';
|
|
||||||
export { statsReducer } from './redux/reducers/stats';
|
export { statsReducer } from './redux/reducers/stats';
|
||||||
export { syncReducer } from './redux/reducers/sync';
|
export { syncReducer } from './redux/reducers/sync';
|
||||||
|
|
||||||
|
@ -43,17 +39,10 @@ export {
|
||||||
selectAllCostInfoByUri,
|
selectAllCostInfoByUri,
|
||||||
selectFetchingCostInfo,
|
selectFetchingCostInfo,
|
||||||
} from './redux/selectors/cost_info';
|
} from './redux/selectors/cost_info';
|
||||||
export {
|
|
||||||
selectBlackListedOutpoints,
|
|
||||||
selectBlacklistedOutpointMap,
|
|
||||||
} from './redux/selectors/blacklist';
|
|
||||||
export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/selectors/filtered';
|
|
||||||
export {
|
export {
|
||||||
selectViewCount,
|
selectViewCount,
|
||||||
selectViewCountForUri,
|
selectViewCountForUri,
|
||||||
// makeSelectViewCountForUri, // deprecated
|
|
||||||
selectSubCountForUri,
|
selectSubCountForUri,
|
||||||
// makeSelectSubCountForUri, // deprecated
|
|
||||||
} from './redux/selectors/stats';
|
} from './redux/selectors/stats';
|
||||||
export { selectBanStateForUri } from './redux/selectors/ban';
|
export { selectBanStateForUri } from './redux/selectors/ban';
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import { Lbryio } from 'lbryinc';
|
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
|
|
||||||
const CHECK_BLACK_LISTED_CONTENT_INTERVAL = 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export function doFetchBlackListedOutpoints() {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED,
|
|
||||||
});
|
|
||||||
|
|
||||||
const success = ({ outpoints }) => {
|
|
||||||
const splitOutpoints = [];
|
|
||||||
if (outpoints) {
|
|
||||||
outpoints.forEach((outpoint, index) => {
|
|
||||||
const [txid, nout] = outpoint.split(':');
|
|
||||||
|
|
||||||
splitOutpoints[index] = { txid, nout: Number.parseInt(nout, 10) };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED,
|
|
||||||
data: {
|
|
||||||
outpoints: splitOutpoints,
|
|
||||||
success: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const failure = ({ message: error }) => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED,
|
|
||||||
data: {
|
|
||||||
error,
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Lbryio.call('file', 'list_blocked', {
|
|
||||||
auth_token: '',
|
|
||||||
}).then(success, failure);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doBlackListedOutpointsSubscribe() {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch(doFetchBlackListedOutpoints());
|
|
||||||
setInterval(() => dispatch(doFetchBlackListedOutpoints()), CHECK_BLACK_LISTED_CONTENT_INTERVAL);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { Lbryio } from 'lbryinc';
|
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
|
|
||||||
const CHECK_FILTERED_CONTENT_INTERVAL = 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export function doFetchFilteredOutpoints() {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_FILTERED_CONTENT_STARTED,
|
|
||||||
});
|
|
||||||
|
|
||||||
const success = ({ outpoints }) => {
|
|
||||||
let formattedOutpoints = [];
|
|
||||||
if (outpoints) {
|
|
||||||
formattedOutpoints = outpoints.map(outpoint => {
|
|
||||||
const [txid, nout] = outpoint.split(':');
|
|
||||||
return { txid, nout: Number.parseInt(nout, 10) };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED,
|
|
||||||
data: {
|
|
||||||
outpoints: formattedOutpoints,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const failure = ({ error }) => {
|
|
||||||
dispatch({
|
|
||||||
type: ACTIONS.FETCH_FILTERED_CONTENT_FAILED,
|
|
||||||
data: {
|
|
||||||
error,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Lbryio.call('file', 'list_filtered', { auth_token: '' }).then(success, failure);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function doFilteredOutpointsSubscribe() {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch(doFetchFilteredOutpoints());
|
|
||||||
setInterval(() => dispatch(doFetchFilteredOutpoints()), CHECK_FILTERED_CONTENT_INTERVAL);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
import { handleActions } from 'util/redux-utils';
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
fetchingBlackListedOutpoints: false,
|
|
||||||
fetchingBlackListedOutpointsSucceed: undefined,
|
|
||||||
blackListedOutpoints: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const blacklistReducer = handleActions(
|
|
||||||
{
|
|
||||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED]: state => ({
|
|
||||||
...state,
|
|
||||||
fetchingBlackListedOutpoints: true,
|
|
||||||
}),
|
|
||||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED]: (state, action) => {
|
|
||||||
const { outpoints, success } = action.data;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchingBlackListedOutpoints: false,
|
|
||||||
fetchingBlackListedOutpointsSucceed: success,
|
|
||||||
blackListedOutpoints: outpoints,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED]: (state, action) => {
|
|
||||||
const { error, success } = action.data;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
fetchingBlackListedOutpoints: false,
|
|
||||||
fetchingBlackListedOutpointsSucceed: success,
|
|
||||||
fetchingBlackListedOutpointsError: error,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultState
|
|
||||||
);
|
|
|
@ -1,34 +0,0 @@
|
||||||
import * as ACTIONS from 'constants/action_types';
|
|
||||||
import { handleActions } from 'util/redux-utils';
|
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
loading: false,
|
|
||||||
filteredOutpoints: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const filteredReducer = handleActions(
|
|
||||||
{
|
|
||||||
[ACTIONS.FETCH_FILTERED_CONTENT_STARTED]: state => ({
|
|
||||||
...state,
|
|
||||||
loading: true,
|
|
||||||
}),
|
|
||||||
[ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED]: (state, action) => {
|
|
||||||
const { outpoints } = action.data;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
loading: false,
|
|
||||||
filteredOutpoints: outpoints,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[ACTIONS.FETCH_FILTERED_CONTENT_FAILED]: (state, action) => {
|
|
||||||
const { error } = action.data;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
loading: false,
|
|
||||||
fetchingFilteredOutpointsError: error,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultState
|
|
||||||
);
|
|
|
@ -8,18 +8,15 @@ import { createCachedSelector } from 're-reselect';
|
||||||
import { selectClaimForUri, makeSelectIsBlacklisted } from 'redux/selectors/claims';
|
import { selectClaimForUri, makeSelectIsBlacklisted } from 'redux/selectors/claims';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectModerationBlockList } from 'redux/selectors/comments';
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
|
|
||||||
import { getChannelFromClaim } from 'util/claim';
|
import { getChannelFromClaim } from 'util/claim';
|
||||||
import { isURIEqual } from 'util/lbryURI';
|
import { isURIEqual } from 'util/lbryURI';
|
||||||
|
|
||||||
export const selectBanStateForUri = createCachedSelector(
|
export const selectBanStateForUri = createCachedSelector(
|
||||||
selectClaimForUri,
|
selectClaimForUri,
|
||||||
selectBlacklistedOutpointMap,
|
|
||||||
selectFilteredOutpointMap,
|
|
||||||
selectMutedChannels,
|
selectMutedChannels,
|
||||||
selectModerationBlockList,
|
selectModerationBlockList,
|
||||||
(state, uri) => makeSelectIsBlacklisted(uri)(state),
|
(state, uri) => makeSelectIsBlacklisted(uri)(state),
|
||||||
(claim, blackListedOutpointMap, filteredOutpointMap, mutedChannelUris, personalBlocklist, isBlacklisted) => {
|
(claim, mutedChannelUris, personalBlocklist, isBlacklisted) => {
|
||||||
const banState = {};
|
const banState = {};
|
||||||
|
|
||||||
if (!claim) {
|
if (!claim) {
|
||||||
|
@ -32,27 +29,6 @@ export const selectBanStateForUri = createCachedSelector(
|
||||||
banState['blacklisted'] = true;
|
banState['blacklisted'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will be replaced once blocking is done at the wallet server level.
|
|
||||||
if (blackListedOutpointMap) {
|
|
||||||
if (
|
|
||||||
(channelClaim && blackListedOutpointMap[`${channelClaim.txid}:${channelClaim.nout}`]) ||
|
|
||||||
blackListedOutpointMap[`${claim.txid}:${claim.nout}`]
|
|
||||||
) {
|
|
||||||
banState['blacklisted'] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're checking to see if the stream outpoint or signing channel outpoint
|
|
||||||
// is in the filter list.
|
|
||||||
if (filteredOutpointMap) {
|
|
||||||
if (
|
|
||||||
(channelClaim && filteredOutpointMap[`${channelClaim.txid}:${channelClaim.nout}`]) ||
|
|
||||||
filteredOutpointMap[`${claim.txid}:${claim.nout}`]
|
|
||||||
) {
|
|
||||||
banState['filtered'] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// block stream claims
|
// block stream claims
|
||||||
// block channel claims if we can't control for them in claim search
|
// block channel claims if we can't control for them in claim search
|
||||||
if (mutedChannelUris.length && channelClaim) {
|
if (mutedChannelUris.length && channelClaim) {
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
export const selectState = state => state.blacklist || {};
|
|
||||||
|
|
||||||
export const selectBlackListedOutpoints = createSelector(
|
|
||||||
selectState,
|
|
||||||
state => state.blackListedOutpoints
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectBlacklistedOutpointMap = createSelector(
|
|
||||||
selectBlackListedOutpoints,
|
|
||||||
outpoints =>
|
|
||||||
outpoints
|
|
||||||
? outpoints.reduce((acc, val) => {
|
|
||||||
const outpoint = `${val.txid}:${val.nout}`;
|
|
||||||
acc[outpoint] = 1;
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
: {}
|
|
||||||
);
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
export const selectState = state => state.filtered || {};
|
|
||||||
|
|
||||||
export const selectFilteredOutpoints = createSelector(
|
|
||||||
selectState,
|
|
||||||
state => state.filteredOutpoints
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFilteredOutpointMap = createSelector(
|
|
||||||
selectFilteredOutpoints,
|
|
||||||
outpoints =>
|
|
||||||
outpoints
|
|
||||||
? outpoints.reduce((acc, val) => {
|
|
||||||
const outpoint = `${val.txid}:${val.nout}`;
|
|
||||||
acc[outpoint] = 1;
|
|
||||||
return acc;
|
|
||||||
}, {})
|
|
||||||
: {}
|
|
||||||
);
|
|
1
flow-typed/Claim.js
vendored
1
flow-typed/Claim.js
vendored
|
@ -167,6 +167,7 @@ declare type ClaimErrorCensor = {
|
||||||
take_over_height: number,
|
take_over_height: number,
|
||||||
},
|
},
|
||||||
name: string,
|
name: string,
|
||||||
|
text: string,
|
||||||
normalized_name: string,
|
normalized_name: string,
|
||||||
nout: number,
|
nout: number,
|
||||||
permanent_url: string,
|
permanent_url: string,
|
||||||
|
|
11
flow-typed/Lbry.js
vendored
11
flow-typed/Lbry.js
vendored
|
@ -75,7 +75,16 @@ declare type BalanceResponse = {
|
||||||
|
|
||||||
declare type ResolveResponse = {
|
declare type ResolveResponse = {
|
||||||
// Keys are the url(s) passed to resolve
|
// Keys are the url(s) passed to resolve
|
||||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, collection?: CollectionClaim, claimsInChannel?: number },
|
[string]: {
|
||||||
|
error?: {
|
||||||
|
censor?: {},
|
||||||
|
text?: string,
|
||||||
|
},
|
||||||
|
stream?: StreamClaim,
|
||||||
|
channel?: ChannelClaim,
|
||||||
|
collection?: CollectionClaim,
|
||||||
|
claimsInChannel?: number
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type GetResponse = FileListItem & { error?: string };
|
declare type GetResponse = FileListItem & { error?: string };
|
||||||
|
|
10
flow-typed/file-data.js
vendored
10
flow-typed/file-data.js
vendored
|
@ -1,10 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
declare type FileData = {
|
|
||||||
file?: Blob,
|
|
||||||
path: string,
|
|
||||||
duration?: number,
|
|
||||||
size?: number,
|
|
||||||
mimeType: string,
|
|
||||||
error?: string,
|
|
||||||
}
|
|
7
flow-typed/npm/lbryinc_vx.x.x.js
vendored
7
flow-typed/npm/lbryinc_vx.x.x.js
vendored
|
@ -82,10 +82,6 @@ declare module 'lbryinc/src/redux/reducers/auth' {
|
||||||
declare module.exports: any;
|
declare module.exports: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'lbryinc/src/redux/reducers/blacklist' {
|
|
||||||
declare module.exports: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'lbryinc/src/redux/reducers/cost_info' {
|
declare module 'lbryinc/src/redux/reducers/cost_info' {
|
||||||
declare module.exports: any;
|
declare module.exports: any;
|
||||||
}
|
}
|
||||||
|
@ -212,9 +208,6 @@ declare module 'lbryinc/src/redux/actions/user.js' {
|
||||||
declare module 'lbryinc/src/redux/reducers/auth.js' {
|
declare module 'lbryinc/src/redux/reducers/auth.js' {
|
||||||
declare module.exports: $Exports<'lbryinc/src/redux/reducers/auth'>;
|
declare module.exports: $Exports<'lbryinc/src/redux/reducers/auth'>;
|
||||||
}
|
}
|
||||||
declare module 'lbryinc/src/redux/reducers/blacklist.js' {
|
|
||||||
declare module.exports: $Exports<'lbryinc/src/redux/reducers/blacklist'>;
|
|
||||||
}
|
|
||||||
declare module 'lbryinc/src/redux/reducers/cost_info.js' {
|
declare module 'lbryinc/src/redux/reducers/cost_info.js' {
|
||||||
declare module.exports: $Exports<'lbryinc/src/redux/reducers/cost_info'>;
|
declare module.exports: $Exports<'lbryinc/src/redux/reducers/cost_info'>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -2319,8 +2319,8 @@
|
||||||
"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.",
|
"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.",
|
"Content Blocked": "Content Blocked",
|
||||||
"Anon --[used in <%anonymous% Reposted>]--": "Anon",
|
"Your hub has blocked %content% because it subscribes to the following blocking channel:": "Your hub has blocked %content% because it subscribes to the following blocking channel:",
|
||||||
"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.",
|
"Your hub has blocked %content% because it subscribes to %channel%. You can change your hub in %settings%.": "Your hub has blocked %content% because it subscribes to %channel%. You can change your hub in %settings%.",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')}
|
||||||
|
|
|
@ -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')}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import {
|
||||||
import { doFetchItemsInCollection, doCollectionDelete } from 'redux/actions/collections';
|
import { doFetchItemsInCollection, doCollectionDelete } from 'redux/actions/collections';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import CollectionPreviewTile from './view';
|
import CollectionPreviewTile from './view';
|
||||||
|
|
||||||
|
@ -42,8 +41,6 @@ const select = (state, props) => {
|
||||||
isResolvingUri: collectionUri && selectIsUriResolving(state, collectionUri),
|
isResolvingUri: collectionUri && selectIsUriResolving(state, collectionUri),
|
||||||
thumbnail: getThumbnailFromClaim(claim),
|
thumbnail: getThumbnailFromClaim(claim),
|
||||||
title: collectionUri && selectTitleForUri(state, collectionUri),
|
title: collectionUri && selectTitleForUri(state, collectionUri),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
|
||||||
filteredOutpoints: selectFilteredOutpoints(state),
|
|
||||||
blockedChannelUris: selectMutedChannels(state),
|
blockedChannelUris: selectMutedChannels(state),
|
||||||
showMature: selectShowMatureContent(state),
|
showMature: selectShowMatureContent(state),
|
||||||
isMature: makeSelectClaimIsNsfw(collectionUri)(state),
|
isMature: makeSelectClaimIsNsfw(collectionUri)(state),
|
||||||
|
|
|
@ -25,14 +25,6 @@ type Props = {
|
||||||
thumbnail?: string,
|
thumbnail?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
placeholder: boolean,
|
placeholder: boolean,
|
||||||
blackListedOutpoints: Array<{
|
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
filteredOutpoints: Array<{
|
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
blockedChannelUris: Array<string>,
|
blockedChannelUris: Array<string>,
|
||||||
isMature?: boolean,
|
isMature?: boolean,
|
||||||
showMature: boolean,
|
showMature: boolean,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -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:
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import {
|
||||||
makeSelectMetadataItemForUri,
|
makeSelectMetadataItemForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
|
||||||
import PreviewLink from './view';
|
import PreviewLink from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -22,7 +21,6 @@ const select = (state, props) => {
|
||||||
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
|
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
|
||||||
channelIsMine: selectClaimIsMine(state, claim),
|
channelIsMine: selectClaimIsMine(state, claim),
|
||||||
isResolvingUri: selectIsUriResolving(state, props.uri),
|
isResolvingUri: selectIsUriResolving(state, props.uri),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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')}
|
||||||
|
|
|
@ -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) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) &&
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import eventTracking from 'videojs-event-tracking';
|
||||||
import * as OVERLAY from './overlays';
|
import * as OVERLAY from './overlays';
|
||||||
import './plugins/videojs-mobile-ui/plugin';
|
import './plugins/videojs-mobile-ui/plugin';
|
||||||
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
import hlsQualitySelector from './plugins/videojs-hls-quality-selector/plugin';
|
||||||
import recsys from './plugins/videojs-recsys/plugin';
|
// import recsys from './plugins/videojs-recsys/plugin'; // points to view tracking plugin
|
||||||
import qualityLevels from 'videojs-contrib-quality-levels';
|
import qualityLevels from 'videojs-contrib-quality-levels';
|
||||||
import LbryVolumeBarClass from './lbry-volume-bar';
|
import LbryVolumeBarClass from './lbry-volume-bar';
|
||||||
import keyboardShorcuts from './videojs-keyboard-shortcuts';
|
import keyboardShorcuts from './videojs-keyboard-shortcuts';
|
||||||
|
@ -92,9 +92,9 @@ if (!Object.keys(videojs.getPlugins()).includes('qualityLevels')) {
|
||||||
videojs.registerPlugin('qualityLevels', qualityLevels);
|
videojs.registerPlugin('qualityLevels', qualityLevels);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
|
// if (!Object.keys(videojs.getPlugins()).includes('recsys')) {
|
||||||
videojs.registerPlugin('recsys', recsys);
|
// videojs.registerPlugin('recsys', recsys);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// VideoJs
|
// VideoJs
|
||||||
|
|
|
@ -470,18 +470,6 @@ export const REPORT_CONTENT_STARTED = 'REPORT_CONTENT_STARTED';
|
||||||
export const REPORT_CONTENT_COMPLETED = 'REPORT_CONTENT_COMPLETED';
|
export const REPORT_CONTENT_COMPLETED = 'REPORT_CONTENT_COMPLETED';
|
||||||
export const REPORT_CONTENT_FAILED = 'REPORT_CONTENT_FAILED';
|
export const REPORT_CONTENT_FAILED = 'REPORT_CONTENT_FAILED';
|
||||||
|
|
||||||
// Blacklist
|
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED';
|
|
||||||
export const FETCH_BLACK_LISTED_CONTENT_FAILED = 'FETCH_BLACK_LISTED_CONTENT_FAILED';
|
|
||||||
export const BLACK_LISTED_CONTENT_SUBSCRIBE = 'BLACK_LISTED_CONTENT_SUBSCRIBE';
|
|
||||||
|
|
||||||
// Filtered list
|
|
||||||
export const FETCH_FILTERED_CONTENT_STARTED = 'FETCH_FILTERED_CONTENT_STARTED';
|
|
||||||
export const FETCH_FILTERED_CONTENT_COMPLETED = 'FETCH_FILTERED_CONTENT_COMPLETED';
|
|
||||||
export const FETCH_FILTERED_CONTENT_FAILED = 'FETCH_FILTERED_CONTENT_FAILED';
|
|
||||||
export const FILTERED_CONTENT_SUBSCRIBE = 'FILTERED_CONTENT_SUBSCRIBE';
|
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
export const FETCH_VIEW_COUNT_STARTED = 'FETCH_VIEW_COUNT_STARTED';
|
export const FETCH_VIEW_COUNT_STARTED = 'FETCH_VIEW_COUNT_STARTED';
|
||||||
export const FETCH_VIEW_COUNT_FAILED = 'FETCH_VIEW_COUNT_FAILED';
|
export const FETCH_VIEW_COUNT_FAILED = 'FETCH_VIEW_COUNT_FAILED';
|
||||||
|
|
|
@ -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';
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
import { isURIValid } from 'util/lbryURI';
|
import { isURIValid } from 'util/lbryURI';
|
||||||
import { setSearchApi } from 'redux/actions/search';
|
import { setSearchApi } from 'redux/actions/search';
|
||||||
import { doSetLanguage, doFetchLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
|
import { doSetLanguage, doFetchLanguage, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||||
import { Lbryio, doBlackListedOutpointsSubscribe, doFilteredOutpointsSubscribe } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import rewards from 'rewards';
|
import rewards from 'rewards';
|
||||||
import { store, persistor, history } from 'store';
|
import { store, persistor, history } from 'store';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
|
@ -274,8 +274,6 @@ function AppWrapper() {
|
||||||
}
|
}
|
||||||
app.store.dispatch(doUpdateIsNightAsync());
|
app.store.dispatch(doUpdateIsNightAsync());
|
||||||
app.store.dispatch(doDaemonReady());
|
app.store.dispatch(doDaemonReady());
|
||||||
app.store.dispatch(doBlackListedOutpointsSubscribe());
|
|
||||||
app.store.dispatch(doFilteredOutpointsSubscribe());
|
|
||||||
|
|
||||||
const appReadyTime = Date.now();
|
const appReadyTime = Date.now();
|
||||||
const timeToStart = appReadyTime - startTime;
|
const timeToStart = appReadyTime - startTime;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
makeSelectClaimIsPending,
|
makeSelectClaimIsPending,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { selectMyUnpublishedCollections } from 'redux/selectors/collections';
|
import { selectMyUnpublishedCollections } from 'redux/selectors/collections';
|
||||||
import { selectBlackListedOutpoints, doFetchSubCount, selectSubCountForUri } from 'lbryinc'; // ban state
|
import { doFetchSubCount, selectSubCountForUri } from 'lbryinc'; // ban state
|
||||||
import { selectYoutubeChannels } from 'redux/selectors/user';
|
import { selectYoutubeChannels } from 'redux/selectors/user';
|
||||||
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
|
import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
|
||||||
import { selectModerationBlockList } from 'redux/selectors/comments';
|
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
|
@ -28,7 +28,6 @@ const select = (state, props) => {
|
||||||
page: selectCurrentChannelPage(state),
|
page: selectCurrentChannelPage(state),
|
||||||
claim,
|
claim,
|
||||||
isSubscribed: selectIsSubscribedForUri(state, props.uri),
|
isSubscribed: selectIsSubscribedForUri(state, props.uri),
|
||||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
|
||||||
subCount: selectSubCountForUri(state, props.uri),
|
subCount: selectSubCountForUri(state, props.uri),
|
||||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||||
youtubeChannels: selectYoutubeChannels(state),
|
youtubeChannels: selectYoutubeChannels(state),
|
||||||
|
|
|
@ -50,10 +50,6 @@ type Props = {
|
||||||
channelIsMine: boolean,
|
channelIsMine: boolean,
|
||||||
isSubscribed: boolean,
|
isSubscribed: boolean,
|
||||||
channelIsBlocked: boolean,
|
channelIsBlocked: boolean,
|
||||||
blackListedOutpoints: Array<{
|
|
||||||
txid: string,
|
|
||||||
nout: number,
|
|
||||||
}>,
|
|
||||||
fetchSubCount: (string) => void,
|
fetchSubCount: (string) => void,
|
||||||
subCount: number,
|
subCount: number,
|
||||||
pending: boolean,
|
pending: boolean,
|
||||||
|
@ -72,7 +68,6 @@ function ChannelPage(props: Props) {
|
||||||
// page, ?page= may come back some day?
|
// page, ?page= may come back some day?
|
||||||
channelIsMine,
|
channelIsMine,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
blackListedOutpoints,
|
|
||||||
fetchSubCount,
|
fetchSubCount,
|
||||||
subCount,
|
subCount,
|
||||||
pending,
|
pending,
|
||||||
|
@ -135,14 +130,6 @@ function ChannelPage(props: Props) {
|
||||||
collectionEmpty = <section className="main--empty">{__('No Lists Found')}</section>;
|
collectionEmpty = <section className="main--empty">{__('No Lists Found')}</section>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let channelIsBlackListed = false;
|
|
||||||
|
|
||||||
if (claim && blackListedOutpoints) {
|
|
||||||
channelIsBlackListed = blackListedOutpoints.some(
|
|
||||||
(outpoint) => outpoint.txid === claim.txid && outpoint.nout === claim.nout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a user changes tabs, update the url so it stays on the same page if they refresh.
|
// If a user changes tabs, update the url so it stays on the same page if they refresh.
|
||||||
// We don't want to use links here because we can't animate the tab change and using links
|
// We don't want to use links here because we can't animate the tab change and using links
|
||||||
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
|
// would alter the Tab label's role attribute, which should stay role="tab" to work with keyboards/screen readers.
|
||||||
|
@ -219,9 +206,9 @@ function ChannelPage(props: Props) {
|
||||||
navigate={`/$/${PAGES.CHANNELS}`}
|
navigate={`/$/${PAGES.CHANNELS}`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!channelIsBlackListed && <ShareButton uri={uri} />}
|
<ShareButton uri={uri} />
|
||||||
{!(isBlocked || isMuted) && <ClaimSupportButton uri={uri} />}
|
{!(isBlocked || isMuted) && <ClaimSupportButton uri={uri} />}
|
||||||
{!(isBlocked || isMuted) && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
|
{!(isBlocked || isMuted) && isSubscribed && <SubscribeButton uri={permanentUrl} />}
|
||||||
{/* TODO: add channel collections <ClaimCollectionAddButton uri={uri} fileAction /> */}
|
{/* TODO: add channel collections <ClaimCollectionAddButton uri={uri} fileAction /> */}
|
||||||
<ClaimMenuList uri={claim.permanent_url} inline isChannelPage />
|
<ClaimMenuList uri={claim.permanent_url} inline isChannelPage />
|
||||||
</div>
|
</div>
|
||||||
|
@ -293,7 +280,6 @@ function ChannelPage(props: Props) {
|
||||||
{currentView === PAGE.CONTENT && (
|
{currentView === PAGE.CONTENT && (
|
||||||
<ChannelContent
|
<ChannelContent
|
||||||
uri={uri}
|
uri={uri}
|
||||||
channelIsBlackListed={channelIsBlackListed}
|
|
||||||
viewHiddenChannels
|
viewHiddenChannels
|
||||||
claimType={['stream', 'repost']}
|
claimType={['stream', 'repost']}
|
||||||
empty={<section className="main--empty">{__('No Content Found')}</section>}
|
empty={<section className="main--empty">{__('No Content Found')}</section>}
|
||||||
|
@ -302,13 +288,7 @@ function ChannelPage(props: Props) {
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
{currentView === PAGE.LISTS && (
|
{currentView === PAGE.LISTS && (
|
||||||
<ChannelContent
|
<ChannelContent claimType={'collection'} uri={uri} viewHiddenChannels empty={collectionEmpty} />
|
||||||
claimType={'collection'}
|
|
||||||
uri={uri}
|
|
||||||
channelIsBlackListed={channelIsBlackListed}
|
|
||||||
viewHiddenChannels
|
|
||||||
empty={collectionEmpty}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
|
|
@ -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' : ''}`}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Redirect, useHistory } from 'react-router-dom';
|
import { Redirect, useHistory } from 'react-router-dom';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
import ChannelPage from 'page/channel';
|
import ChannelPage from 'page/channel';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Card from 'component/common/card';
|
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { parseURI } from 'util/lbryURI';
|
import { parseURI } from 'util/lbryURI';
|
||||||
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||||
|
@ -117,6 +117,7 @@ function ShowPage(props: Props) {
|
||||||
{!isResolvingUri && !isSubscribed && (
|
{!isResolvingUri && !isSubscribed && (
|
||||||
<div className="main--empty">
|
<div className="main--empty">
|
||||||
<Yrbl
|
<Yrbl
|
||||||
|
type={'sad'}
|
||||||
title={isChannel ? __('Channel Not Found') : __('No Content Found')}
|
title={isChannel ? __('Channel Not Found') : __('No Content Found')}
|
||||||
subtitle={
|
subtitle={
|
||||||
isChannel ? (
|
isChannel ? (
|
||||||
|
@ -147,33 +148,49 @@ function ShowPage(props: Props) {
|
||||||
} else if (isBlacklisted && !claimIsMine) {
|
} else if (isBlacklisted && !claimIsMine) {
|
||||||
innerContent = isBlacklistedDueToDMCA ? (
|
innerContent = isBlacklistedDueToDMCA ? (
|
||||||
<Page>
|
<Page>
|
||||||
<Card
|
<div className="main--empty">
|
||||||
title={uri}
|
<Yrbl
|
||||||
subtitle={__(
|
title={isChannel ? __('Channel Not Found') : __('No Content Found')}
|
||||||
'Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.'
|
subtitle={__(
|
||||||
)}
|
'Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.'
|
||||||
actions={
|
)}
|
||||||
<div className="section__actions">
|
actions={
|
||||||
<Button button="link" href="https://lbry.com/faq/dmca" label={__('Read More')} />
|
<div className="section__actions">
|
||||||
</div>
|
<Button button="link" href="https://lbry.com/faq/dmca" label={__('Read More')} />
|
||||||
}
|
</div>
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
) : (
|
) : (
|
||||||
<Page>
|
<Page>
|
||||||
<Card
|
<div className="main--empty">
|
||||||
title={uri}
|
<Yrbl
|
||||||
subtitle={
|
type={'sad'}
|
||||||
<>
|
title={__('Content Blocked')}
|
||||||
{__('Your hub has blocked this content because it subscribes to the following blocking channel:')}{' '}
|
subtitle={
|
||||||
<Button
|
<div>
|
||||||
button="link"
|
<I18nMessage
|
||||||
navigate={errorCensor && errorCensor.canonical_url}
|
tokens={{
|
||||||
label={errorCensor && errorCensor.name}
|
content: uri,
|
||||||
/>
|
settings: <Button button="link" label={__('Settings')} navigate={`/$/${PAGES.SETTINGS}`} />,
|
||||||
</>
|
channel: (
|
||||||
}
|
<Button
|
||||||
/>
|
button="link"
|
||||||
|
navigate={errorCensor && errorCensor.canonical_url}
|
||||||
|
label={errorCensor && errorCensor.name}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Your hub has blocked %content% because it subscribes to %channel%. You can change your hub in
|
||||||
|
%settings%.
|
||||||
|
</I18nMessage>
|
||||||
|
{/* {errorCensor && <p>{`Message:\n${errorCensor.text}`}</p>} */}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
} else if (claim) {
|
} else if (claim) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { connectRouter } from 'connected-react-router';
|
import { connectRouter } from 'connected-react-router';
|
||||||
import { costInfoReducer, blacklistReducer, filteredReducer, statsReducer } from 'lbryinc';
|
import { costInfoReducer, statsReducer } from 'lbryinc';
|
||||||
import { claimsReducer } from 'redux/reducers/claims';
|
import { claimsReducer } from 'redux/reducers/claims';
|
||||||
import { fileInfoReducer } from 'redux/reducers/file_info';
|
import { fileInfoReducer } from 'redux/reducers/file_info';
|
||||||
import { walletReducer } from 'redux/reducers/wallet';
|
import { walletReducer } from 'redux/reducers/wallet';
|
||||||
|
@ -25,8 +25,6 @@ export default (history) =>
|
||||||
combineReducers({
|
combineReducers({
|
||||||
router: connectRouter(history),
|
router: connectRouter(history),
|
||||||
app: appReducer,
|
app: appReducer,
|
||||||
blacklist: blacklistReducer,
|
|
||||||
filtered: filteredReducer,
|
|
||||||
claims: claimsReducer,
|
claims: claimsReducer,
|
||||||
comments: commentsReducer,
|
comments: commentsReducer,
|
||||||
content: contentReducer,
|
content: contentReducer,
|
||||||
|
|
|
@ -86,8 +86,25 @@ export function doResolveUris(
|
||||||
// https://github.com/facebook/flow/issues/2221
|
// https://github.com/facebook/flow/issues/2221
|
||||||
if (uriResolveInfo) {
|
if (uriResolveInfo) {
|
||||||
if (uriResolveInfo.error) {
|
if (uriResolveInfo.error) {
|
||||||
// $FlowFixMe
|
// TODO: handled filtered too
|
||||||
resolveInfo[uri] = { ...fallbackResolveInfo, errorCensor: uriResolveInfo.error.censor };
|
if (uriResolveInfo.error.name === 'BLOCKED') {
|
||||||
|
// $FlowFixMe
|
||||||
|
resolveInfo[uri] = {
|
||||||
|
// $FlowFixMe
|
||||||
|
...fallbackResolveInfo,
|
||||||
|
errorCensor: {
|
||||||
|
// $FlowFixMe
|
||||||
|
...uriResolveInfo.error.censor,
|
||||||
|
// $FlowFixMe
|
||||||
|
text: uriResolveInfo.error.text,
|
||||||
|
}, // $FlowFixMe
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// $FlowFixMe
|
||||||
|
resolveInfo[uri] = {
|
||||||
|
...fallbackResolveInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (checkReposts) {
|
if (checkReposts) {
|
||||||
if (uriResolveInfo.reposted_claim) {
|
if (uriResolveInfo.reposted_claim) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { createCachedSelector } from 're-reselect';
|
import { createCachedSelector } from 're-reselect';
|
||||||
import { isClaimNsfw, filterClaims } from 'util/claim';
|
import { isClaimNsfw, filterClaims } from 'util/claim';
|
||||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
|
||||||
import * as CLAIM from 'constants/claim';
|
import * as CLAIM from 'constants/claim';
|
||||||
|
|
||||||
type State = { claims: any };
|
type State = { claims: any };
|
||||||
|
@ -84,6 +83,7 @@ export const selectClaimIdForUri = (state: State, uri: string) => selectClaimIds
|
||||||
|
|
||||||
export const selectReflectingById = (state: State) => selectState(state).reflectingById;
|
export const selectReflectingById = (state: State) => selectState(state).reflectingById;
|
||||||
|
|
||||||
|
// TODO: remove this.
|
||||||
export const makeSelectBlacklistedDueToDMCA = (claimUri: string) =>
|
export const makeSelectBlacklistedDueToDMCA = (claimUri: string) =>
|
||||||
createSelector(makeSelectClaimErrorCensor(claimUri), (claimError) => {
|
createSelector(makeSelectClaimErrorCensor(claimUri), (claimError) => {
|
||||||
if (!claimError) {
|
if (!claimError) {
|
||||||
|
@ -96,33 +96,20 @@ export const makeSelectClaimErrorCensor = (claimUri: string) =>
|
||||||
createSelector(selectState, (state) => state.blacklistedByUri[claimUri]);
|
createSelector(selectState, (state) => state.blacklistedByUri[claimUri]);
|
||||||
|
|
||||||
export const makeSelectIsBlacklisted = (claimUri: string) =>
|
export const makeSelectIsBlacklisted = (claimUri: string) =>
|
||||||
createSelector(
|
createSelector(makeSelectClaimErrorCensor(claimUri), makeSelectClaimForUri(claimUri), (errorCensor, claim) => {
|
||||||
makeSelectClaimErrorCensor(claimUri),
|
if (errorCensor) {
|
||||||
selectBlackListedOutpoints,
|
return true;
|
||||||
makeSelectClaimForUri(claimUri),
|
|
||||||
(errorCensor, legacyBlacklistedList, claim) => {
|
|
||||||
if (errorCensor) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Fallback to legacy just in case.
|
|
||||||
if (!claim) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!legacyBlacklistedList) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const signingChannel = claim.signing_channel;
|
|
||||||
if (!signingChannel) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const isInLegacyBlacklist = legacyBlacklistedList.some(
|
|
||||||
(outpoint) =>
|
|
||||||
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
|
||||||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
|
||||||
);
|
|
||||||
return isInLegacyBlacklist;
|
|
||||||
}
|
}
|
||||||
);
|
// Fallback to legacy just in case.
|
||||||
|
if (!claim) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const signingChannel = claim.signing_channel;
|
||||||
|
if (!signingChannel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
export const makeSelectClaimForClaimId = (claimId: string) => createSelector(selectClaimsById, (byId) => byId[claimId]);
|
export const makeSelectClaimForClaimId = (claimId: string) => createSelector(selectClaimsById, (byId) => byId[claimId]);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { createCachedSelector } from 're-reselect';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { selectMentionSearchResults, selectMentionQuery } from 'redux/selectors/search';
|
import { selectMentionSearchResults, selectMentionQuery } from 'redux/selectors/search';
|
||||||
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
|
|
||||||
import {
|
import {
|
||||||
selectClaimsById,
|
selectClaimsById,
|
||||||
selectMyClaimIdsRaw,
|
selectMyClaimIdsRaw,
|
||||||
|
@ -198,10 +197,8 @@ const filterCommentsDepOnList = {
|
||||||
claimsById: selectClaimsById,
|
claimsById: selectClaimsById,
|
||||||
myClaimIds: selectMyClaimIdsRaw,
|
myClaimIds: selectMyClaimIdsRaw,
|
||||||
myChannelClaimIds: selectMyChannelClaimIds,
|
myChannelClaimIds: selectMyChannelClaimIds,
|
||||||
mutedChannels: selectMutedChannels,
|
|
||||||
personalBlockList: selectModerationBlockList,
|
personalBlockList: selectModerationBlockList,
|
||||||
blacklistedMap: selectBlacklistedOutpointMap,
|
mutedChannels: selectMutedChannels,
|
||||||
filteredMap: selectFilteredOutpointMap,
|
|
||||||
showMatureContent: selectShowMatureContent,
|
showMatureContent: selectShowMatureContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -284,16 +281,8 @@ const filterComments = (comments: Array<Comment>, claimId?: string, filterInputs
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
const {
|
const { claimsById, myClaimIds, myChannelClaimIds, personalBlockList, mutedChannels, showMatureContent } =
|
||||||
claimsById,
|
filterProps;
|
||||||
myClaimIds,
|
|
||||||
myChannelClaimIds,
|
|
||||||
mutedChannels,
|
|
||||||
personalBlockList,
|
|
||||||
blacklistedMap,
|
|
||||||
filteredMap,
|
|
||||||
showMatureContent,
|
|
||||||
} = filterProps;
|
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
? comments.filter((comment) => {
|
? comments.filter((comment) => {
|
||||||
|
@ -317,11 +306,6 @@ const filterComments = (comments: Array<Comment>, claimId?: string, filterInputs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const outpoint = `${channelClaim.txid}:${channelClaim.nout}`;
|
|
||||||
if (blacklistedMap[outpoint] || filteredMap[outpoint]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!showMatureContent) {
|
if (!showMatureContent) {
|
||||||
const claimIsMature = isClaimNsfw(channelClaim);
|
const claimIsMature = isClaimNsfw(channelClaim);
|
||||||
if (claimIsMature) {
|
if (claimIsMature) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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, '\\\\')}"`,
|
||||||
|
|
94
yarn.lock
94
yarn.lock
|
@ -3857,8 +3857,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 +3870,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 +5315,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 +7994,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 +8308,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 +9074,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 +11450,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
|
||||||
|
@ -16426,7 +16390,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 +16401,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 +17641,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:
|
||||||
|
@ -19561,10 +19501,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 +19561,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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue